From 4e106e6cc5b0f927a8c5a826bcc0f715e52dceca Mon Sep 17 00:00:00 2001 From: =?utf8?q?Aur=C3=A9lien=20COUDERC?= Date: Fri, 6 Jan 2023 23:22:19 +0000 Subject: [PATCH 1/1] Import kwin_5.26.5.orig.tar.xz [dgit import orig kwin_5.26.5.orig.tar.xz] --- .clang-format | 83 + .gitignore | 27 + .gitlab-ci.yml | 12 + .kde-ci.yml | 40 + CMakeLists.txt | 490 ++ CONTRIBUTING.md | 125 + KWinDBusInterfaceConfig.cmake.in | 10 + LICENSES/BSD-3-Clause.txt | 26 + LICENSES/CC0-1.0.txt | 121 + LICENSES/GPL-2.0-only.txt | 319 ++ LICENSES/GPL-2.0-or-later.txt | 319 ++ LICENSES/GPL-3.0-only.txt | 625 +++ LICENSES/GPL-3.0-or-later.txt | 625 +++ LICENSES/LGPL-2.0-only.txt | 446 ++ LICENSES/LGPL-2.0-or-later.txt | 446 ++ LICENSES/LGPL-2.1-only.txt | 467 ++ LICENSES/LGPL-2.1-or-later.txt | 175 + LICENSES/LGPL-3.0-only.txt | 163 + LICENSES/LicenseRef-KDE-Accepted-GPL.txt | 12 + LICENSES/LicenseRef-KDE-Accepted-LGPL.txt | 12 + LICENSES/MIT.txt | 19 + Mainpage.dox | 19 + README.md | 49 + autotests/CMakeLists.txt | 251 + autotests/integration/CMakeLists.txt | 171 + autotests/integration/activation_test.cpp | 570 ++ autotests/integration/activities_test.cpp | 148 + .../integration/buffer_size_change_test.cpp | 114 + .../data/anim-data-delete-effect/effect.js | 14 + autotests/integration/data/example.desktop | 3 + .../data/rules/maximize-vert-apply-initial | 13 + autotests/integration/dbus_interface_test.cpp | 385 ++ autotests/integration/debug_console_test.cpp | 497 ++ .../integration/decoration_input_test.cpp | 789 +++ .../integration/desktop_window_x11_test.cpp | 163 + .../dont_crash_aurorae_destroy_deco.cpp | 139 + .../dont_crash_cancel_animation.cpp | 110 + .../dont_crash_cursor_physical_size_empty.cpp | 99 + .../integration/dont_crash_empty_deco.cpp | 106 + autotests/integration/dont_crash_glxgears.cpp | 92 + .../integration/dont_crash_no_border.cpp | 108 + .../dont_crash_reinitialize_compositor.cpp | 149 + .../dont_crash_useractions_menu.cpp | 104 + autotests/integration/effects/CMakeLists.txt | 11 + .../desktop_switching_animation_test.cpp | 142 + .../effects/maximize_animation_test.cpp | 182 + .../effects/minimize_animation_test.cpp | 174 + .../popup_open_close_animation_test.cpp | 265 + .../effects/scripted_effects_test.cpp | 758 +++ .../effects/scripts/animationTest.js | 12 + .../effects/scripts/animationTestMulti.js | 24 + .../effects/scripts/completeTest.js | 19 + .../effects/scripts/effectContext.js | 6 + .../effects/scripts/effectsHandler.js | 16 + .../effects/scripts/fullScreenEffectTest.js | 8 + .../scripts/fullScreenEffectTestGlobal.js | 21 + .../scripts/fullScreenEffectTestMulti.js | 22 + ...rabAlreadyGrabbedWindowForcedTest_owner.js | 7 + ...rabAlreadyGrabbedWindowForcedTest_thief.js | 7 + .../grabAlreadyGrabbedWindowTest_grabber.js | 7 + .../grabAlreadyGrabbedWindowTest_owner.js | 7 + .../integration/effects/scripts/grabTest.js | 7 + .../effects/scripts/keepAliveTest.js | 13 + .../effects/scripts/keepAliveTestDontKeep.js | 13 + .../redirectAnimateDontTerminateTest.js | 18 + .../scripts/redirectAnimateTerminateTest.js | 18 + .../scripts/redirectSetDontTerminateTest.js | 19 + .../scripts/redirectSetTerminateTest.js | 19 + .../effects/scripts/screenEdgeTest.js | 3 + .../effects/scripts/screenEdgeTouchTest.js | 3 + .../effects/scripts/shortcutsTest.js | 3 + .../integration/effects/scripts/ungrabTest.js | 15 + .../effects/slidingpopups_test.cpp | 351 ++ .../toplevel_open_close_animation_test.cpp | 204 + .../integration/effects/translucency_test.cpp | 227 + .../integration/effects/wobbly_shade_test.cpp | 178 + autotests/integration/fakes/CMakeLists.txt | 2 + .../fakes/org.kde.kdecoration2/CMakeLists.txt | 15 + .../fakedecoration_with_shadows.cpp | 65 + .../fakedecoration_with_shadows.json | 15 + .../integration/generic_scene_opengl_test.cpp | 86 + .../integration/generic_scene_opengl_test.h | 29 + .../integration/globalshortcuts_test.cpp | 446 ++ autotests/integration/helper/CMakeLists.txt | 11 + autotests/integration/helper/copy.cpp | 60 + autotests/integration/helper/kill.cpp | 35 + autotests/integration/helper/paste.cpp | 56 + .../integration/idle_inhibition_test.cpp | 304 + .../integration/input_stacking_order.cpp | 160 + autotests/integration/inputmethod_test.cpp | 694 +++ autotests/integration/internal_window.cpp | 824 +++ .../integration/keyboard_layout_test.cpp | 558 ++ .../keymap_creation_failure_test.cpp | 89 + autotests/integration/kwin_wayland_test.cpp | 199 + autotests/integration/kwin_wayland_test.h | 686 +++ autotests/integration/kwinbindings_test.cpp | 249 + .../integration/layershellv1window_test.cpp | 560 ++ autotests/integration/lockscreen.cpp | 756 +++ autotests/integration/maximize_test.cpp | 330 ++ .../modifier_only_shortcut_test.cpp | 374 ++ .../integration/move_resize_window_test.cpp | 1102 ++++ autotests/integration/nightcolor_test.cpp | 106 + .../integration/no_global_shortcuts_test.cpp | 268 + autotests/integration/outputchanges_test.cpp | 454 ++ autotests/integration/placement_test.cpp | 384 ++ autotests/integration/plasma_surface_test.cpp | 394 ++ autotests/integration/plasmawindow_test.cpp | 310 ++ autotests/integration/platformcursor.cpp | 60 + .../integration/pointer_constraints_test.cpp | 366 ++ autotests/integration/pointer_input.cpp | 1846 +++++++ .../protocols/wlr-layer-shell-unstable-v1.xml | 325 ++ autotests/integration/quick_tiling_test.cpp | 875 +++ .../integration/scene_opengl_es_test.cpp | 22 + autotests/integration/scene_opengl_test.cpp | 22 + autotests/integration/scene_qpainter_test.cpp | 386 ++ autotests/integration/screen_changes_test.cpp | 178 + .../screenedge_client_show_test.cpp | 270 + autotests/integration/screenedges_test.cpp | 347 ++ autotests/integration/screens_test.cpp | 190 + .../integration/scripting/CMakeLists.txt | 2 + .../scripting/minimizeall_test.cpp | 155 + .../integration/scripting/screenedge_test.cpp | 280 + .../scripting/scripts/screenedge.js | 1 + .../scripting/scripts/screenedgetouch.qml | 10 + .../scripting/scripts/screenedgeunregister.js | 12 + .../scripting/scripts/touchScreenedge.js | 1 + autotests/integration/shade_test.cpp | 127 + .../integration/showing_desktop_test.cpp | 114 + autotests/integration/stacking_order_test.cpp | 857 +++ autotests/integration/struts_test.cpp | 990 ++++ autotests/integration/tabbox_test.cpp | 236 + autotests/integration/test_helpers.cpp | 1433 +++++ .../integration/test_virtualkeyboard_dbus.cpp | 125 + autotests/integration/touch_input_test.cpp | 429 ++ autotests/integration/transient_placement.cpp | 555 ++ .../integration/virtual_desktop_test.cpp | 283 + autotests/integration/window_rules_test.cpp | 220 + .../integration/window_selection_test.cpp | 524 ++ autotests/integration/x11_window_test.cpp | 1143 ++++ .../integration/xdgshellwindow_rules_test.cpp | 2970 ++++++++++ autotests/integration/xdgshellwindow_test.cpp | 1935 +++++++ autotests/integration/xwayland_input_test.cpp | 281 + .../integration/xwayland_selections_test.cpp | 143 + .../integration/xwaylandserver_crash_test.cpp | 136 + .../xwaylandserver_restart_test.cpp | 116 + autotests/libinput/CMakeLists.txt | 62 + autotests/libinput/device_test.cpp | 2355 ++++++++ autotests/libinput/gesture_event_test.cpp | 204 + autotests/libinput/input_event_test.cpp | 179 + autotests/libinput/key_event_test.cpp | 106 + autotests/libinput/mock_libinput.cpp | 989 ++++ autotests/libinput/mock_libinput.h | 172 + autotests/libinput/pointer_event_test.cpp | 222 + autotests/libinput/switch_event_test.cpp | 89 + autotests/libinput/touch_event_test.cpp | 120 + autotests/libkwineffects/CMakeLists.txt | 23 + .../amd-catalyst-radeonhd-7700M-3.1.13399 | 18 + .../data/glplatform/amd-gallium-bonaire-3.0 | 21 + .../glplatform/amd-gallium-cayman-gles-3.0 | 22 + .../data/glplatform/amd-gallium-hawaii-3.0 | 21 + .../data/glplatform/amd-gallium-navi-4.5 | 21 + .../glplatform/amd-gallium-radeon-r9-290-4.5 | 21 + .../amd-gallium-radeon-rx-480-series-4.5 | 21 + .../amd-gallium-radeon-rx-550-series-3.1 | 21 + .../amd-gallium-radeon-rx-5700-xt-4.6 | 21 + .../amd-gallium-radeon-rx-580-series-4.5 | 21 + .../amd-gallium-radeon-rx-vega-56-4.5 | 21 + .../amd-gallium-radeon-rx-vega-64-4.5 | 21 + .../data/glplatform/amd-gallium-redwood-3.0 | 21 + .../data/glplatform/amd-gallium-tonga-4.1 | 21 + .../data/glplatform/intel-broadwell-gt2-3.3 | 19 + .../data/glplatform/intel-haswell-mobile-3.3 | 19 + .../glplatform/intel-ivybridge-desktop-3.0 | 20 + .../glplatform/intel-ivybridge-desktop-3.3 | 19 + .../glplatform/intel-ivybridge-mobile-3.3 | 19 + .../data/glplatform/intel-kabylake-gt2-4.6 | 19 + .../glplatform/intel-sandybridge-mobile-3.3 | 19 + .../data/glplatform/intel-skylake-gt2-3.0 | 19 + .../data/glplatform/llvmpipe-10.0 | 22 + .../data/glplatform/llvmpipe-3.0 | 22 + .../data/glplatform/llvmpipe-5.0 | 22 + .../glplatform/nvidia-geforce-gtx-560-4.5 | 19 + .../glplatform/nvidia-geforce-gtx-660-3.1 | 18 + .../glplatform/nvidia-geforce-gtx-950-4.5 | 18 + .../glplatform/nvidia-geforce-gtx-970-3.1 | 18 + .../glplatform/nvidia-geforce-gtx-970M-3.1 | 18 + .../glplatform/nvidia-geforce-gtx-980-3.1 | 18 + .../glplatform/panfrost-malit860-desktop-3.0 | 19 + .../qualcomm-adreno-330-libhybris-gles-3.0 | 16 + .../libkwineffects/data/glplatform/virgl-3.1 | 22 + .../libkwineffects/kwinglplatformtest.cpp | 279 + autotests/libkwineffects/mock_gl.cpp | 59 + autotests/libkwineffects/mock_gl.h | 30 + autotests/libkwineffects/timelinetest.cpp | 415 ++ .../libkwineffects/windowquadlisttest.cpp | 212 + autotests/onscreennotificationtest.cpp | 124 + autotests/onscreennotificationtest.h | 24 + .../opengl_context_attribute_builder_test.cpp | 465 ++ autotests/tabbox/CMakeLists.txt | 104 + autotests/tabbox/mock_tabboxclient.cpp | 26 + autotests/tabbox/mock_tabboxclient.h | 74 + autotests/tabbox/mock_tabboxhandler.cpp | 111 + autotests/tabbox/mock_tabboxhandler.h | 118 + autotests/tabbox/test_desktopchain.cpp | 252 + autotests/tabbox/test_tabbox_clientmodel.cpp | 87 + autotests/tabbox/test_tabbox_clientmodel.h | 42 + autotests/tabbox/test_tabbox_config.cpp | 77 + autotests/tabbox/test_tabbox_handler.cpp | 56 + autotests/test_client_machine.cpp | 150 + autotests/test_ftrace.cpp | 72 + autotests/test_gestures.cpp | 689 +++ autotests/test_utils.cpp | 71 + autotests/test_virtual_desktops.cpp | 652 +++ autotests/test_window_paint_data.cpp | 179 + autotests/test_x11_timestamp_update.cpp | 115 + autotests/test_xcb_size_hints.cpp | 369 ++ autotests/test_xcb_window.cpp | 206 + autotests/test_xcb_wrapper.cpp | 534 ++ autotests/test_xkb.cpp | 482 ++ autotests/testutils.h | 55 + autotests/xcb_scaling_mock.cpp | 34 + cmake/modules/FindLibcap.cmake | 38 + cmake/modules/FindLibdrm.cmake | 105 + cmake/modules/FindXKB.cmake | 89 + cmake/modules/FindXwayland.cmake | 39 + cmake/modules/Findgbm.cmake | 104 + cmake/modules/Findhwdata.cmake | 25 + cmake/modules/Findlcms2.cmake | 75 + data/CMakeLists.txt | 14 + data/icons/16-apps-kwin.png | Bin 0 -> 380 bytes data/icons/32-apps-kwin.png | Bin 0 -> 611 bytes data/icons/48-apps-kwin.png | Bin 0 -> 877 bytes data/icons/CMakeLists.txt | 11 + data/icons/sc-apps-kwin.svgz | Bin 0 -> 3106 bytes data/org_kde_kwin.categories | 23 + data/update_default_rules.cpp | 57 + doc/CMakeLists.txt | 9 + doc/TESTING.md | 33 + doc/coding-conventions.md | 86 + doc/desktop/CMakeLists.txt | 2 + doc/desktop/index.docbook | 84 + doc/kwindecoration/CMakeLists.txt | 2 + doc/kwindecoration/button.png | Bin 0 -> 26082 bytes doc/kwindecoration/configure.png | Bin 0 -> 1080 bytes doc/kwindecoration/decoration.png | Bin 0 -> 18726 bytes doc/kwindecoration/index.docbook | 146 + doc/kwindecoration/main.png | Bin 0 -> 35633 bytes doc/kwineffects/CMakeLists.txt | 2 + doc/kwineffects/configure-effects.png | Bin 0 -> 512 bytes doc/kwineffects/dialog-information.png | Bin 0 -> 745 bytes doc/kwineffects/index.docbook | 86 + doc/kwineffects/video.png | Bin 0 -> 375 bytes doc/kwinscreenedges/CMakeLists.txt | 2 + doc/kwinscreenedges/index.docbook | 62 + doc/kwintabbox/CMakeLists.txt | 2 + doc/kwintabbox/index.docbook | 104 + doc/kwintouchscreen/CMakeLists.txt | 2 + doc/kwintouchscreen/index.docbook | 40 + doc/kwinvirtualkeyboard/CMakeLists.txt | 2 + doc/kwinvirtualkeyboard/index.docbook | 44 + doc/windowbehaviour/CMakeLists.txt | 2 + doc/windowbehaviour/index.docbook | 1210 ++++ doc/windowspecific/CMakeLists.txt | 2 + doc/windowspecific/Face-smile.png | Bin 0 -> 1233 bytes doc/windowspecific/akgregator-info.png | Bin 0 -> 46542 bytes doc/windowspecific/akregator-attributes.png | Bin 0 -> 68855 bytes doc/windowspecific/akregator-fav.png | Bin 0 -> 157267 bytes doc/windowspecific/config-win-behavior.png | Bin 0 -> 36233 bytes doc/windowspecific/emacs-attribute.png | Bin 0 -> 71059 bytes doc/windowspecific/emacs-info.png | Bin 0 -> 34767 bytes .../focus-stealing-pop2top-attribute.png | Bin 0 -> 58619 bytes doc/windowspecific/index.docbook | 1021 ++++ doc/windowspecific/knotes-attribute.png | Bin 0 -> 35453 bytes doc/windowspecific/knotes-info.png | Bin 0 -> 21435 bytes doc/windowspecific/kopete-attribute-2.png | Bin 0 -> 49387 bytes doc/windowspecific/kopete-chat-attribute.png | Bin 0 -> 50398 bytes doc/windowspecific/kopete-chat-info.png | Bin 0 -> 32735 bytes doc/windowspecific/kopete-info.png | Bin 0 -> 31642 bytes doc/windowspecific/kwin-detect-window.png | Bin 0 -> 27744 bytes doc/windowspecific/kwin-kopete-rules.png | Bin 0 -> 47435 bytes doc/windowspecific/kwin-rule-editor.png | Bin 0 -> 40361 bytes .../kwin-rules-main-n-akregator.png | Bin 0 -> 49262 bytes doc/windowspecific/kwin-rules-main.png | Bin 0 -> 48580 bytes doc/windowspecific/kwin-rules-ordering.png | Bin 0 -> 29609 bytes doc/windowspecific/kwin-window-attributes.png | Bin 0 -> 61505 bytes doc/windowspecific/kwin-window-matching.png | Bin 0 -> 53362 bytes doc/windowspecific/pager-4-desktops.png | Bin 0 -> 11817 bytes .../tbird-compose-attribute.png | Bin 0 -> 59885 bytes doc/windowspecific/tbird-compose-info.png | Bin 0 -> 36961 bytes doc/windowspecific/tbird-main-attribute.png | Bin 0 -> 73055 bytes doc/windowspecific/tbird-main-info.png | Bin 0 -> 36343 bytes .../tbird-reminder-attribute-2.png | Bin 0 -> 52173 bytes doc/windowspecific/tbird-reminder-info.png | Bin 0 -> 36154 bytes doc/windowspecific/window-matching-emacs.png | Bin 0 -> 55919 bytes doc/windowspecific/window-matching-init.png | Bin 0 -> 47718 bytes doc/windowspecific/window-matching-knotes.png | Bin 0 -> 37378 bytes .../window-matching-kopete-chat.png | Bin 0 -> 52380 bytes doc/windowspecific/window-matching-kopete.png | Bin 0 -> 50418 bytes .../window-matching-ready-akregator.png | Bin 0 -> 50090 bytes .../window-matching-tbird-compose.png | Bin 0 -> 50248 bytes .../window-matching-tbird-main.png | Bin 0 -> 55370 bytes .../window-matching-tbird-reminder.png | Bin 0 -> 55508 bytes kconf_update/CMakeLists.txt | 29 + kconf_update/kwin-5.16-auto-bordersize.sh | 24 + kconf_update/kwin-5.18-move-animspeed.py | 14 + .../kwin-5.21-desktop-grid-click-behavior.py | 16 + kconf_update/kwin-5.21-no-swap-encourage.py | 11 + .../kwin-5.23-disable-translucency-effect.sh | 23 + kconf_update/kwin-5.23-remove-cover-switch.py | 11 + kconf_update/kwin-5.23-remove-cubeslide.py | 11 + kconf_update/kwin-5.23-remove-flip-switch.py | 11 + .../kwin-5.23-remove-xrender-backend.py | 11 + .../kwin-5.25-effect-pluginid-config-group.py | 35 + kconf_update/kwin.upd | 99 + kconf_update/kwinrules-5.19-placement.pl | 22 + .../kwinrules-5.23-virtual-desktop-ids.py | 47 + kconf_update/kwinrules.upd | 11 + logo.png | Bin 0 -> 4200 bytes plasma-kwin_wayland.service.in | 8 + plasma-kwin_x11.service.in | 10 + po/af/kcm_kwindecoration.po | 290 + po/af/kcm_kwinrules.po | 946 ++++ po/af/kcmkwm.po | 1438 +++++ po/af/kwin.po | 2773 ++++++++++ po/af/kwin_clients.po | 132 + po/ar/kcm_kwin_effects.po | 93 + po/ar/kcm_kwin_scripts.po | 76 + po/ar/kcm_kwin_virtualdesktops.po | 143 + po/ar/kcm_kwindecoration.po | 296 + po/ar/kcm_kwinrules.po | 947 ++++ po/ar/kcm_kwintabbox.po | 236 + po/ar/kcm_virtualkeyboard.po | 54 + po/ar/kcmkwincommon.po | 82 + po/ar/kcmkwincompositing.po | 240 + po/ar/kcmkwinscreenedges.po | 234 + po/ar/kcmkwm.po | 1390 +++++ po/ar/kwin.po | 2692 +++++++++ po/ar/kwin_clients.po | 130 + po/ar/kwin_effects.po | 1121 ++++ po/ar/kwin_scripting.po | 36 + po/ar/kwin_scripts.po | 62 + po/as/kwin.po | 2772 ++++++++++ po/ast/kcm_kwin_effects.po | 92 + po/ast/kcm_kwin_scripts.po | 75 + po/ast/kcm_kwin_virtualdesktops.po | 137 + po/ast/kcmkwincompositing.po | 228 + po/ast/kwin.po | 2663 +++++++++ po/ast/kwin_effects.po | 1113 ++++ po/az/kcm_kwin_effects.po | 93 + po/az/kcm_kwin_scripts.po | 75 + po/az/kcm_kwin_virtualdesktops.po | 137 + po/az/kcm_kwindecoration.po | 293 + po/az/kcm_kwinrules.po | 952 ++++ po/az/kcm_kwintabbox.po | 239 + po/az/kcm_virtualkeyboard.po | 53 + po/az/kcmkwincommon.po | 81 + po/az/kcmkwincompositing.po | 240 + po/az/kcmkwinscreenedges.po | 239 + po/az/kcmkwm.po | 1400 +++++ po/az/kwin.po | 2709 +++++++++ po/az/kwin_clients.po | 127 + po/az/kwin_effects.po | 1116 ++++ po/az/kwin_scripting.po | 34 + po/az/kwin_scripts.po | 61 + po/be/kcm_kwin_virtualdesktops.po | 140 + po/be/kcm_kwindecoration.po | 295 + po/be/kcm_kwinrules.po | 923 ++++ po/be/kcmkwincompositing.po | 227 + po/be/kcmkwm.po | 1291 +++++ po/be/kwin.po | 2773 ++++++++++ po/be/kwin_clients.po | 132 + po/be/kwin_effects.po | 1139 ++++ po/be@latin/kwin.po | 2782 ++++++++++ po/bg/kcm_kwin_effects.po | 93 + po/bg/kcm_kwin_scripts.po | 75 + po/bg/kcm_kwin_virtualdesktops.po | 139 + po/bg/kcm_kwindecoration.po | 294 + po/bg/kcm_kwinrules.po | 952 ++++ po/bg/kcm_kwintabbox.po | 241 + po/bg/kcm_virtualkeyboard.po | 53 + po/bg/kcmkwincommon.po | 81 + po/bg/kcmkwincompositing.po | 246 + po/bg/kcmkwinscreenedges.po | 238 + po/bg/kcmkwm.po | 1418 +++++ po/bg/kwin.po | 2711 +++++++++ po/bg/kwin_clients.po | 129 + po/bg/kwin_effects.po | 1121 ++++ po/bg/kwin_scripting.po | 34 + po/bg/kwin_scripts.po | 61 + po/bn/kcmkwm.po | 1389 +++++ po/bn/kwin.po | 2764 ++++++++++ po/bn/kwin_effects.po | 1156 ++++ po/bn_IN/kcm_kwin_virtualdesktops.po | 137 + po/bn_IN/kcm_kwindecoration.po | 292 + po/bn_IN/kcm_kwinrules.po | 928 ++++ po/bn_IN/kcmkwm.po | 1300 +++++ po/bn_IN/kwin.po | 2771 ++++++++++ po/bn_IN/kwin_clients.po | 129 + po/bn_IN/kwin_effects.po | 1145 ++++ po/br/kcm_kwindecoration.po | 288 + po/br/kcm_kwinrules.po | 912 +++ po/br/kcmkwm.po | 1296 +++++ po/br/kwin.po | 2753 ++++++++++ po/br/kwin_clients.po | 126 + po/bs/kcm_kwin_scripts.po | 77 + po/bs/kcm_kwin_virtualdesktops.po | 195 + po/bs/kcm_kwindecoration.po | 303 + po/bs/kcm_kwinrules.po | 1234 +++++ po/bs/kcm_kwintabbox.po | 242 + po/bs/kcmkwincompositing.po | 253 + po/bs/kcmkwinscreenedges.po | 284 + po/bs/kcmkwm.po | 1517 +++++ po/bs/kwin.po | 2784 ++++++++++ po/bs/kwin_clients.po | 605 ++ po/bs/kwin_effects.po | 1930 +++++++ po/bs/kwin_scripting.po | 37 + po/ca/docs/kcontrol/desktop/index.docbook | 128 + po/ca/docs/kcontrol/kwindecoration/button.png | Bin 0 -> 40217 bytes .../kcontrol/kwindecoration/decoration.png | Bin 0 -> 65246 bytes .../kcontrol/kwindecoration/index.docbook | 176 + po/ca/docs/kcontrol/kwindecoration/main.png | Bin 0 -> 61358 bytes po/ca/docs/kcontrol/kwineffects/index.docbook | 123 + .../kcontrol/kwinscreenedges/index.docbook | 78 + po/ca/docs/kcontrol/kwintabbox/index.docbook | 242 + .../kcontrol/kwintouchscreen/index.docbook | 53 + .../kwinvirtualkeyboard/index.docbook | 54 + .../kcontrol/windowbehaviour/index.docbook | 1396 +++++ .../kcontrol/windowspecific/index.docbook | 2202 ++++++++ po/ca/kcm_kwin_effects.po | 97 + po/ca/kcm_kwin_scripts.po | 80 + po/ca/kcm_kwin_virtualdesktops.po | 142 + po/ca/kcm_kwindecoration.po | 304 + po/ca/kcm_kwinrules.po | 954 ++++ po/ca/kcm_kwintabbox.po | 246 + po/ca/kcm_virtualkeyboard.po | 55 + po/ca/kcmkwincommon.po | 86 + po/ca/kcmkwincompositing.po | 244 + po/ca/kcmkwinscreenedges.po | 246 + po/ca/kcmkwm.po | 1426 +++++ po/ca/kwin.po | 2725 +++++++++ po/ca/kwin_clients.po | 135 + po/ca/kwin_effects.po | 1123 ++++ po/ca/kwin_scripting.po | 41 + po/ca/kwin_scripts.po | 65 + po/ca@valencia/kcm_kwin_effects.po | 97 + po/ca@valencia/kcm_kwin_scripts.po | 80 + po/ca@valencia/kcm_kwin_virtualdesktops.po | 142 + po/ca@valencia/kcm_kwindecoration.po | 304 + po/ca@valencia/kcm_kwinrules.po | 956 ++++ po/ca@valencia/kcm_kwintabbox.po | 246 + po/ca@valencia/kcm_virtualkeyboard.po | 55 + po/ca@valencia/kcmkwincommon.po | 86 + po/ca@valencia/kcmkwincompositing.po | 245 + po/ca@valencia/kcmkwinscreenedges.po | 246 + po/ca@valencia/kcmkwm.po | 1423 +++++ po/ca@valencia/kwin.po | 2727 +++++++++ po/ca@valencia/kwin_clients.po | 134 + po/ca@valencia/kwin_effects.po | 1123 ++++ po/ca@valencia/kwin_scripting.po | 41 + po/ca@valencia/kwin_scripts.po | 65 + po/cs/kcm_kwin_effects.po | 91 + po/cs/kcm_kwin_scripts.po | 76 + po/cs/kcm_kwin_virtualdesktops.po | 138 + po/cs/kcm_kwindecoration.po | 284 + po/cs/kcm_kwinrules.po | 926 ++++ po/cs/kcm_kwintabbox.po | 238 + po/cs/kcm_virtualkeyboard.po | 53 + po/cs/kcmkwincommon.po | 86 + po/cs/kcmkwincompositing.po | 235 + po/cs/kcmkwinscreenedges.po | 239 + po/cs/kcmkwm.po | 1307 +++++ po/cs/kwin.po | 2683 +++++++++ po/cs/kwin_clients.po | 128 + po/cs/kwin_effects.po | 1111 ++++ po/cs/kwin_scripting.po | 36 + po/cs/kwin_scripts.po | 61 + po/csb/kcm_kwin_virtualdesktops.po | 139 + po/csb/kwin.po | 2776 ++++++++++ po/csb/kwin_clients.po | 137 + po/csb/kwin_effects.po | 1176 ++++ po/cy/kcm_kwindecoration.po | 287 + po/cy/kcm_kwinrules.po | 918 ++++ po/cy/kcmkwm.po | 1395 +++++ po/cy/kwin.po | 2770 ++++++++++ po/cy/kwin_clients.po | 125 + po/da/kcm_kwin_effects.po | 93 + po/da/kcm_kwin_scripts.po | 75 + po/da/kcm_kwin_virtualdesktops.po | 139 + po/da/kcm_kwindecoration.po | 281 + po/da/kcm_kwinrules.po | 975 ++++ po/da/kcm_kwintabbox.po | 241 + po/da/kcmkwincommon.po | 81 + po/da/kcmkwincompositing.po | 250 + po/da/kcmkwinscreenedges.po | 239 + po/da/kcmkwm.po | 1464 +++++ po/da/kwin.po | 2731 +++++++++ po/da/kwin_clients.po | 127 + po/da/kwin_effects.po | 1153 ++++ po/da/kwin_scripting.po | 34 + po/da/kwin_scripts.po | 61 + po/de/docs/kcontrol/desktop/index.docbook | 156 + .../kcontrol/kwindecoration/index.docbook | 188 + po/de/docs/kcontrol/kwineffects/index.docbook | 137 + .../kcontrol/kwinscreenedges/index.docbook | 92 + po/de/docs/kcontrol/kwintabbox/index.docbook | 258 + .../kcontrol/kwintouchscreen/index.docbook | 67 + .../kwinvirtualkeyboard/index.docbook | 68 + .../kcontrol/windowbehaviour/index.docbook | 839 +++ .../kcontrol/windowspecific/index.docbook | 2218 ++++++++ po/de/kcm_kwin_effects.po | 92 + po/de/kcm_kwin_scripts.po | 72 + po/de/kcm_kwin_virtualdesktops.po | 138 + po/de/kcm_kwindecoration.po | 282 + po/de/kcm_kwinrules.po | 953 ++++ po/de/kcm_kwintabbox.po | 241 + po/de/kcm_virtualkeyboard.po | 53 + po/de/kcmkwincommon.po | 80 + po/de/kcmkwincompositing.po | 244 + po/de/kcmkwinscreenedges.po | 240 + po/de/kcmkwm.po | 1429 +++++ po/de/kwin.po | 3125 +++++++++++ po/de/kwin_clients.po | 130 + po/de/kwin_effects.po | 1136 ++++ po/de/kwin_scripting.po | 31 + po/de/kwin_scripts.po | 57 + po/el/kcm_kwin_scripts.po | 78 + po/el/kcm_kwin_virtualdesktops.po | 140 + po/el/kcm_kwindecoration.po | 315 ++ po/el/kcm_kwinrules.po | 993 ++++ po/el/kcm_kwintabbox.po | 243 + po/el/kcmkwincompositing.po | 261 + po/el/kcmkwinscreenedges.po | 252 + po/el/kcmkwm.po | 1550 ++++++ po/el/kwin.po | 2851 ++++++++++ po/el/kwin_clients.po | 135 + po/el/kwin_effects.po | 1182 ++++ po/el/kwin_scripting.po | 34 + po/el/kwin_scripts.po | 61 + po/en_GB/kcm_kwin_effects.po | 93 + po/en_GB/kcm_kwin_scripts.po | 75 + po/en_GB/kcm_kwin_virtualdesktops.po | 140 + po/en_GB/kcm_kwindecoration.po | 296 + po/en_GB/kcm_kwinrules.po | 942 ++++ po/en_GB/kcm_kwintabbox.po | 237 + po/en_GB/kcm_virtualkeyboard.po | 53 + po/en_GB/kcmkwincommon.po | 81 + po/en_GB/kcmkwincompositing.po | 239 + po/en_GB/kcmkwinscreenedges.po | 241 + po/en_GB/kcmkwm.po | 1400 +++++ po/en_GB/kwin.po | 2703 +++++++++ po/en_GB/kwin_clients.po | 127 + po/en_GB/kwin_effects.po | 1117 ++++ po/en_GB/kwin_scripting.po | 34 + po/en_GB/kwin_scripts.po | 61 + po/eo/kcm_kwin_virtualdesktops.po | 136 + po/eo/kcm_kwindecoration.po | 304 + po/eo/kcm_kwinrules.po | 944 ++++ po/eo/kcm_kwintabbox.po | 247 + po/eo/kcmkwincompositing.po | 228 + po/eo/kcmkwinscreenedges.po | 247 + po/eo/kcmkwm.po | 1422 +++++ po/eo/kwin.po | 2795 ++++++++++ po/eo/kwin_clients.po | 131 + po/eo/kwin_effects.po | 1167 ++++ po/es/docs/kcontrol/desktop/index.docbook | 198 + po/es/kcm_kwin_effects.po | 95 + po/es/kcm_kwin_scripts.po | 75 + po/es/kcm_kwin_virtualdesktops.po | 141 + po/es/kcm_kwindecoration.po | 309 ++ po/es/kcm_kwinrules.po | 958 ++++ po/es/kcm_kwintabbox.po | 246 + po/es/kcm_virtualkeyboard.po | 55 + po/es/kcmkwincommon.po | 83 + po/es/kcmkwincompositing.po | 247 + po/es/kcmkwinscreenedges.po | 245 + po/es/kcmkwm.po | 1435 +++++ po/es/kwin.po | 2735 +++++++++ po/es/kwin_clients.po | 134 + po/es/kwin_effects.po | 1123 ++++ po/es/kwin_scripting.po | 36 + po/es/kwin_scripts.po | 63 + po/et/kcm_kwin_effects.po | 93 + po/et/kcm_kwin_scripts.po | 76 + po/et/kcm_kwin_virtualdesktops.po | 138 + po/et/kcm_kwindecoration.po | 280 + po/et/kcm_kwinrules.po | 938 ++++ po/et/kcm_kwintabbox.po | 236 + po/et/kcmkwincommon.po | 81 + po/et/kcmkwincompositing.po | 250 + po/et/kcmkwinscreenedges.po | 239 + po/et/kcmkwm.po | 1409 +++++ po/et/kwin.po | 2748 +++++++++ po/et/kwin_clients.po | 126 + po/et/kwin_effects.po | 1134 ++++ po/et/kwin_scripting.po | 34 + po/et/kwin_scripts.po | 61 + po/eu/kcm_kwin_effects.po | 96 + po/eu/kcm_kwin_scripts.po | 79 + po/eu/kcm_kwin_virtualdesktops.po | 145 + po/eu/kcm_kwindecoration.po | 306 ++ po/eu/kcm_kwinrules.po | 961 ++++ po/eu/kcm_kwintabbox.po | 245 + po/eu/kcm_virtualkeyboard.po | 57 + po/eu/kcmkwincommon.po | 84 + po/eu/kcmkwincompositing.po | 246 + po/eu/kcmkwinscreenedges.po | 304 + po/eu/kcmkwm.po | 1418 +++++ po/eu/kwin.po | 2725 +++++++++ po/eu/kwin_clients.po | 133 + po/eu/kwin_effects.po | 1126 ++++ po/eu/kwin_scripting.po | 37 + po/eu/kwin_scripts.po | 64 + po/fa/kcm_kwin_scripts.po | 76 + po/fa/kcm_kwin_virtualdesktops.po | 135 + po/fa/kcm_kwindecoration.po | 293 + po/fa/kcm_kwinrules.po | 941 ++++ po/fa/kcm_kwintabbox.po | 233 + po/fa/kcmkwincompositing.po | 218 + po/fa/kcmkwinscreenedges.po | 231 + po/fa/kcmkwm.po | 1435 +++++ po/fa/kwin.po | 2778 ++++++++++ po/fa/kwin_clients.po | 134 + po/fa/kwin_effects.po | 1100 ++++ po/fi/kcm_kwin_effects.po | 91 + po/fi/kcm_kwin_scripts.po | 77 + po/fi/kcm_kwin_virtualdesktops.po | 246 + po/fi/kcm_kwindecoration.po | 475 ++ po/fi/kcm_kwinrules.po | 1072 ++++ po/fi/kcm_kwintabbox.po | 244 + po/fi/kcm_virtualkeyboard.po | 53 + po/fi/kcmkwincommon.po | 95 + po/fi/kcmkwincompositing.po | 940 ++++ po/fi/kcmkwinscreenedges.po | 245 + po/fi/kcmkwm.po | 1416 +++++ po/fi/kwin.po | 3045 ++++++++++ po/fi/kwin_clients.po | 137 + po/fi/kwin_effects.po | 1838 +++++++ po/fi/kwin_scripting.po | 35 + po/fi/kwin_scripts.po | 61 + po/fr/docs/kcontrol/desktop/index.docbook | 156 + .../kcontrol/kwinscreenedges/index.docbook | 80 + po/fr/docs/kcontrol/kwintabbox/index.docbook | 157 + .../kcontrol/windowspecific/index.docbook | 2203 ++++++++ po/fr/kcm_kwin_effects.po | 94 + po/fr/kcm_kwin_scripts.po | 85 + po/fr/kcm_kwin_virtualdesktops.po | 143 + po/fr/kcm_kwindecoration.po | 318 ++ po/fr/kcm_kwinrules.po | 987 ++++ po/fr/kcm_kwintabbox.po | 246 + po/fr/kcm_virtualkeyboard.po | 50 + po/fr/kcmkwincommon.po | 92 + po/fr/kcmkwincompositing.po | 391 ++ po/fr/kcmkwinscreenedges.po | 251 + po/fr/kcmkwm.po | 1452 +++++ po/fr/kwin.po | 2754 ++++++++++ po/fr/kwin_clients.po | 139 + po/fr/kwin_effects.po | 1366 +++++ po/fr/kwin_scripting.po | 41 + po/fr/kwin_scripts.po | 64 + po/fy/kcm_kwin_virtualdesktops.po | 136 + po/fy/kcm_kwindecoration.po | 301 + po/fy/kcm_kwinrules.po | 949 ++++ po/fy/kcmkwincompositing.po | 231 + po/fy/kcmkwinscreenedges.po | 249 + po/fy/kcmkwm.po | 1685 ++++++ po/fy/kwin.po | 2769 ++++++++++ po/fy/kwin_clients.po | 142 + po/fy/kwin_effects.po | 1173 ++++ po/ga/kcm_kwin_scripts.po | 75 + po/ga/kcm_kwin_virtualdesktops.po | 139 + po/ga/kcm_kwindecoration.po | 303 + po/ga/kcm_kwinrules.po | 957 ++++ po/ga/kcm_kwintabbox.po | 238 + po/ga/kcmkwincompositing.po | 237 + po/ga/kcmkwinscreenedges.po | 251 + po/ga/kcmkwm.po | 1811 ++++++ po/ga/kwin.po | 2768 ++++++++++ po/ga/kwin_clients.po | 683 +++ po/ga/kwin_effects.po | 1368 +++++ po/gl/kcm_kwin_effects.po | 93 + po/gl/kcm_kwin_scripts.po | 75 + po/gl/kcm_kwin_virtualdesktops.po | 142 + po/gl/kcm_kwindecoration.po | 288 + po/gl/kcm_kwinrules.po | 978 ++++ po/gl/kcm_kwintabbox.po | 242 + po/gl/kcmkwincommon.po | 80 + po/gl/kcmkwincompositing.po | 258 + po/gl/kcmkwinscreenedges.po | 243 + po/gl/kcmkwm.po | 1477 +++++ po/gl/kwin.po | 2812 ++++++++++ po/gl/kwin_clients.po | 132 + po/gl/kwin_effects.po | 1149 ++++ po/gl/kwin_scripting.po | 36 + po/gl/kwin_scripts.po | 61 + po/gu/kcm_kwin_virtualdesktops.po | 136 + po/gu/kcm_kwindecoration.po | 300 + po/gu/kcm_kwinrules.po | 921 ++++ po/gu/kcm_kwintabbox.po | 243 + po/gu/kcmkwincompositing.po | 232 + po/gu/kcmkwinscreenedges.po | 234 + po/gu/kcmkwm.po | 1279 +++++ po/gu/kwin.po | 2751 ++++++++++ po/gu/kwin_clients.po | 132 + po/gu/kwin_effects.po | 1170 ++++ po/he/kcm_kwin_scripts.po | 78 + po/he/kcm_kwin_virtualdesktops.po | 140 + po/he/kcm_kwindecoration.po | 314 ++ po/he/kcm_kwinrules.po | 949 ++++ po/he/kcm_kwintabbox.po | 243 + po/he/kcmkwincompositing.po | 226 + po/he/kcmkwinscreenedges.po | 241 + po/he/kcmkwm.po | 1508 +++++ po/he/kwin.po | 2786 ++++++++++ po/he/kwin_clients.po | 128 + po/he/kwin_effects.po | 1146 ++++ po/he/kwin_scripting.po | 31 + po/he/kwin_scripts.po | 58 + po/hi/kcm_kwin_virtualdesktops.po | 136 + po/hi/kcm_kwindecoration.po | 293 + po/hi/kcm_kwinrules.po | 935 ++++ po/hi/kcm_kwintabbox.po | 246 + po/hi/kcmkwincompositing.po | 225 + po/hi/kcmkwinscreenedges.po | 234 + po/hi/kcmkwm.po | 1364 +++++ po/hi/kwin.po | 2784 ++++++++++ po/hi/kwin_clients.po | 139 + po/hi/kwin_effects.po | 1183 ++++ po/hne/kcm_kwin_virtualdesktops.po | 137 + po/hne/kcm_kwindecoration.po | 294 + po/hne/kcm_kwinrules.po | 936 ++++ po/hne/kcmkwincompositing.po | 232 + po/hne/kcmkwm.po | 1358 +++++ po/hne/kwin.po | 2779 ++++++++++ po/hne/kwin_clients.po | 134 + po/hne/kwin_effects.po | 1155 ++++ po/hr/kcm_kwin_virtualdesktops.po | 142 + po/hr/kcm_kwindecoration.po | 311 ++ po/hr/kcm_kwinrules.po | 964 ++++ po/hr/kcm_kwintabbox.po | 257 + po/hr/kcmkwincompositing.po | 243 + po/hr/kcmkwinscreenedges.po | 253 + po/hr/kcmkwm.po | 1443 +++++ po/hr/kwin.po | 2774 ++++++++++ po/hr/kwin_clients.po | 141 + po/hr/kwin_effects.po | 1184 ++++ po/hsb/kcm_kwin_virtualdesktops.po | 137 + po/hsb/kcm_kwindecoration.po | 292 + po/hsb/kcm_kwinrules.po | 897 +++ po/hsb/kcmkwincompositing.po | 230 + po/hsb/kcmkwm.po | 1325 +++++ po/hsb/kwin.po | 2762 ++++++++++ po/hsb/kwin_clients.po | 124 + po/hsb/kwin_effects.po | 1140 ++++ po/hu/kcm_kwin_effects.po | 93 + po/hu/kcm_kwin_scripts.po | 77 + po/hu/kcm_kwin_virtualdesktops.po | 140 + po/hu/kcm_kwindecoration.po | 297 + po/hu/kcm_kwinrules.po | 948 ++++ po/hu/kcm_kwintabbox.po | 241 + po/hu/kcm_virtualkeyboard.po | 54 + po/hu/kcmkwincommon.po | 82 + po/hu/kcmkwincompositing.po | 243 + po/hu/kcmkwinscreenedges.po | 235 + po/hu/kcmkwm.po | 1405 +++++ po/hu/kwin.po | 2713 +++++++++ po/hu/kwin_clients.po | 129 + po/hu/kwin_effects.po | 1134 ++++ po/hu/kwin_scripting.po | 36 + po/hu/kwin_scripts.po | 62 + po/ia/kcm_kwin_effects.po | 93 + po/ia/kcm_kwin_scripts.po | 75 + po/ia/kcm_kwin_virtualdesktops.po | 139 + po/ia/kcm_kwindecoration.po | 299 + po/ia/kcm_kwinrules.po | 948 ++++ po/ia/kcm_kwintabbox.po | 239 + po/ia/kcm_virtualkeyboard.po | 53 + po/ia/kcmkwincommon.po | 81 + po/ia/kcmkwincompositing.po | 241 + po/ia/kcmkwinscreenedges.po | 241 + po/ia/kcmkwm.po | 1410 +++++ po/ia/kwin.po | 2713 +++++++++ po/ia/kwin_clients.po | 127 + po/ia/kwin_effects.po | 1117 ++++ po/ia/kwin_scripting.po | 34 + po/ia/kwin_scripts.po | 61 + po/id/docs/kcontrol/desktop/index.docbook | 170 + .../kcontrol/kwindecoration/index.docbook | 183 + po/id/docs/kcontrol/kwineffects/index.docbook | 134 + .../kcontrol/kwinscreenedges/index.docbook | 92 + po/id/docs/kcontrol/kwintabbox/index.docbook | 171 + .../kcontrol/windowbehaviour/index.docbook | 811 +++ .../kcontrol/windowspecific/index.docbook | 2214 ++++++++ po/id/kcm_kwin_effects.po | 92 + po/id/kcm_kwin_scripts.po | 75 + po/id/kcm_kwin_virtualdesktops.po | 137 + po/id/kcm_kwindecoration.po | 296 + po/id/kcm_kwinrules.po | 948 ++++ po/id/kcm_kwintabbox.po | 241 + po/id/kcm_virtualkeyboard.po | 53 + po/id/kcmkwincommon.po | 83 + po/id/kcmkwincompositing.po | 243 + po/id/kcmkwinscreenedges.po | 242 + po/id/kcmkwm.po | 1408 +++++ po/id/kwin.po | 2715 +++++++++ po/id/kwin_clients.po | 134 + po/id/kwin_effects.po | 1123 ++++ po/id/kwin_scripting.po | 33 + po/id/kwin_scripts.po | 61 + po/is/kcm_kwin_virtualdesktops.po | 139 + po/is/kcm_kwindecoration.po | 307 ++ po/is/kcm_kwinrules.po | 942 ++++ po/is/kcm_kwintabbox.po | 255 + po/is/kcmkwincompositing.po | 234 + po/is/kcmkwinscreenedges.po | 251 + po/is/kcmkwm.po | 1440 +++++ po/is/kwin.po | 2690 +++++++++ po/is/kwin_clients.po | 137 + po/is/kwin_effects.po | 1177 ++++ po/it/docs/kcontrol/desktop/index.docbook | 164 + .../kcontrol/kwindecoration/index.docbook | 206 + po/it/docs/kcontrol/kwineffects/index.docbook | 137 + .../kcontrol/kwinscreenedges/index.docbook | 106 + po/it/docs/kcontrol/kwintabbox/index.docbook | 268 + .../kcontrol/kwintouchscreen/index.docbook | 67 + .../kwinvirtualkeyboard/index.docbook | 68 + .../kcontrol/windowbehaviour/index.docbook | 1416 +++++ .../kcontrol/windowspecific/index.docbook | 2208 ++++++++ po/it/kcm_kwin_effects.po | 93 + po/it/kcm_kwin_scripts.po | 76 + po/it/kcm_kwin_virtualdesktops.po | 253 + po/it/kcm_kwindecoration.po | 415 ++ po/it/kcm_kwinrules.po | 954 ++++ po/it/kcm_kwintabbox.po | 242 + po/it/kcm_virtualkeyboard.po | 53 + po/it/kcmkwincommon.po | 85 + po/it/kcmkwincompositing.po | 246 + po/it/kcmkwinscreenedges.po | 243 + po/it/kcmkwm.po | 1423 +++++ po/it/kwin.po | 2774 ++++++++++ po/it/kwin_clients.po | 130 + po/it/kwin_effects.po | 1125 ++++ po/it/kwin_scripting.po | 37 + po/it/kwin_scripts.po | 62 + po/ja/kcm_kwin_effects.po | 93 + po/ja/kcm_kwin_scripts.po | 70 + po/ja/kcm_kwin_virtualdesktops.po | 138 + po/ja/kcm_kwindecoration.po | 301 + po/ja/kcm_kwinrules.po | 945 ++++ po/ja/kcm_kwintabbox.po | 243 + po/ja/kcm_virtualkeyboard.po | 50 + po/ja/kcmkwincommon.po | 86 + po/ja/kcmkwincompositing.po | 227 + po/ja/kcmkwinscreenedges.po | 242 + po/ja/kcmkwm.po | 1474 +++++ po/ja/kwin.po | 2785 ++++++++++ po/ja/kwin_clients.po | 132 + po/ja/kwin_effects.po | 1167 ++++ po/ja/kwin_scripting.po | 31 + po/ja/kwin_scripts.po | 58 + po/ka/kcm_kwin_effects.po | 94 + po/ka/kcm_kwin_scripts.po | 76 + po/ka/kcm_kwin_virtualdesktops.po | 138 + po/ka/kcm_kwindecoration.po | 281 + po/ka/kcm_kwinrules.po | 883 +++ po/ka/kcm_kwintabbox.po | 237 + po/ka/kcm_virtualkeyboard.po | 54 + po/ka/kcmkwincommon.po | 82 + po/ka/kcmkwincompositing.po | 228 + po/ka/kcmkwinscreenedges.po | 232 + po/ka/kcmkwm.po | 1244 +++++ po/ka/kwin.po | 2656 +++++++++ po/ka/kwin_clients.po | 124 + po/ka/kwin_effects.po | 1105 ++++ po/ka/kwin_scripting.po | 35 + po/ka/kwin_scripts.po | 62 + po/kk/kcm_kwin_scripts.po | 74 + po/kk/kcm_kwin_virtualdesktops.po | 138 + po/kk/kcm_kwindecoration.po | 306 ++ po/kk/kcm_kwinrules.po | 975 ++++ po/kk/kcm_kwintabbox.po | 238 + po/kk/kcmkwincompositing.po | 250 + po/kk/kcmkwinscreenedges.po | 246 + po/kk/kcmkwm.po | 1524 +++++ po/kk/kwin.po | 2805 ++++++++++ po/kk/kwin_clients.po | 141 + po/kk/kwin_effects.po | 1161 ++++ po/kk/kwin_scripting.po | 34 + po/km/kcm_kwin_scripts.po | 78 + po/km/kcm_kwin_virtualdesktops.po | 137 + po/km/kcm_kwindecoration.po | 300 + po/km/kcm_kwinrules.po | 1323 +++++ po/km/kcm_kwintabbox.po | 239 + po/km/kcmkwincompositing.po | 239 + po/km/kcmkwinscreenedges.po | 242 + po/km/kcmkwm.po | 1408 +++++ po/km/kwin.po | 2765 ++++++++++ po/km/kwin_clients.po | 593 ++ po/km/kwin_effects.po | 1167 ++++ po/kn/kcm_kwin_virtualdesktops.po | 138 + po/kn/kcm_kwindecoration.po | 295 + po/kn/kcm_kwinrules.po | 944 ++++ po/kn/kcm_kwintabbox.po | 253 + po/kn/kcmkwincompositing.po | 231 + po/kn/kcmkwinscreenedges.po | 241 + po/kn/kcmkwm.po | 1434 +++++ po/kn/kwin.po | 2770 ++++++++++ po/kn/kwin_clients.po | 129 + po/kn/kwin_effects.po | 1161 ++++ po/ko/kcm_kwin_effects.po | 93 + po/ko/kcm_kwin_scripts.po | 75 + po/ko/kcm_kwin_virtualdesktops.po | 134 + po/ko/kcm_kwindecoration.po | 289 + po/ko/kcm_kwinrules.po | 934 ++++ po/ko/kcm_kwintabbox.po | 235 + po/ko/kcm_virtualkeyboard.po | 53 + po/ko/kcmkwincommon.po | 82 + po/ko/kcmkwincompositing.po | 236 + po/ko/kcmkwinscreenedges.po | 231 + po/ko/kcmkwm.po | 1366 +++++ po/ko/kwin.po | 2698 +++++++++ po/ko/kwin_clients.po | 128 + po/ko/kwin_effects.po | 1118 ++++ po/ko/kwin_scripting.po | 34 + po/ko/kwin_scripts.po | 61 + po/ku/kcm_kwin_virtualdesktops.po | 137 + po/ku/kcm_kwindecoration.po | 294 + po/ku/kcm_kwinrules.po | 924 ++++ po/ku/kcmkwincompositing.po | 230 + po/ku/kcmkwm.po | 1353 +++++ po/ku/kwin.po | 2760 ++++++++++ po/ku/kwin_clients.po | 135 + po/ku/kwin_effects.po | 1140 ++++ po/lt/kcm_kwin_effects.po | 95 + po/lt/kcm_kwin_scripts.po | 78 + po/lt/kcm_kwin_virtualdesktops.po | 143 + po/lt/kcm_kwindecoration.po | 283 + po/lt/kcm_kwinrules.po | 957 ++++ po/lt/kcm_kwintabbox.po | 245 + po/lt/kcmkwincommon.po | 83 + po/lt/kcmkwincompositing.po | 253 + po/lt/kcmkwinscreenedges.po | 241 + po/lt/kcmkwm.po | 1415 +++++ po/lt/kwin.po | 2759 ++++++++++ po/lt/kwin_clients.po | 130 + po/lt/kwin_effects.po | 1156 ++++ po/lt/kwin_scripting.po | 38 + po/lt/kwin_scripts.po | 63 + po/lv/kcm_kwin_virtualdesktops.po | 143 + po/lv/kcm_kwindecoration.po | 304 + po/lv/kcm_kwinrules.po | 985 ++++ po/lv/kcm_kwintabbox.po | 254 + po/lv/kcmkwincompositing.po | 243 + po/lv/kcmkwinscreenedges.po | 244 + po/lv/kcmkwm.po | 1435 +++++ po/lv/kwin.po | 2780 ++++++++++ po/lv/kwin_clients.po | 131 + po/lv/kwin_effects.po | 1166 ++++ po/mai/kcm_kwin_virtualdesktops.po | 137 + po/mai/kcm_kwindecoration.po | 297 + po/mai/kcm_kwinrules.po | 939 ++++ po/mai/kcm_kwintabbox.po | 241 + po/mai/kcmkwincompositing.po | 233 + po/mai/kcmkwinscreenedges.po | 237 + po/mai/kcmkwm.po | 1350 +++++ po/mai/kwin.po | 2775 ++++++++++ po/mai/kwin_clients.po | 143 + po/mai/kwin_effects.po | 1165 ++++ po/mk/kcm_kwin_virtualdesktops.po | 140 + po/mk/kcm_kwindecoration.po | 294 + po/mk/kcm_kwinrules.po | 949 ++++ po/mk/kcmkwincompositing.po | 233 + po/mk/kcmkwm.po | 1432 +++++ po/mk/kwin.po | 2773 ++++++++++ po/mk/kwin_clients.po | 136 + po/mk/kwin_effects.po | 1171 ++++ po/ml/kcm_kwin_effects.po | 90 + po/ml/kcm_kwin_scripts.po | 74 + po/ml/kcm_kwin_virtualdesktops.po | 134 + po/ml/kcm_kwindecoration.po | 279 + po/ml/kcm_kwinrules.po | 913 +++ po/ml/kcm_kwintabbox.po | 234 + po/ml/kcmkwincommon.po | 82 + po/ml/kcmkwincompositing.po | 219 + po/ml/kcmkwinscreenedges.po | 237 + po/ml/kcmkwm.po | 1299 +++++ po/ml/kwin.po | 2686 +++++++++ po/ml/kwin_clients.po | 128 + po/ml/kwin_effects.po | 1111 ++++ po/ml/kwin_scripting.po | 35 + po/ml/kwin_scripts.po | 62 + po/mr/kcm_kwin_scripts.po | 79 + po/mr/kcm_kwin_virtualdesktops.po | 138 + po/mr/kcm_kwindecoration.po | 308 ++ po/mr/kcm_kwinrules.po | 945 ++++ po/mr/kcm_kwintabbox.po | 238 + po/mr/kcmkwincompositing.po | 241 + po/mr/kcmkwinscreenedges.po | 249 + po/mr/kcmkwm.po | 1329 +++++ po/mr/kwin.po | 2752 ++++++++++ po/mr/kwin_clients.po | 132 + po/mr/kwin_effects.po | 1159 ++++ po/mr/kwin_scripting.po | 34 + po/ms/kcm_kwin_virtualdesktops.po | 137 + po/ms/kcm_kwindecoration.po | 300 + po/ms/kcm_kwinrules.po | 945 ++++ po/ms/kcm_kwintabbox.po | 245 + po/ms/kcmkwincompositing.po | 228 + po/ms/kcmkwinscreenedges.po | 235 + po/ms/kcmkwm.po | 1428 +++++ po/ms/kwin.po | 2773 ++++++++++ po/ms/kwin_clients.po | 132 + po/ms/kwin_effects.po | 1120 ++++ po/nb/kcm_kwin_scripts.po | 75 + po/nb/kcm_kwin_virtualdesktops.po | 135 + po/nb/kcm_kwindecoration.po | 281 + po/nb/kcm_kwinrules.po | 924 ++++ po/nb/kcm_kwintabbox.po | 240 + po/nb/kcmkwincommon.po | 83 + po/nb/kcmkwincompositing.po | 229 + po/nb/kcmkwinscreenedges.po | 236 + po/nb/kcmkwm.po | 1307 +++++ po/nb/kwin.po | 2687 +++++++++ po/nb/kwin_clients.po | 130 + po/nb/kwin_effects.po | 1107 ++++ po/nb/kwin_scripting.po | 36 + po/nds/kcm_kwin_scripts.po | 77 + po/nds/kcm_kwin_virtualdesktops.po | 138 + po/nds/kcm_kwindecoration.po | 306 ++ po/nds/kcm_kwinrules.po | 978 ++++ po/nds/kcm_kwintabbox.po | 244 + po/nds/kcmkwincompositing.po | 251 + po/nds/kcmkwinscreenedges.po | 245 + po/nds/kcmkwm.po | 1519 +++++ po/nds/kwin.po | 2808 ++++++++++ po/nds/kwin_clients.po | 133 + po/nds/kwin_effects.po | 1177 ++++ po/nds/kwin_scripting.po | 34 + po/ne/kcm_kwin_virtualdesktops.po | 135 + po/ne/kcm_kwindecoration.po | 293 + po/ne/kcm_kwinrules.po | 945 ++++ po/ne/kcmkwincompositing.po | 226 + po/ne/kcmkwm.po | 1430 +++++ po/ne/kwin.po | 2788 ++++++++++ po/ne/kwin_clients.po | 136 + po/nl/docs/kcontrol/desktop/index.docbook | 142 + .../kcontrol/kwindecoration/index.docbook | 174 + po/nl/docs/kcontrol/kwineffects/index.docbook | 123 + .../kcontrol/kwinscreenedges/index.docbook | 78 + po/nl/docs/kcontrol/kwintabbox/index.docbook | 244 + .../kcontrol/kwintouchscreen/index.docbook | 53 + .../kwinvirtualkeyboard/index.docbook | 54 + .../kcontrol/windowbehaviour/index.docbook | 1394 +++++ .../kcontrol/windowspecific/index.docbook | 2198 ++++++++ po/nl/kcm_kwin_effects.po | 93 + po/nl/kcm_kwin_scripts.po | 76 + po/nl/kcm_kwin_virtualdesktops.po | 139 + po/nl/kcm_kwindecoration.po | 307 ++ po/nl/kcm_kwinrules.po | 961 ++++ po/nl/kcm_kwintabbox.po | 240 + po/nl/kcm_virtualkeyboard.po | 53 + po/nl/kcmkwincommon.po | 81 + po/nl/kcmkwincompositing.po | 248 + po/nl/kcmkwinscreenedges.po | 245 + po/nl/kcmkwm.po | 1427 +++++ po/nl/kwin.po | 2723 +++++++++ po/nl/kwin_clients.po | 131 + po/nl/kwin_effects.po | 1123 ++++ po/nl/kwin_scripting.po | 34 + po/nl/kwin_scripts.po | 61 + po/nn/kcm_kwin_effects.po | 95 + po/nn/kcm_kwin_scripts.po | 77 + po/nn/kcm_kwin_virtualdesktops.po | 140 + po/nn/kcm_kwindecoration.po | 294 + po/nn/kcm_kwinrules.po | 950 ++++ po/nn/kcm_kwintabbox.po | 242 + po/nn/kcm_virtualkeyboard.po | 55 + po/nn/kcmkwincommon.po | 86 + po/nn/kcmkwincompositing.po | 245 + po/nn/kcmkwinscreenedges.po | 238 + po/nn/kcmkwm.po | 1394 +++++ po/nn/kwin.po | 2714 +++++++++ po/nn/kwin_clients.po | 131 + po/nn/kwin_effects.po | 1121 ++++ po/nn/kwin_scripting.po | 36 + po/nn/kwin_scripts.po | 63 + po/oc/kcm_kwin_virtualdesktops.po | 136 + po/oc/kcm_kwindecoration.po | 290 + po/oc/kcm_kwinrules.po | 895 +++ po/oc/kcmkwincompositing.po | 222 + po/oc/kcmkwm.po | 1271 +++++ po/oc/kwin.po | 2696 +++++++++ po/oc/kwin_clients.po | 132 + po/oc/kwin_effects.po | 1115 ++++ po/pa/kcm_kwin_effects.po | 91 + po/pa/kcm_kwin_scripts.po | 75 + po/pa/kcm_kwin_virtualdesktops.po | 134 + po/pa/kcm_kwindecoration.po | 303 + po/pa/kcm_kwinrules.po | 940 ++++ po/pa/kcm_kwintabbox.po | 235 + po/pa/kcmkwincommon.po | 81 + po/pa/kcmkwincompositing.po | 239 + po/pa/kcmkwinscreenedges.po | 235 + po/pa/kcmkwm.po | 1326 +++++ po/pa/kwin.po | 2724 +++++++++ po/pa/kwin_clients.po | 132 + po/pa/kwin_effects.po | 1158 ++++ po/pa/kwin_scripting.po | 34 + po/pa/kwin_scripts.po | 61 + po/pl/kcm_kwin_effects.po | 92 + po/pl/kcm_kwin_scripts.po | 76 + po/pl/kcm_kwin_virtualdesktops.po | 143 + po/pl/kcm_kwindecoration.po | 300 + po/pl/kcm_kwinrules.po | 950 ++++ po/pl/kcm_kwintabbox.po | 242 + po/pl/kcm_virtualkeyboard.po | 54 + po/pl/kcmkwincommon.po | 82 + po/pl/kcmkwincompositing.po | 245 + po/pl/kcmkwinscreenedges.po | 239 + po/pl/kcmkwm.po | 1421 +++++ po/pl/kwin.po | 2726 +++++++++ po/pl/kwin_clients.po | 130 + po/pl/kwin_effects.po | 1126 ++++ po/pl/kwin_scripting.po | 35 + po/pl/kwin_scripts.po | 62 + po/pt/docs/kcontrol/desktop/index.docbook | 170 + .../kcontrol/kwindecoration/index.docbook | 183 + po/pt/docs/kcontrol/kwineffects/index.docbook | 134 + .../kcontrol/kwinscreenedges/index.docbook | 94 + po/pt/docs/kcontrol/kwintabbox/index.docbook | 171 + .../kcontrol/windowbehaviour/index.docbook | 837 +++ .../kcontrol/windowspecific/index.docbook | 2227 ++++++++ po/pt/kcm_kwin_effects.po | 94 + po/pt/kcm_kwin_scripts.po | 71 + po/pt/kcm_kwin_virtualdesktops.po | 133 + po/pt/kcm_kwindecoration.po | 295 + po/pt/kcm_kwinrules.po | 949 ++++ po/pt/kcm_kwintabbox.po | 235 + po/pt/kcm_virtualkeyboard.po | 53 + po/pt/kcmkwincommon.po | 77 + po/pt/kcmkwincompositing.po | 240 + po/pt/kcmkwinscreenedges.po | 235 + po/pt/kcmkwm.po | 1424 +++++ po/pt/kwin.po | 2728 +++++++++ po/pt/kwin_clients.po | 129 + po/pt/kwin_effects.po | 1122 ++++ po/pt/kwin_scripting.po | 36 + po/pt/kwin_scripts.po | 57 + po/pt_BR/docs/kcontrol/desktop/index.docbook | 170 + .../kcontrol/kwindecoration/configure.png | Bin 0 -> 582 bytes .../kcontrol/kwindecoration/index.docbook | 183 + .../kcontrol/kwinscreenedges/index.docbook | 94 + .../docs/kcontrol/kwintabbox/index.docbook | 171 + .../kcontrol/windowbehaviour/index.docbook | 837 +++ .../kcontrol/windowspecific/index.docbook | 2225 ++++++++ po/pt_BR/kcm_kwin_effects.po | 95 + po/pt_BR/kcm_kwin_scripts.po | 77 + po/pt_BR/kcm_kwin_virtualdesktops.po | 140 + po/pt_BR/kcm_kwindecoration.po | 306 ++ po/pt_BR/kcm_kwinrules.po | 1158 ++++ po/pt_BR/kcm_kwintabbox.po | 243 + po/pt_BR/kcm_virtualkeyboard.po | 53 + po/pt_BR/kcmkwincommon.po | 82 + po/pt_BR/kcmkwincompositing.po | 244 + po/pt_BR/kcmkwinscreenedges.po | 242 + po/pt_BR/kcmkwm.po | 1418 +++++ po/pt_BR/kwin.po | 2728 +++++++++ po/pt_BR/kwin_clients.po | 135 + po/pt_BR/kwin_effects.po | 1627 ++++++ po/pt_BR/kwin_scripting.po | 36 + po/pt_BR/kwin_scripts.po | 62 + po/ro/kcm_kwin_effects.po | 94 + po/ro/kcm_kwin_scripts.po | 77 + po/ro/kcm_kwin_virtualdesktops.po | 144 + po/ro/kcm_kwindecoration.po | 288 + po/ro/kcm_kwinrules.po | 954 ++++ po/ro/kcm_kwintabbox.po | 241 + po/ro/kcm_virtualkeyboard.po | 54 + po/ro/kcmkwincommon.po | 82 + po/ro/kcmkwincompositing.po | 246 + po/ro/kcmkwinscreenedges.po | 243 + po/ro/kcmkwm.po | 1373 +++++ po/ro/kwin.po | 2712 +++++++++ po/ro/kwin_clients.po | 134 + po/ro/kwin_effects.po | 1131 ++++ po/ro/kwin_scripting.po | 35 + po/ro/kwin_scripts.po | 62 + po/ru/docs/kcontrol/desktop/index.docbook | 162 + po/ru/docs/kcontrol/kwindecoration/button.png | Bin 0 -> 34437 bytes .../kcontrol/kwindecoration/configure.png | Bin 0 -> 483 bytes .../kcontrol/kwindecoration/decoration.png | Bin 0 -> 29903 bytes .../kcontrol/kwindecoration/index.docbook | 204 + po/ru/docs/kcontrol/kwindecoration/main.png | Bin 0 -> 53065 bytes po/ru/docs/kcontrol/kwineffects/index.docbook | 137 + .../kcontrol/kwinscreenedges/index.docbook | 92 + po/ru/docs/kcontrol/kwintabbox/index.docbook | 264 + .../kcontrol/kwintouchscreen/index.docbook | 81 + .../kwinvirtualkeyboard/index.docbook | 68 + .../kcontrol/windowbehaviour/index.docbook | 1430 +++++ .../windowspecific/akgregator-info.png | Bin 0 -> 807311 bytes .../windowspecific/akregator-attributes.png | Bin 0 -> 78261 bytes .../kcontrol/windowspecific/akregator-fav.png | Bin 0 -> 54037 bytes .../windowspecific/config-win-behavior.png | Bin 0 -> 51629 bytes .../windowspecific/emacs-attribute.png | Bin 0 -> 77149 bytes .../kcontrol/windowspecific/emacs-info.png | Bin 0 -> 835866 bytes .../focus-stealing-pop2top-attribute.png | Bin 0 -> 64469 bytes .../kcontrol/windowspecific/index.docbook | 2242 ++++++++ .../windowspecific/knotes-attribute.png | Bin 0 -> 42443 bytes .../kcontrol/windowspecific/knotes-info.png | Bin 0 -> 34092 bytes .../windowspecific/kopete-attribute-2.png | Bin 0 -> 42618 bytes .../windowspecific/kopete-chat-attribute.png | Bin 0 -> 42460 bytes .../windowspecific/kopete-chat-info.png | Bin 0 -> 33630 bytes .../kcontrol/windowspecific/kopete-info.png | Bin 0 -> 33230 bytes .../windowspecific/kwin-detect-window.png | Bin 0 -> 34194 bytes .../windowspecific/kwin-kopete-rules.png | Bin 0 -> 34830 bytes .../windowspecific/kwin-rule-editor.png | Bin 0 -> 46851 bytes .../kwin-rules-main-n-akregator.png | Bin 0 -> 33970 bytes .../windowspecific/kwin-rules-main.png | Bin 0 -> 35382 bytes .../windowspecific/kwin-rules-ordering.png | Bin 0 -> 35771 bytes .../windowspecific/kwin-window-attributes.png | Bin 0 -> 79639 bytes .../windowspecific/kwin-window-matching.png | Bin 0 -> 46851 bytes .../tbird-compose-attribute.png | Bin 0 -> 64438 bytes .../windowspecific/tbird-compose-info.png | Bin 0 -> 828004 bytes .../windowspecific/tbird-main-attribute.png | Bin 0 -> 77587 bytes .../windowspecific/tbird-main-info.png | Bin 0 -> 826785 bytes .../tbird-reminder-attribute-2.png | Bin 0 -> 43435 bytes .../windowspecific/tbird-reminder-info.png | Bin 0 -> 816667 bytes .../windowspecific/window-matching-emacs.png | Bin 0 -> 51975 bytes .../windowspecific/window-matching-init.png | Bin 0 -> 46859 bytes .../windowspecific/window-matching-knotes.png | Bin 0 -> 47837 bytes .../window-matching-kopete-chat.png | Bin 0 -> 49628 bytes .../windowspecific/window-matching-kopete.png | Bin 0 -> 47908 bytes .../window-matching-ready-akregator.png | Bin 0 -> 48337 bytes .../window-matching-tbird-compose.png | Bin 0 -> 50376 bytes .../window-matching-tbird-main.png | Bin 0 -> 49064 bytes .../window-matching-tbird-reminder.png | Bin 0 -> 48901 bytes po/ru/kcm_kwin_effects.po | 93 + po/ru/kcm_kwin_scripts.po | 83 + po/ru/kcm_kwin_virtualdesktops.po | 149 + po/ru/kcm_kwindecoration.po | 306 ++ po/ru/kcm_kwinrules.po | 1159 ++++ po/ru/kcm_kwintabbox.po | 245 + po/ru/kcm_virtualkeyboard.po | 54 + po/ru/kcmkwincommon.po | 83 + po/ru/kcmkwincompositing.po | 596 ++ po/ru/kcmkwinscreenedges.po | 248 + po/ru/kcmkwm.po | 1426 +++++ po/ru/kwin.po | 2973 ++++++++++ po/ru/kwin_clients.po | 137 + po/ru/kwin_effects.po | 1141 ++++ po/ru/kwin_scripting.po | 36 + po/ru/kwin_scripts.po | 62 + po/se/kcm_kwin_virtualdesktops.po | 135 + po/se/kcm_kwindecoration.po | 279 + po/se/kcm_kwinrules.po | 884 +++ po/se/kcmkwincommon.po | 83 + po/se/kcmkwincompositing.po | 223 + po/se/kcmkwm.po | 1271 +++++ po/se/kwin.po | 2671 +++++++++ po/se/kwin_clients.po | 125 + po/si/kcm_kwin_virtualdesktops.po | 135 + po/si/kcm_kwindecoration.po | 292 + po/si/kcm_kwinrules.po | 941 ++++ po/si/kcm_kwintabbox.po | 250 + po/si/kcmkwincompositing.po | 237 + po/si/kcmkwinscreenedges.po | 241 + po/si/kcmkwm.po | 1423 +++++ po/si/kwin.po | 2770 ++++++++++ po/si/kwin_clients.po | 134 + po/si/kwin_effects.po | 1170 ++++ po/sk/kcm_kwin_effects.po | 90 + po/sk/kcm_kwin_scripts.po | 76 + po/sk/kcm_kwin_virtualdesktops.po | 139 + po/sk/kcm_kwindecoration.po | 297 + po/sk/kcm_kwinrules.po | 947 ++++ po/sk/kcm_kwintabbox.po | 239 + po/sk/kcm_virtualkeyboard.po | 54 + po/sk/kcmkwincommon.po | 80 + po/sk/kcmkwincompositing.po | 241 + po/sk/kcmkwinscreenedges.po | 236 + po/sk/kcmkwm.po | 1390 +++++ po/sk/kwin.po | 2709 +++++++++ po/sk/kwin_clients.po | 129 + po/sk/kwin_effects.po | 1121 ++++ po/sk/kwin_scripting.po | 32 + po/sk/kwin_scripts.po | 59 + po/sl/kcm_kwin_effects.po | 94 + po/sl/kcm_kwin_scripts.po | 82 + po/sl/kcm_kwin_virtualdesktops.po | 145 + po/sl/kcm_kwindecoration.po | 305 + po/sl/kcm_kwinrules.po | 947 ++++ po/sl/kcm_kwintabbox.po | 241 + po/sl/kcm_virtualkeyboard.po | 54 + po/sl/kcmkwincommon.po | 82 + po/sl/kcmkwincompositing.po | 244 + po/sl/kcmkwinscreenedges.po | 244 + po/sl/kcmkwm.po | 1417 +++++ po/sl/kwin.po | 2715 +++++++++ po/sl/kwin_clients.po | 132 + po/sl/kwin_effects.po | 1125 ++++ po/sl/kwin_scripting.po | 35 + po/sl/kwin_scripts.po | 62 + po/sq/kcm_kwin_virtualdesktops.po | 137 + po/sq/kcm_kwindecoration.po | 284 + po/sq/kcm_kwinrules.po | 906 +++ po/sq/kcmkwincompositing.po | 229 + po/sq/kcmkwinscreenedges.po | 235 + po/sq/kcmkwm.po | 1278 +++++ po/sq/kwin.po | 2713 +++++++++ po/sq/kwin_clients.po | 127 + po/sq/kwin_effects.po | 1149 ++++ po/sr/docs/kcontrol/desktop/index.docbook | 182 + po/sr/kcm_kwin_scripts.po | 89 + po/sr/kcm_kwin_virtualdesktops.po | 134 + po/sr/kcm_kwindecoration.po | 246 + po/sr/kcm_kwinrules.po | 925 ++++ po/sr/kcm_kwintabbox.po | 245 + po/sr/kcmkwincompositing.po | 243 + po/sr/kcmkwinscreenedges.po | 255 + po/sr/kcmkwm.po | 1464 +++++ po/sr/kwin.po | 2754 ++++++++++ po/sr/kwin_clients.po | 138 + po/sr/kwin_effects.po | 2245 ++++++++ po/sr/kwin_scripting.po | 111 + po/sr/kwin_scripts.po | 64 + po/sr@ijekavian/kcm_kwin_scripts.po | 89 + po/sr@ijekavian/kcm_kwin_virtualdesktops.po | 134 + po/sr@ijekavian/kcm_kwindecoration.po | 246 + po/sr@ijekavian/kcm_kwinrules.po | 925 ++++ po/sr@ijekavian/kcm_kwintabbox.po | 245 + po/sr@ijekavian/kcmkwincompositing.po | 243 + po/sr@ijekavian/kcmkwinscreenedges.po | 255 + po/sr@ijekavian/kcmkwm.po | 1464 +++++ po/sr@ijekavian/kwin.po | 2754 ++++++++++ po/sr@ijekavian/kwin_clients.po | 138 + po/sr@ijekavian/kwin_effects.po | 2245 ++++++++ po/sr@ijekavian/kwin_scripting.po | 111 + po/sr@ijekavian/kwin_scripts.po | 64 + po/sr@ijekavianlatin/kcm_kwin_scripts.po | 89 + .../kcm_kwin_virtualdesktops.po | 134 + po/sr@ijekavianlatin/kcm_kwindecoration.po | 246 + po/sr@ijekavianlatin/kcm_kwinrules.po | 925 ++++ po/sr@ijekavianlatin/kcm_kwintabbox.po | 245 + po/sr@ijekavianlatin/kcmkwincompositing.po | 243 + po/sr@ijekavianlatin/kcmkwinscreenedges.po | 255 + po/sr@ijekavianlatin/kcmkwm.po | 1466 +++++ po/sr@ijekavianlatin/kwin.po | 2755 ++++++++++ po/sr@ijekavianlatin/kwin_clients.po | 138 + po/sr@ijekavianlatin/kwin_effects.po | 2246 ++++++++ po/sr@ijekavianlatin/kwin_scripting.po | 111 + po/sr@ijekavianlatin/kwin_scripts.po | 64 + .../docs/kcontrol/desktop/index.docbook | 182 + po/sr@latin/kcm_kwin_scripts.po | 89 + po/sr@latin/kcm_kwin_virtualdesktops.po | 134 + po/sr@latin/kcm_kwindecoration.po | 246 + po/sr@latin/kcm_kwinrules.po | 925 ++++ po/sr@latin/kcm_kwintabbox.po | 245 + po/sr@latin/kcmkwincompositing.po | 243 + po/sr@latin/kcmkwinscreenedges.po | 255 + po/sr@latin/kcmkwm.po | 1466 +++++ po/sr@latin/kwin.po | 2755 ++++++++++ po/sr@latin/kwin_clients.po | 138 + po/sr@latin/kwin_effects.po | 2245 ++++++++ po/sr@latin/kwin_scripting.po | 111 + po/sr@latin/kwin_scripts.po | 64 + po/sv/docs/kcontrol/desktop/index.docbook | 142 + .../kcontrol/kwindecoration/index.docbook | 190 + po/sv/docs/kcontrol/kwineffects/index.docbook | 137 + .../kcontrol/kwinscreenedges/index.docbook | 92 + po/sv/docs/kcontrol/kwintabbox/index.docbook | 258 + .../kcontrol/kwintouchscreen/index.docbook | 67 + .../kwinvirtualkeyboard/index.docbook | 68 + .../kcontrol/windowbehaviour/index.docbook | 1412 +++++ .../kcontrol/windowspecific/index.docbook | 2244 ++++++++ po/sv/kcm_kwin_effects.po | 93 + po/sv/kcm_kwin_scripts.po | 75 + po/sv/kcm_kwin_virtualdesktops.po | 138 + po/sv/kcm_kwindecoration.po | 295 + po/sv/kcm_kwinrules.po | 943 ++++ po/sv/kcm_kwintabbox.po | 236 + po/sv/kcm_virtualkeyboard.po | 53 + po/sv/kcmkwincommon.po | 81 + po/sv/kcmkwincompositing.po | 241 + po/sv/kcmkwinscreenedges.po | 238 + po/sv/kcmkwm.po | 1408 +++++ po/sv/kwin.po | 2706 +++++++++ po/sv/kwin_clients.po | 128 + po/sv/kwin_effects.po | 1119 ++++ po/sv/kwin_scripting.po | 35 + po/sv/kwin_scripts.po | 61 + po/ta/kcm_kwin_effects.po | 93 + po/ta/kcm_kwin_scripts.po | 75 + po/ta/kcm_kwin_virtualdesktops.po | 139 + po/ta/kcm_kwindecoration.po | 280 + po/ta/kcm_kwinrules.po | 939 ++++ po/ta/kcm_kwintabbox.po | 235 + po/ta/kcm_virtualkeyboard.po | 53 + po/ta/kcmkwincommon.po | 81 + po/ta/kcmkwincompositing.po | 225 + po/ta/kcmkwinscreenedges.po | 234 + po/ta/kcmkwm.po | 1362 +++++ po/ta/kwin.po | 2713 +++++++++ po/ta/kwin_clients.po | 124 + po/ta/kwin_effects.po | 1121 ++++ po/ta/kwin_scripting.po | 34 + po/ta/kwin_scripts.po | 61 + po/te/kcm_kwin_virtualdesktops.po | 136 + po/te/kcm_kwindecoration.po | 293 + po/te/kcm_kwinrules.po | 935 ++++ po/te/kcmkwincompositing.po | 242 + po/te/kcmkwm.po | 1353 +++++ po/te/kwin.po | 2706 +++++++++ po/te/kwin_clients.po | 129 + po/te/kwin_effects.po | 1123 ++++ po/tg/kcm_kwin_virtualdesktops.po | 133 + po/tg/kcm_kwindecoration.po | 275 + po/tg/kcm_kwinrules.po | 898 +++ po/tg/kcmkwincommon.po | 81 + po/tg/kcmkwincompositing.po | 223 + po/tg/kcmkwinscreenedges.po | 231 + po/tg/kcmkwm.po | 1244 +++++ po/tg/kwin.po | 2686 +++++++++ po/tg/kwin_clients.po | 123 + po/tg/kwin_effects.po | 1110 ++++ po/th/kcm_kwin_virtualdesktops.po | 137 + po/th/kcm_kwindecoration.po | 301 + po/th/kcm_kwinrules.po | 944 ++++ po/th/kcm_kwintabbox.po | 251 + po/th/kcmkwincompositing.po | 233 + po/th/kcmkwinscreenedges.po | 244 + po/th/kcmkwm.po | 1419 +++++ po/th/kwin.po | 2775 ++++++++++ po/th/kwin_clients.po | 140 + po/th/kwin_effects.po | 1165 ++++ po/tr/kcm_kwin_effects.po | 93 + po/tr/kcm_kwin_scripts.po | 77 + po/tr/kcm_kwin_virtualdesktops.po | 142 + po/tr/kcm_kwindecoration.po | 295 + po/tr/kcm_kwinrules.po | 947 ++++ po/tr/kcm_kwintabbox.po | 243 + po/tr/kcm_virtualkeyboard.po | 53 + po/tr/kcmkwincommon.po | 81 + po/tr/kcmkwincompositing.po | 244 + po/tr/kcmkwinscreenedges.po | 242 + po/tr/kcmkwm.po | 1417 +++++ po/tr/kwin.po | 2712 +++++++++ po/tr/kwin_clients.po | 135 + po/tr/kwin_effects.po | 1127 ++++ po/tr/kwin_scripting.po | 37 + po/tr/kwin_scripts.po | 62 + po/ug/kcm_kwin_scripts.po | 73 + po/ug/kcm_kwin_virtualdesktops.po | 134 + po/ug/kcm_kwindecoration.po | 303 + po/ug/kcm_kwinrules.po | 935 ++++ po/ug/kcm_kwintabbox.po | 234 + po/ug/kcmkwincompositing.po | 229 + po/ug/kcmkwinscreenedges.po | 233 + po/ug/kcmkwm.po | 1315 +++++ po/ug/kwin.po | 2746 +++++++++ po/ug/kwin_clients.po | 134 + po/ug/kwin_effects.po | 1160 ++++ po/ug/kwin_scripting.po | 34 + po/uk/docs/kcontrol/desktop/index.docbook | 142 + po/uk/docs/kcontrol/kwindecoration/button.png | Bin 0 -> 29178 bytes .../kcontrol/kwindecoration/decoration.png | Bin 0 -> 22538 bytes .../kcontrol/kwindecoration/index.docbook | 188 + po/uk/docs/kcontrol/kwindecoration/main.png | Bin 0 -> 38464 bytes po/uk/docs/kcontrol/kwineffects/index.docbook | 137 + .../kcontrol/kwinscreenedges/index.docbook | 92 + po/uk/docs/kcontrol/kwintabbox/index.docbook | 258 + .../kcontrol/kwintouchscreen/index.docbook | 67 + .../kwinvirtualkeyboard/index.docbook | 68 + .../kcontrol/windowbehaviour/index.docbook | 1406 +++++ .../kcontrol/windowspecific/index.docbook | 2228 ++++++++ po/uk/kcm_kwin_effects.po | 96 + po/uk/kcm_kwin_scripts.po | 78 + po/uk/kcm_kwin_virtualdesktops.po | 144 + po/uk/kcm_kwindecoration.po | 297 + po/uk/kcm_kwinrules.po | 962 ++++ po/uk/kcm_kwintabbox.po | 240 + po/uk/kcm_virtualkeyboard.po | 54 + po/uk/kcmkwincommon.po | 84 + po/uk/kcmkwincompositing.po | 247 + po/uk/kcmkwinscreenedges.po | 239 + po/uk/kcmkwm.po | 1418 +++++ po/uk/kwin.po | 2722 +++++++++ po/uk/kwin_clients.po | 133 + po/uk/kwin_effects.po | 1125 ++++ po/uk/kwin_scripting.po | 37 + po/uk/kwin_scripts.po | 64 + po/uz/kcm_kwindecoration.po | 292 + po/uz/kcm_kwinrules.po | 918 ++++ po/uz/kcmkwm.po | 1348 +++++ po/uz/kwin.po | 2762 ++++++++++ po/uz/kwin_clients.po | 130 + po/uz@cyrillic/kcm_kwindecoration.po | 292 + po/uz@cyrillic/kcm_kwinrules.po | 918 ++++ po/uz@cyrillic/kcmkwm.po | 1348 +++++ po/uz@cyrillic/kwin.po | 2762 ++++++++++ po/uz@cyrillic/kwin_clients.po | 130 + po/vi/kcm_kwin_effects.po | 93 + po/vi/kcm_kwin_scripts.po | 75 + po/vi/kcm_kwin_virtualdesktops.po | 136 + po/vi/kcm_kwindecoration.po | 293 + po/vi/kcm_kwinrules.po | 940 ++++ po/vi/kcm_kwintabbox.po | 239 + po/vi/kcm_virtualkeyboard.po | 53 + po/vi/kcmkwincommon.po | 81 + po/vi/kcmkwincompositing.po | 238 + po/vi/kcmkwinscreenedges.po | 239 + po/vi/kcmkwm.po | 1406 +++++ po/vi/kwin.po | 2703 +++++++++ po/vi/kwin_clients.po | 128 + po/vi/kwin_effects.po | 1114 ++++ po/vi/kwin_scripting.po | 34 + po/vi/kwin_scripts.po | 61 + po/wa/kcm_kwin_virtualdesktops.po | 136 + po/wa/kcm_kwindecoration.po | 302 + po/wa/kcm_kwinrules.po | 942 ++++ po/wa/kcm_kwintabbox.po | 252 + po/wa/kcmkwincompositing.po | 230 + po/wa/kcmkwinscreenedges.po | 246 + po/wa/kcmkwm.po | 1411 +++++ po/wa/kwin.po | 2762 ++++++++++ po/wa/kwin_clients.po | 137 + po/wa/kwin_effects.po | 1164 ++++ po/xh/kcm_kwindecoration.po | 281 + po/xh/kcmkwm.po | 1368 +++++ po/xh/kwin.po | 2709 +++++++++ po/zh_CN/kcm_kwin_effects.po | 94 + po/zh_CN/kcm_kwin_scripts.po | 75 + po/zh_CN/kcm_kwin_virtualdesktops.po | 132 + po/zh_CN/kcm_kwindecoration.po | 284 + po/zh_CN/kcm_kwinrules.po | 926 ++++ po/zh_CN/kcm_kwintabbox.po | 233 + po/zh_CN/kcm_virtualkeyboard.po | 53 + po/zh_CN/kcmkwincommon.po | 81 + po/zh_CN/kcmkwincompositing.po | 232 + po/zh_CN/kcmkwinscreenedges.po | 233 + po/zh_CN/kcmkwm.po | 1342 +++++ po/zh_CN/kwin.po | 2685 +++++++++ po/zh_CN/kwin_clients.po | 123 + po/zh_CN/kwin_effects.po | 1113 ++++ po/zh_CN/kwin_scripting.po | 34 + po/zh_CN/kwin_scripts.po | 61 + po/zh_TW/kcm_kwin_effects.po | 93 + po/zh_TW/kcm_kwin_scripts.po | 77 + po/zh_TW/kcm_kwin_virtualdesktops.po | 138 + po/zh_TW/kcm_kwindecoration.po | 290 + po/zh_TW/kcm_kwinrules.po | 926 ++++ po/zh_TW/kcm_kwintabbox.po | 238 + po/zh_TW/kcm_virtualkeyboard.po | 55 + po/zh_TW/kcmkwincommon.po | 81 + po/zh_TW/kcmkwincompositing.po | 237 + po/zh_TW/kcmkwinscreenedges.po | 236 + po/zh_TW/kcmkwm.po | 1347 +++++ po/zh_TW/kwin.po | 2696 +++++++++ po/zh_TW/kwin_clients.po | 130 + po/zh_TW/kwin_effects.po | 1127 ++++ po/zh_TW/kwin_scripting.po | 35 + po/zh_TW/kwin_scripts.po | 61 + src/3rdparty/colortemperature.h | 267 + src/3rdparty/xcursor.c | 594 ++ src/3rdparty/xcursor.h | 70 + src/CMakeLists.txt | 360 ++ src/Messages.sh | 3 + src/activation.cpp | 943 ++++ src/activities.cpp | 219 + src/activities.h | 113 + src/appmenu.cpp | 113 + src/appmenu.h | 62 + src/atoms.cpp | 91 + src/atoms.h | 97 + src/backends/CMakeLists.txt | 6 + src/backends/drm/CMakeLists.txt | 32 + src/backends/drm/drm_abstract_output.cpp | 67 + src/backends/drm/drm_abstract_output.h | 44 + src/backends/drm/drm_backend.cpp | 529 ++ src/backends/drm/drm_backend.h | 115 + src/backends/drm/drm_buffer.cpp | 146 + src/backends/drm/drm_buffer.h | 78 + src/backends/drm/drm_buffer_gbm.cpp | 214 + src/backends/drm/drm_buffer_gbm.h | 59 + src/backends/drm/drm_dmabuf_feedback.cpp | 89 + src/backends/drm/drm_dmabuf_feedback.h | 45 + src/backends/drm/drm_dumb_buffer.cpp | 80 + src/backends/drm/drm_dumb_buffer.h | 34 + src/backends/drm/drm_dumb_swapchain.cpp | 92 + src/backends/drm/drm_dumb_swapchain.h | 53 + src/backends/drm/drm_egl_backend.cpp | 280 + src/backends/drm/drm_egl_backend.h | 98 + src/backends/drm/drm_egl_cursor_layer.cpp | 72 + src/backends/drm/drm_egl_cursor_layer.h | 46 + src/backends/drm/drm_egl_layer.cpp | 188 + src/backends/drm/drm_egl_layer.h | 52 + src/backends/drm/drm_egl_layer_surface.cpp | 395 ++ src/backends/drm/drm_egl_layer_surface.h | 93 + src/backends/drm/drm_gbm_surface.cpp | 162 + src/backends/drm/drm_gbm_surface.h | 73 + src/backends/drm/drm_gpu.cpp | 837 +++ src/backends/drm/drm_gpu.h | 141 + src/backends/drm/drm_layer.cpp | 63 + src/backends/drm/drm_layer.h | 62 + src/backends/drm/drm_logging.cpp | 10 + src/backends/drm/drm_logging.h | 15 + src/backends/drm/drm_object.cpp | 229 + src/backends/drm/drm_object.h | 120 + src/backends/drm/drm_object_connector.cpp | 463 ++ src/backends/drm/drm_object_connector.h | 123 + src/backends/drm/drm_object_crtc.cpp | 111 + src/backends/drm/drm_object_crtc.h | 68 + src/backends/drm/drm_object_plane.cpp | 186 + src/backends/drm/drm_object_plane.h | 101 + src/backends/drm/drm_output.cpp | 544 ++ src/backends/drm/drm_output.h | 96 + src/backends/drm/drm_pipeline.cpp | 711 +++ src/backends/drm/drm_pipeline.h | 205 + src/backends/drm/drm_pipeline_legacy.cpp | 155 + src/backends/drm/drm_pointer.h | 153 + src/backends/drm/drm_property.cpp | 192 + src/backends/drm/drm_property.h | 112 + src/backends/drm/drm_qpainter_backend.cpp | 62 + src/backends/drm/drm_qpainter_backend.h | 42 + src/backends/drm/drm_qpainter_layer.cpp | 177 + src/backends/drm/drm_qpainter_layer.h | 78 + src/backends/drm/drm_render_backend.h | 32 + src/backends/drm/drm_shadow_buffer.cpp | 120 + src/backends/drm/drm_shadow_buffer.h | 42 + src/backends/drm/drm_virtual_egl_layer.cpp | 179 + src/backends/drm/drm_virtual_egl_layer.h | 60 + src/backends/drm/drm_virtual_output.cpp | 82 + src/backends/drm/drm_virtual_output.h | 46 + src/backends/drm/gbm_dmabuf.h | 87 + src/backends/drm/overview.md | 51 + src/backends/fakeinput/CMakeLists.txt | 4 + src/backends/fakeinput/fakeinputbackend.cpp | 35 + src/backends/fakeinput/fakeinputbackend.h | 40 + src/backends/fakeinput/fakeinputdevice.cpp | 160 + src/backends/fakeinput/fakeinputdevice.h | 49 + src/backends/libinput/CMakeLists.txt | 25 + src/backends/libinput/connection.cpp | 654 +++ src/backends/libinput/connection.h | 90 + src/backends/libinput/context.cpp | 178 + src/backends/libinput/context.h | 78 + src/backends/libinput/device.cpp | 720 +++ src/backends/libinput/device.h | 750 +++ src/backends/libinput/events.cpp | 402 ++ src/backends/libinput/events.h | 434 ++ src/backends/libinput/libinput_logging.cpp | 10 + src/backends/libinput/libinput_logging.h | 15 + src/backends/libinput/libinputbackend.cpp | 54 + src/backends/libinput/libinputbackend.h | 39 + src/backends/virtual/CMakeLists.txt | 7 + src/backends/virtual/virtual_backend.cpp | 134 + src/backends/virtual/virtual_backend.h | 66 + src/backends/virtual/virtual_egl_backend.cpp | 215 + src/backends/virtual/virtual_egl_backend.h | 68 + src/backends/virtual/virtual_logging.cpp | 9 + src/backends/virtual/virtual_logging.h | 11 + src/backends/virtual/virtual_output.cpp | 88 + src/backends/virtual/virtual_output.h | 52 + .../virtual/virtual_qpainter_backend.cpp | 84 + .../virtual/virtual_qpainter_backend.h | 57 + src/backends/wayland/CMakeLists.txt | 15 + src/backends/wayland/wayland_backend.cpp | 1003 ++++ src/backends/wayland/wayland_backend.h | 384 ++ src/backends/wayland/wayland_egl_backend.cpp | 396 ++ src/backends/wayland/wayland_egl_backend.h | 120 + src/backends/wayland/wayland_logging.cpp | 10 + src/backends/wayland/wayland_logging.h | 14 + src/backends/wayland/wayland_output.cpp | 221 + src/backends/wayland/wayland_output.h | 112 + .../wayland/wayland_qpainter_backend.cpp | 187 + .../wayland/wayland_qpainter_backend.h | 99 + src/backends/x11/CMakeLists.txt | 5 + src/backends/x11/common/CMakeLists.txt | 11 + src/backends/x11/common/ge_event_mem_mover.h | 45 + src/backends/x11/common/kwinxrenderutils.cpp | 167 + src/backends/x11/common/kwinxrenderutils.h | 114 + .../x11/common/x11_common_egl_backend.cpp | 285 + .../x11/common/x11_common_egl_backend.h | 71 + .../x11/common/x11_common_logging.cpp | 10 + .../x11/common/x11_common_logging_p.h | 17 + src/backends/x11/standalone/CMakeLists.txt | 40 + .../x11/standalone/x11_standalone_cursor.cpp | 137 + .../x11/standalone/x11_standalone_cursor.h | 72 + .../x11/standalone/x11_standalone_edge.cpp | 129 + .../x11/standalone/x11_standalone_edge.h | 68 + .../x11/standalone/x11_standalone_effects.cpp | 110 + .../x11/standalone/x11_standalone_effects.h | 47 + ...lone_effects_mouse_interception_filter.cpp | 94 + ...dalone_effects_mouse_interception_filter.h | 32 + .../standalone/x11_standalone_egl_backend.cpp | 303 + .../standalone/x11_standalone_egl_backend.h | 104 + .../standalone/x11_standalone_glx_backend.cpp | 975 ++++ .../standalone/x11_standalone_glx_backend.h | 178 + ...andalone_glx_context_attribute_builder.cpp | 42 + ...standalone_glx_context_attribute_builder.h | 21 + .../x11_standalone_glxconvenience.cpp | 59 + .../x11_standalone_glxconvenience.h | 17 + .../x11/standalone/x11_standalone_logging.cpp | 10 + .../x11/standalone/x11_standalone_logging.h | 15 + .../x11_standalone_non_composited_outline.cpp | 134 + .../x11_standalone_non_composited_outline.h | 47 + ..._standalone_omlsynccontrolvsyncmonitor.cpp | 157 + ...11_standalone_omlsynccontrolvsyncmonitor.h | 76 + .../x11/standalone/x11_standalone_output.cpp | 73 + .../x11/standalone/x11_standalone_output.h | 62 + .../x11_standalone_overlaywindow.cpp | 200 + .../standalone/x11_standalone_overlaywindow.h | 49 + .../x11_standalone_placeholderoutput.cpp | 48 + .../x11_standalone_placeholderoutput.h | 31 + .../standalone/x11_standalone_platform.cpp | 695 +++ .../x11/standalone/x11_standalone_platform.h | 101 + .../x11_standalone_screenedges_filter.cpp | 55 + .../x11_standalone_screenedges_filter.h | 26 + ...11_standalone_sgivideosyncvsyncmonitor.cpp | 159 + .../x11_standalone_sgivideosyncvsyncmonitor.h | 75 + .../x11_standalone_windowselector.cpp | 258 + .../x11_standalone_windowselector.h | 59 + ..._standalone_xfixes_cursor_event_filter.cpp | 29 + ...11_standalone_xfixes_cursor_event_filter.h | 30 + .../x11_standalone_xinputintegration.cpp | 287 + .../x11_standalone_xinputintegration.h | 60 + src/backends/x11/windowed/CMakeLists.txt | 12 + src/backends/x11/windowed/x11.json | 93 + .../x11/windowed/x11_windowed_backend.cpp | 686 +++ .../x11/windowed/x11_windowed_backend.h | 177 + .../x11/windowed/x11_windowed_egl_backend.cpp | 153 + .../x11/windowed/x11_windowed_egl_backend.h | 75 + .../x11/windowed/x11_windowed_logging.cpp | 10 + .../x11/windowed/x11_windowed_logging.h | 14 + .../x11/windowed/x11_windowed_output.cpp | 213 + .../x11/windowed/x11_windowed_output.h | 85 + .../x11_windowed_qpainter_backend.cpp | 110 + .../windowed/x11_windowed_qpainter_backend.h | 62 + src/client_machine.cpp | 231 + src/client_machine.h | 104 + src/colors/colordevice.cpp | 321 ++ src/colors/colordevice.h | 88 + src/colors/colormanager.cpp | 87 + src/colors/colormanager.h | 63 + src/composite.cpp | 995 ++++ src/composite.h | 262 + src/config-kwin.h.cmake | 38 + src/core/colorlut.cpp | 51 + src/core/colorlut.h | 37 + src/core/colorpipelinestage.cpp | 48 + src/core/colorpipelinestage.h | 33 + src/core/colortransformation.cpp | 61 + src/core/colortransformation.h | 41 + src/core/dmabufattributes.h | 37 + src/core/inputbackend.cpp | 27 + src/core/inputbackend.h | 46 + src/core/inputdevice.cpp | 17 + src/core/inputdevice.h | 83 + src/core/output.cpp | 401 ++ src/core/output.h | 354 ++ src/core/outputconfiguration.cpp | 37 + src/core/outputconfiguration.h | 44 + src/core/outputlayer.cpp | 43 + src/core/outputlayer.h | 57 + src/core/overlaywindow.cpp | 22 + src/core/overlaywindow.h | 44 + src/core/platform.cpp | 301 + src/core/platform.h | 379 ++ src/core/renderbackend.cpp | 34 + src/core/renderbackend.h | 42 + src/core/renderjournal.cpp | 56 + src/core/renderjournal.h | 57 + src/core/renderlayer.cpp | 276 + src/core/renderlayer.h | 97 + src/core/renderlayerdelegate.cpp | 45 + src/core/renderlayerdelegate.h | 67 + src/core/renderloop.cpp | 271 + src/core/renderloop.h | 152 + src/core/renderloop_p.h | 56 + src/core/rendertarget.cpp | 53 + src/core/rendertarget.h | 40 + src/core/session.cpp | 46 + src/core/session.h | 119 + src/core/session_consolekit.cpp | 352 ++ src/core/session_consolekit.h | 51 + src/core/session_logind.cpp | 351 ++ src/core/session_logind.h | 51 + src/core/session_noop.cpp | 57 + src/core/session_noop.h | 34 + src/cursor.cpp | 707 +++ src/cursor.h | 374 ++ src/cursordelegate_opengl.cpp | 80 + src/cursordelegate_opengl.h | 32 + src/cursordelegate_qpainter.cpp | 39 + src/cursordelegate_qpainter.h | 24 + src/dbusinterface.cpp | 556 ++ src/dbusinterface.h | 308 ++ src/debug_console.cpp | 1710 ++++++ src/debug_console.h | 209 + src/debug_console.ui | 525 ++ src/decorationitem.cpp | 292 + src/decorationitem.h | 106 + src/decorations/decoratedclient.cpp | 290 + src/decorations/decoratedclient.h | 107 + src/decorations/decorationbridge.cpp | 292 + src/decorations/decorationbridge.h | 89 + src/decorations/decorationpalette.cpp | 162 + src/decorations/decorationpalette.h | 77 + src/decorations/decorations_logging.cpp | 10 + src/decorations/decorations_logging.h | 15 + src/decorations/settings.cpp | 179 + src/decorations/settings.h | 64 + src/deleted.cpp | 206 + src/deleted.h | 181 + src/dmabuftexture.cpp | 39 + src/dmabuftexture.h | 36 + src/dpmsinputeventfilter.cpp | 114 + src/dpmsinputeventfilter.h | 45 + src/effectloader.cpp | 424 ++ src/effectloader.h | 346 ++ src/effects.cpp | 2677 +++++++++ src/effects.h | 684 +++ src/effects/CMakeLists.txt | 106 + src/effects/Messages.sh | 4 + src/effects/backgroundcontrast/.directory | 3 + src/effects/backgroundcontrast/CMakeLists.txt | 15 + src/effects/backgroundcontrast/contrast.cpp | 474 ++ src/effects/backgroundcontrast/contrast.h | 86 + .../backgroundcontrast/contrastshader.cpp | 203 + .../backgroundcontrast/contrastshader.h | 51 + src/effects/backgroundcontrast/main.cpp | 20 + src/effects/backgroundcontrast/metadata.json | 84 + src/effects/blendchanges/CMakeLists.txt | 14 + src/effects/blendchanges/blendchanges.cpp | 103 + src/effects/blendchanges/blendchanges.h | 52 + src/effects/blendchanges/main.cpp | 18 + src/effects/blendchanges/metadata.json | 81 + src/effects/blur/CMakeLists.txt | 42 + src/effects/blur/blur.cpp | 899 +++ src/effects/blur/blur.h | 141 + src/effects/blur/blur.kcfg | 15 + src/effects/blur/blur.qrc | 14 + src/effects/blur/blur_config.cpp | 45 + src/effects/blur/blur_config.h | 32 + src/effects/blur/blur_config.ui | 160 + src/effects/blur/blurconfig.kcfgc | 5 + src/effects/blur/blurshader.cpp | 300 + src/effects/blur/blurshader.h | 102 + src/effects/blur/main.cpp | 20 + src/effects/blur/metadata.json | 85 + src/effects/blur/shaders/copy.frag | 9 + src/effects/blur/shaders/copy_core.frag | 13 + src/effects/blur/shaders/downsample.frag | 17 + src/effects/blur/shaders/downsample_core.frag | 21 + src/effects/blur/shaders/noise.frag | 14 + src/effects/blur/shaders/noise_core.frag | 18 + src/effects/blur/shaders/upsample.frag | 20 + src/effects/blur/shaders/upsample_core.frag | 24 + src/effects/blur/shaders/vertex.vert | 7 + src/effects/blur/shaders/vertex_core.vert | 9 + src/effects/colorpicker/CMakeLists.txt | 16 + src/effects/colorpicker/colorpicker.cpp | 114 + src/effects/colorpicker/colorpicker.h | 53 + src/effects/colorpicker/main.cpp | 18 + src/effects/colorpicker/metadata.json | 85 + src/effects/desktopgrid/CMakeLists.txt | 49 + .../desktopgrid/desktopgrid_config.cpp | 125 + src/effects/desktopgrid/desktopgrid_config.h | 51 + src/effects/desktopgrid/desktopgrid_config.ui | 201 + .../desktopgrid/desktopgridconfig.kcfg | 32 + .../desktopgrid/desktopgridconfig.kcfgc | 9 + src/effects/desktopgrid/desktopgrideffect.cpp | 377 ++ src/effects/desktopgrid/desktopgrideffect.h | 116 + src/effects/desktopgrid/main.cpp | 18 + src/effects/desktopgrid/metadata.json | 86 + src/effects/desktopgrid/qml/DesktopView.qml | 300 + src/effects/desktopgrid/qml/main.qml | 324 ++ .../package/contents/code/main.js | 153 + .../dialogparent/package/metadata.desktop | 160 + src/effects/diminactive/CMakeLists.txt | 36 + src/effects/diminactive/diminactive.cpp | 406 ++ src/effects/diminactive/diminactive.h | 130 + src/effects/diminactive/diminactive.kcfg | 27 + .../diminactive/diminactive_config.cpp | 50 + src/effects/diminactive/diminactive_config.h | 37 + src/effects/diminactive/diminactive_config.ui | 83 + .../diminactive/diminactiveconfig.kcfgc | 5 + src/effects/diminactive/main.cpp | 17 + src/effects/diminactive/metadata.json | 86 + .../dimscreen/package/contents/code/main.js | 227 + .../dimscreen/package/metadata.desktop | 146 + .../eyeonscreen/package/contents/code/main.js | 154 + .../eyeonscreen/package/metadata.desktop | 95 + .../fade/package/contents/code/main.js | 118 + .../fade/package/contents/config/main.xml | 20 + src/effects/fade/package/metadata.desktop | 164 + .../fadedesktop/package/contents/code/main.js | 125 + .../fadedesktop/package/metadata.desktop | 151 + .../package/contents/code/main.js | 141 + .../fadingpopups/package/metadata.desktop | 94 + src/effects/fallapart/CMakeLists.txt | 19 + src/effects/fallapart/fallapart.cpp | 217 + src/effects/fallapart/fallapart.h | 67 + src/effects/fallapart/fallapart.kcfg | 14 + src/effects/fallapart/fallapartconfig.kcfgc | 5 + src/effects/fallapart/main.cpp | 18 + src/effects/fallapart/metadata.json | 82 + .../frozenapp/package/contents/code/main.js | 118 + .../frozenapp/package/metadata.desktop | 107 + .../package/contents/code/fullscreen.js | 100 + .../fullscreen/package/metadata.desktop | 93 + src/effects/glide/CMakeLists.txt | 36 + src/effects/glide/glide.cpp | 312 ++ src/effects/glide/glide.h | 154 + src/effects/glide/glide.kcfg | 40 + src/effects/glide/glide_config.cpp | 45 + src/effects/glide/glide_config.h | 33 + src/effects/glide/glide_config.ui | 260 + src/effects/glide/glideconfig.kcfgc | 5 + src/effects/glide/main.cpp | 18 + src/effects/glide/metadata.json | 82 + src/effects/highlightwindow/CMakeLists.txt | 15 + .../highlightwindow/highlightwindow.cpp | 276 + src/effects/highlightwindow/highlightwindow.h | 64 + src/effects/highlightwindow/main.cpp | 17 + src/effects/highlightwindow/metadata.json | 84 + src/effects/invert/CMakeLists.txt | 34 + src/effects/invert/invert.cpp | 136 + src/effects/invert/invert.h | 62 + src/effects/invert/invert.qrc | 6 + src/effects/invert/invert_config.cpp | 90 + src/effects/invert/invert_config.h | 38 + src/effects/invert/main.cpp | 18 + src/effects/invert/metadata.json | 85 + src/effects/invert/shaders/invert.frag | 22 + src/effects/invert/shaders/invert_core.frag | 25 + src/effects/kscreen/CMakeLists.txt | 16 + src/effects/kscreen/kscreen.cpp | 220 + src/effects/kscreen/kscreen.h | 67 + src/effects/kscreen/kscreen.kcfg | 12 + src/effects/kscreen/kscreenconfig.kcfgc | 5 + src/effects/kscreen/main.cpp | 17 + src/effects/kscreen/metadata.json | 83 + src/effects/kwineffect.desktop | 108 + .../login/package/contents/code/main.js | 69 + .../login/package/contents/config/main.xml | 12 + .../login/package/contents/ui/config.ui | 38 + src/effects/login/package/metadata.desktop | 173 + .../logout/package/contents/code/main.js | 68 + src/effects/logout/package/metadata.desktop | 98 + src/effects/magiclamp/CMakeLists.txt | 36 + src/effects/magiclamp/magiclamp.cpp | 373 ++ src/effects/magiclamp/magiclamp.h | 66 + src/effects/magiclamp/magiclamp.kcfg | 12 + src/effects/magiclamp/magiclamp_config.cpp | 56 + src/effects/magiclamp/magiclamp_config.h | 42 + src/effects/magiclamp/magiclamp_config.ui | 53 + src/effects/magiclamp/magiclampconfig.kcfgc | 5 + src/effects/magiclamp/main.cpp | 18 + src/effects/magiclamp/metadata.json | 86 + src/effects/magnifier/CMakeLists.txt | 41 + src/effects/magnifier/magnifier.cpp | 288 + src/effects/magnifier/magnifier.h | 62 + src/effects/magnifier/magnifier.kcfg | 18 + src/effects/magnifier/magnifier_config.cpp | 104 + src/effects/magnifier/magnifier_config.h | 46 + src/effects/magnifier/magnifier_config.ui | 106 + src/effects/magnifier/magnifierconfig.kcfgc | 5 + src/effects/magnifier/main.cpp | 18 + src/effects/magnifier/metadata.json | 86 + .../package/contents/code/maximize.js | 116 + src/effects/maximize/package/metadata.desktop | 124 + .../package/contents/code/morphingpopups.js | 131 + .../morphingpopups/package/metadata.desktop | 105 + src/effects/mouseclick/CMakeLists.txt | 41 + src/effects/mouseclick/main.cpp | 17 + src/effects/mouseclick/metadata.json | 85 + src/effects/mouseclick/mouseclick.cpp | 430 ++ src/effects/mouseclick/mouseclick.h | 162 + src/effects/mouseclick/mouseclick.kcfg | 34 + src/effects/mouseclick/mouseclick_config.cpp | 80 + src/effects/mouseclick/mouseclick_config.h | 45 + src/effects/mouseclick/mouseclick_config.ui | 282 + src/effects/mouseclick/mouseclickconfig.kcfgc | 5 + src/effects/mousemark/CMakeLists.txt | 40 + src/effects/mousemark/main.cpp | 17 + src/effects/mousemark/metadata.json | 82 + src/effects/mousemark/mousemark.cpp | 234 + src/effects/mousemark/mousemark.h | 63 + src/effects/mousemark/mousemark.kcfg | 15 + src/effects/mousemark/mousemark_config.cpp | 105 + src/effects/mousemark/mousemark_config.h | 48 + src/effects/mousemark/mousemark_config.ui | 109 + src/effects/mousemark/mousemarkconfig.kcfgc | 5 + src/effects/outputlocator/CMakeLists.txt | 14 + src/effects/outputlocator/main.cpp | 14 + src/effects/outputlocator/metadata.json | 17 + src/effects/outputlocator/outputlocator.cpp | 122 + src/effects/outputlocator/outputlocator.h | 37 + src/effects/outputlocator/qml/OutputLabel.qml | 43 + src/effects/overview/CMakeLists.txt | 29 + src/effects/overview/kcm/CMakeLists.txt | 17 + .../overview/kcm/overvieweffectkcm.cpp | 75 + src/effects/overview/kcm/overvieweffectkcm.h | 32 + src/effects/overview/kcm/overvieweffectkcm.ui | 89 + src/effects/overview/main.cpp | 18 + src/effects/overview/metadata.json | 86 + src/effects/overview/overviewconfig.kcfg | 27 + src/effects/overview/overviewconfig.kcfgc | 9 + src/effects/overview/overvieweffect.cpp | 311 ++ src/effects/overview/overvieweffect.h | 98 + src/effects/overview/qml/DesktopBar.qml | 313 ++ src/effects/overview/qml/DesktopView.qml | 34 + src/effects/overview/qml/ScreenView.qml | 333 ++ src/effects/private/CMakeLists.txt | 22 + src/effects/private/expoarea.cpp | 83 + src/effects/private/expoarea.h | 48 + src/effects/private/expolayout.cpp | 723 +++ src/effects/private/expolayout.h | 166 + src/effects/private/plugin.cpp | 18 + src/effects/private/plugin.h | 18 + src/effects/private/qml/WindowHeap.qml | 371 ++ .../private/qml/WindowHeapDelegate.qml | 479 ++ src/effects/private/qmldir | 11 + .../scale/package/contents/code/main.js | 172 + .../scale/package/contents/config/main.xml | 18 + .../scale/package/contents/ui/config.ui | 93 + src/effects/scale/package/metadata.desktop | 99 + src/effects/screenedge/CMakeLists.txt | 16 + src/effects/screenedge/main.cpp | 17 + src/effects/screenedge/metadata.json | 81 + src/effects/screenedge/screenedgeeffect.cpp | 303 + src/effects/screenedge/screenedgeeffect.h | 69 + src/effects/screenshot/CMakeLists.txt | 35 + src/effects/screenshot/main.cpp | 18 + src/effects/screenshot/metadata.json | 83 + .../screenshot/org.kde.KWin.ScreenShot2.xml | 273 + src/effects/screenshot/screenshot.cpp | 433 ++ src/effects/screenshot/screenshot.h | 109 + .../screenshot/screenshotdbusinterface1.cpp | 870 +++ .../screenshot/screenshotdbusinterface1.h | 177 + .../screenshot/screenshotdbusinterface2.cpp | 536 ++ .../screenshot/screenshotdbusinterface2.h | 68 + src/effects/screentransform/CMakeLists.txt | 14 + src/effects/screentransform/main.cpp | 18 + src/effects/screentransform/metadata.json | 84 + .../screentransform/screentransform.cpp | 244 + src/effects/screentransform/screentransform.h | 70 + .../screentransform/screentransform.qrc | 9 + .../screentransform/shaders/crossfade.frag | 12 + .../screentransform/shaders/crossfade.vert | 12 + .../shaders/crossfade_core.frag | 16 + .../shaders/crossfade_core.vert | 14 + .../sessionquit/package/contents/code/main.js | 32 + .../sessionquit/package/metadata.desktop | 94 + src/effects/sheet/CMakeLists.txt | 16 + src/effects/sheet/main.cpp | 18 + src/effects/sheet/metadata.json | 80 + src/effects/sheet/sheet.cpp | 212 + src/effects/sheet/sheet.h | 77 + src/effects/sheet/sheet.kcfg | 12 + src/effects/sheet/sheetconfig.kcfgc | 5 + src/effects/showfps/CMakeLists.txt | 20 + src/effects/showfps/main.cpp | 18 + src/effects/showfps/metadata.json | 81 + src/effects/showfps/qml/main.qml | 195 + src/effects/showfps/showfpseffect.cpp | 126 + src/effects/showfps/showfpseffect.h | 65 + src/effects/showpaint/CMakeLists.txt | 33 + src/effects/showpaint/main.cpp | 17 + src/effects/showpaint/metadata.json | 82 + src/effects/showpaint/showpaint.cpp | 113 + src/effects/showpaint/showpaint.h | 44 + src/effects/showpaint/showpaint_config.cpp | 69 + src/effects/showpaint/showpaint_config.h | 35 + src/effects/showpaint/showpaint_config.ui | 39 + src/effects/slide/CMakeLists.txt | 37 + src/effects/slide/main.cpp | 18 + src/effects/slide/metadata.json | 83 + src/effects/slide/slide.cpp | 571 ++ src/effects/slide/slide.h | 173 + src/effects/slide/slide.kcfg | 22 + src/effects/slide/slide_config.cpp | 49 + src/effects/slide/slide_config.h | 35 + src/effects/slide/slide_config.ui | 100 + src/effects/slide/slideconfig.kcfgc | 5 + src/effects/slide/springmotion.cpp | 154 + src/effects/slide/springmotion.h | 103 + src/effects/slideback/CMakeLists.txt | 13 + src/effects/slideback/main.cpp | 17 + src/effects/slideback/metadata.json | 79 + src/effects/slideback/slideback.cpp | 339 ++ src/effects/slideback/slideback.h | 69 + src/effects/slidingpopups/CMakeLists.txt | 20 + src/effects/slidingpopups/main.cpp | 18 + src/effects/slidingpopups/metadata.json | 82 + src/effects/slidingpopups/slidingpopups.cpp | 583 ++ src/effects/slidingpopups/slidingpopups.h | 122 + src/effects/slidingpopups/slidingpopups.kcfg | 17 + .../slidingpopups/slidingpopupsconfig.kcfgc | 5 + src/effects/snaphelper/CMakeLists.txt | 14 + src/effects/snaphelper/main.cpp | 17 + src/effects/snaphelper/metadata.json | 83 + src/effects/snaphelper/snaphelper.cpp | 265 + src/effects/snaphelper/snaphelper.h | 56 + .../squash/package/contents/code/main.js | 155 + src/effects/squash/package/metadata.desktop | 95 + src/effects/startupfeedback/CMakeLists.txt | 18 + src/effects/startupfeedback/main.cpp | 18 + src/effects/startupfeedback/metadata.json | 83 + .../shaders/blinking-startup.frag | 13 + .../shaders/blinking-startup_core.frag | 16 + .../startupfeedback/startupfeedback.cpp | 439 ++ src/effects/startupfeedback/startupfeedback.h | 98 + .../startupfeedback/startupfeedback.qrc | 7 + src/effects/strip-effect-metadata.py | 28 + src/effects/thumbnailaside/CMakeLists.txt | 40 + src/effects/thumbnailaside/main.cpp | 17 + src/effects/thumbnailaside/metadata.json | 82 + src/effects/thumbnailaside/thumbnailaside.cpp | 195 + src/effects/thumbnailaside/thumbnailaside.h | 87 + .../thumbnailaside/thumbnailaside.kcfg | 21 + .../thumbnailaside/thumbnailaside_config.cpp | 85 + .../thumbnailaside/thumbnailaside_config.h | 45 + .../thumbnailaside/thumbnailaside_config.ui | 138 + .../thumbnailaside/thumbnailasideconfig.kcfgc | 5 + src/effects/touchpoints/CMakeLists.txt | 15 + src/effects/touchpoints/main.cpp | 17 + src/effects/touchpoints/metadata.json | 82 + src/effects/touchpoints/touchpoints.cpp | 258 + src/effects/touchpoints/touchpoints.h | 92 + src/effects/trackmouse/CMakeLists.txt | 42 + src/effects/trackmouse/data/tm_inner.png | Bin 0 -> 1247 bytes src/effects/trackmouse/data/tm_outer.png | Bin 0 -> 1311 bytes src/effects/trackmouse/main.cpp | 17 + src/effects/trackmouse/metadata.json | 85 + src/effects/trackmouse/trackmouse.cpp | 253 + src/effects/trackmouse/trackmouse.h | 75 + src/effects/trackmouse/trackmouse.kcfg | 21 + src/effects/trackmouse/trackmouse_config.cpp | 109 + src/effects/trackmouse/trackmouse_config.h | 51 + src/effects/trackmouse/trackmouse_config.ui | 104 + src/effects/trackmouse/trackmouseconfig.kcfgc | 5 + .../package/contents/code/main.js | 219 + .../package/contents/config/main.xml | 36 + .../package/contents/ui/config.ui | 473 ++ .../translucency/package/metadata.desktop | 170 + .../package/contents/code/main.js | 243 + .../windowaperture/package/metadata.desktop | 102 + src/effects/windowview/CMakeLists.txt | 32 + src/effects/windowview/kcm/CMakeLists.txt | 17 + .../windowview/kcm/windowvieweffectkcm.cpp | 96 + .../windowview/kcm/windowvieweffectkcm.h | 32 + .../windowview/kcm/windowvieweffectkcm.ui | 72 + src/effects/windowview/main.cpp | 18 + src/effects/windowview/metadata.json | 82 + .../org.kde.KWin.Effect.WindowView1.xml | 24 + src/effects/windowview/qml/main.qml | 240 + src/effects/windowview/windowviewconfig.kcfg | 32 + src/effects/windowview/windowviewconfig.kcfgc | 10 + src/effects/windowview/windowvieweffect.cpp | 470 ++ src/effects/windowview/windowvieweffect.h | 128 + src/effects/wobblywindows/CMakeLists.txt | 36 + src/effects/wobblywindows/main.cpp | 18 + src/effects/wobblywindows/metadata.json | 85 + src/effects/wobblywindows/wobblywindows.cpp | 1161 ++++ src/effects/wobblywindows/wobblywindows.h | 171 + src/effects/wobblywindows/wobblywindows.kcfg | 57 + .../wobblywindows/wobblywindows_config.cpp | 101 + .../wobblywindows/wobblywindows_config.h | 40 + .../wobblywindows/wobblywindows_config.ui | 373 ++ .../wobblywindows/wobblywindowsconfig.kcfgc | 5 + src/effects/zoom/CMakeLists.txt | 52 + src/effects/zoom/accessibilityintegration.cpp | 99 + src/effects/zoom/accessibilityintegration.h | 46 + src/effects/zoom/main.cpp | 17 + src/effects/zoom/metadata.json | 87 + src/effects/zoom/zoom.cpp | 630 +++ src/effects/zoom/zoom.h | 140 + src/effects/zoom/zoom.kcfg | 33 + src/effects/zoom/zoom_config.cpp | 141 + src/effects/zoom/zoom_config.h | 49 + src/effects/zoom/zoom_config.ui | 195 + src/effects/zoom/zoomconfig.kcfgc | 5 + src/events.cpp | 1363 +++++ src/focuschain.cpp | 247 + src/focuschain.h | 232 + src/ftrace.cpp | 121 + src/ftrace.h | 107 + src/gestures.cpp | 581 ++ src/gestures.h | 235 + src/globalshortcuts.cpp | 341 ++ src/globalshortcuts.h | 229 + src/group.cpp | 121 + src/group.h | 86 + src/helpers/CMakeLists.txt | 2 + src/helpers/killer/CMakeLists.txt | 19 + src/helpers/killer/killer.cpp | 118 + src/helpers/wayland_wrapper/CMakeLists.txt | 20 + src/helpers/wayland_wrapper/kwin_wrapper.cpp | 177 + src/helpers/wayland_wrapper/wl-socket.c | 173 + src/helpers/wayland_wrapper/wl-socket.h | 39 + src/hide_cursor_spy.cpp | 64 + src/hide_cursor_spy.h | 30 + src/idle_inhibition.cpp | 97 + src/idle_inhibition.h | 40 + src/idledetector.cpp | 74 + src/idledetector.h | 42 + src/input.cpp | 3515 ++++++++++++ src/input.h | 560 ++ src/input_event.cpp | 67 + src/input_event.h | 223 + src/input_event_spy.cpp | 166 + src/input_event_spy.h | 91 + src/inputmethod.cpp | 860 +++ src/inputmethod.h | 148 + src/inputpanelv1integration.cpp | 32 + src/inputpanelv1integration.h | 29 + src/inputpanelv1window.cpp | 214 + src/inputpanelv1window.h | 102 + src/internalwindow.cpp | 559 ++ src/internalwindow.h | 98 + src/item.cpp | 432 ++ src/item.h | 155 + src/kcmkwin/CMakeLists.txt | 16 + src/kcmkwin/common/CMakeLists.txt | 31 + src/kcmkwin/common/Messages.sh | 2 + src/kcmkwin/common/effectsmodel.cpp | 658 +++ src/kcmkwin/common/effectsmodel.h | 273 + src/kcmkwin/kwincompositing/CMakeLists.txt | 32 + src/kcmkwin/kwincompositing/Messages.sh | 3 + src/kcmkwin/kwincompositing/compositing.ui | 301 + .../kwincompositing/kwincompositing.json | 108 + .../kwincompositing_setting.kcfg | 68 + .../kwincompositing_setting.kcfgc | 5 + .../kwincompositing/kwincompositingdata.cpp | 34 + .../kwincompositing/kwincompositingdata.h | 32 + src/kcmkwin/kwincompositing/main.cpp | 232 + src/kcmkwin/kwindecoration/CMakeLists.txt | 58 + src/kcmkwin/kwindecoration/Messages.sh | 2 + .../declarative-plugin/CMakeLists.txt | 26 + .../declarative-plugin/buttonsmodel.cpp | 182 + .../declarative-plugin/buttonsmodel.h | 52 + .../declarative-plugin/plugin.cpp | 37 + .../declarative-plugin/plugin.h | 27 + .../declarative-plugin/previewbridge.cpp | 233 + .../declarative-plugin/previewbridge.h | 133 + .../declarative-plugin/previewbutton.cpp | 137 + .../declarative-plugin/previewbutton.h | 75 + .../declarative-plugin/previewclient.cpp | 451 ++ .../declarative-plugin/previewclient.h | 201 + .../declarative-plugin/previewitem.cpp | 459 ++ .../declarative-plugin/previewitem.h | 90 + .../declarative-plugin/previewsettings.cpp | 262 + .../declarative-plugin/previewsettings.h | 153 + .../kwindecoration/declarative-plugin/qmldir | 2 + .../kwindecoration/decorationmodel.cpp | 169 + src/kcmkwin/kwindecoration/decorationmodel.h | 57 + src/kcmkwin/kwindecoration/kcm.cpp | 262 + src/kcmkwin/kwindecoration/kcm.h | 98 + .../kwindecoration/kcm_kwindecoration.json | 116 + .../kwin-applywindowdecoration.cpp | 130 + .../kwindecorationsettings.kcfg | 54 + .../kwindecorationsettings.kcfgc | 7 + .../package/contents/ui/ButtonGroup.qml | 65 + .../package/contents/ui/Buttons.qml | 264 + .../package/contents/ui/Themes.qml | 117 + .../package/contents/ui/main.qml | 177 + src/kcmkwin/kwindecoration/utils.cpp | 105 + src/kcmkwin/kwindecoration/utils.h | 28 + .../window-decorations.knsrc.cmake | 68 + src/kcmkwin/kwindesktop/CMakeLists.txt | 33 + src/kcmkwin/kwindesktop/Messages.sh | 2 + src/kcmkwin/kwindesktop/animationsmodel.cpp | 176 + src/kcmkwin/kwindesktop/animationsmodel.h | 71 + src/kcmkwin/kwindesktop/desktopsmodel.cpp | 674 +++ src/kcmkwin/kwindesktop/desktopsmodel.h | 127 + .../kwindesktop/kcm_kwin_virtualdesktops.json | 114 + .../kwindesktop/package/contents/ui/main.qml | 328 ++ src/kcmkwin/kwindesktop/virtualdesktops.cpp | 168 + src/kcmkwin/kwindesktop/virtualdesktops.h | 57 + .../kwindesktop/virtualdesktopsdata.cpp | 52 + src/kcmkwin/kwindesktop/virtualdesktopsdata.h | 40 + .../kwindesktop/virtualdesktopssettings.kcfg | 29 + .../kwindesktop/virtualdesktopssettings.kcfgc | 6 + src/kcmkwin/kwineffects/CMakeLists.txt | 31 + src/kcmkwin/kwineffects/Messages.sh | 2 + .../kwineffects/desktopeffectsdata.cpp | 33 + src/kcmkwin/kwineffects/desktopeffectsdata.h | 34 + .../kwineffects/effectsfilterproxymodel.cpp | 91 + .../kwineffects/effectsfilterproxymodel.h | 51 + src/kcmkwin/kwineffects/kcm.cpp | 100 + src/kcmkwin/kwineffects/kcm.h | 47 + src/kcmkwin/kwineffects/kcm_kwin_effects.json | 105 + src/kcmkwin/kwineffects/kwineffect.knsrc | 54 + .../package/contents/ui/Effect.qml | 132 + .../kwineffects/package/contents/ui/Video.qml | 43 + .../kwineffects/package/contents/ui/main.qml | 136 + src/kcmkwin/kwinoptions/AUTHORS | 12 + src/kcmkwin/kwinoptions/CMakeLists.txt | 34 + src/kcmkwin/kwinoptions/ChangeLog | 51 + src/kcmkwin/kwinoptions/Messages.sh | 3 + src/kcmkwin/kwinoptions/actions.ui | 532 ++ src/kcmkwin/kwinoptions/advanced.ui | 183 + src/kcmkwin/kwinoptions/focus.ui | 291 + src/kcmkwin/kwinoptions/kcm_kwinoptions.json | 108 + .../kwinoptions_kdeglobals_settings.kcfg | 15 + .../kwinoptions_kdeglobals_settings.kcfgc | 6 + .../kwinoptions/kwinoptions_settings.kcfg | 372 ++ .../kwinoptions/kwinoptions_settings.kcfgc | 6 + src/kcmkwin/kwinoptions/main.cpp | 242 + src/kcmkwin/kwinoptions/main.h | 77 + src/kcmkwin/kwinoptions/mouse.cpp | 125 + src/kcmkwin/kwinoptions/mouse.h | 86 + src/kcmkwin/kwinoptions/mouse.ui | 733 +++ src/kcmkwin/kwinoptions/moving.ui | 134 + src/kcmkwin/kwinoptions/windows.cpp | 347 ++ src/kcmkwin/kwinoptions/windows.h | 131 + src/kcmkwin/kwinrules/CMakeLists.txt | 56 + src/kcmkwin/kwinrules/Messages.sh | 2 + src/kcmkwin/kwinrules/kcm_kwinrules.json | 115 + src/kcmkwin/kwinrules/kcmrules.cpp | 492 ++ src/kcmkwin/kwinrules/kcmrules.h | 69 + src/kcmkwin/kwinrules/kwinsrc.cpp | 33 + src/kcmkwin/kwinrules/main.cpp | 63 + src/kcmkwin/kwinrules/optionsmodel.cpp | 247 + src/kcmkwin/kwinrules/optionsmodel.h | 132 + .../org.kde.kwin_rules_dialog.desktop | 70 + .../package/contents/ui/FileDialogLoader.qml | 45 + .../package/contents/ui/OptionsComboBox.qml | 112 + .../package/contents/ui/RuleItemDelegate.qml | 98 + .../package/contents/ui/RulesEditor.qml | 316 ++ .../package/contents/ui/ValueEditor.qml | 234 + .../kwinrules/package/contents/ui/main.qml | 260 + src/kcmkwin/kwinrules/rulebookmodel.cpp | 194 + src/kcmkwin/kwinrules/rulebookmodel.h | 56 + src/kcmkwin/kwinrules/ruleitem.cpp | 213 + src/kcmkwin/kwinrules/ruleitem.h | 112 + src/kcmkwin/kwinrules/rulesmodel.cpp | 889 +++ src/kcmkwin/kwinrules/rulesmodel.h | 121 + src/kcmkwin/kwinscreenedges/CMakeLists.txt | 59 + src/kcmkwin/kwinscreenedges/Messages.sh | 4 + .../kwinscreenedges/kcm_kwinscreenedges.json | 107 + .../kwinscreenedges/kcm_kwintouchscreen.json | 121 + .../kwinscreenedges/kwinscreenedge.cpp | 222 + src/kcmkwin/kwinscreenedges/kwinscreenedge.h | 75 + .../kwinscreenedgeconfigform.cpp | 136 + .../kwinscreenedgeconfigform.h | 74 + .../kwinscreenedgeeffectsettings.kcfg | 14 + .../kwinscreenedgeeffectsettings.kcfgc | 7 + .../kwinscreenedgescriptsettings.kcfg | 14 + .../kwinscreenedgescriptsettings.kcfgc | 7 + .../kwinscreenedgesettings.kcfg | 87 + .../kwinscreenedgesettings.kcfgc | 7 + .../kwintouchscreenedgeconfigform.cpp | 34 + .../kwintouchscreenedgeconfigform.h | 41 + .../kwintouchscreenedgeeffectsettings.kcfg | 14 + .../kwintouchscreenedgeeffectsettings.kcfgc | 7 + .../kwintouchscreenscriptsettings.kcfg | 14 + .../kwintouchscreenscriptsettings.kcfgc | 7 + .../kwintouchscreensettings.kcfg | 40 + .../kwintouchscreensettings.kcfgc | 7 + src/kcmkwin/kwinscreenedges/main.cpp | 362 ++ src/kcmkwin/kwinscreenedges/main.h | 74 + src/kcmkwin/kwinscreenedges/main.ui | 346 ++ src/kcmkwin/kwinscreenedges/monitor.cpp | 315 ++ src/kcmkwin/kwinscreenedges/monitor.h | 109 + .../kwinscreenedges/screenpreviewwidget.cpp | 152 + .../kwinscreenedges/screenpreviewwidget.h | 43 + src/kcmkwin/kwinscreenedges/touch.cpp | 330 ++ src/kcmkwin/kwinscreenedges/touch.h | 74 + src/kcmkwin/kwinscreenedges/touch.ui | 72 + src/kcmkwin/kwinscripts/CMakeLists.txt | 25 + src/kcmkwin/kwinscripts/Messages.sh | 4 + src/kcmkwin/kwinscripts/kcm_kwin_scripts.json | 110 + src/kcmkwin/kwinscripts/kwinscripts.knsrc | 53 + src/kcmkwin/kwinscripts/kwinscriptsdata.cpp | 42 + src/kcmkwin/kwinscripts/kwinscriptsdata.h | 32 + src/kcmkwin/kwinscripts/module.cpp | 171 + src/kcmkwin/kwinscripts/module.h | 87 + .../kwinscripts/package/contents/ui/main.qml | 75 + src/kcmkwin/kwintabbox/CMakeLists.txt | 44 + src/kcmkwin/kwintabbox/Messages.sh | 4 + src/kcmkwin/kwintabbox/kcm_kwintabbox.json | 108 + .../kwintabbox/kwinpluginssettings.kcfg | 12 + .../kwintabbox/kwinpluginssettings.kcfgc | 6 + .../kwintabbox/kwinswitcheffectsettings.kcfg | 17 + .../kwintabbox/kwinswitcheffectsettings.kcfgc | 6 + src/kcmkwin/kwintabbox/kwinswitcher.knsrc | 54 + .../kwintabbox/kwintabboxconfigform.cpp | 474 ++ src/kcmkwin/kwintabbox/kwintabboxconfigform.h | 136 + src/kcmkwin/kwintabbox/kwintabboxdata.cpp | 48 + src/kcmkwin/kwintabbox/kwintabboxdata.h | 46 + .../kwintabbox/kwintabboxsettings.kcfg | 44 + .../kwintabbox/kwintabboxsettings.kcfgc | 7 + src/kcmkwin/kwintabbox/layoutpreview.cpp | 258 + src/kcmkwin/kwintabbox/layoutpreview.h | 153 + src/kcmkwin/kwintabbox/main.cpp | 395 ++ src/kcmkwin/kwintabbox/main.h | 66 + src/kcmkwin/kwintabbox/main.ui | 704 +++ src/kcmkwin/kwintabbox/thumbnailitem.cpp | 143 + src/kcmkwin/kwintabbox/thumbnailitem.h | 87 + src/kcmkwin/kwintabbox/thumbnails/desktop.png | Bin 0 -> 141420 bytes src/kcmkwin/kwintabbox/thumbnails/dolphin.png | Bin 0 -> 26991 bytes src/kcmkwin/kwintabbox/thumbnails/kmail.png | Bin 0 -> 38069 bytes .../kwintabbox/thumbnails/konqueror.png | Bin 0 -> 57484 bytes .../kwintabbox/thumbnails/systemsettings.png | Bin 0 -> 23682 bytes .../kwinvirtualkeyboard/CMakeLists.txt | 25 + src/kcmkwin/kwinvirtualkeyboard/Messages.sh | 2 + .../kcm_virtualkeyboard.json | 111 + .../kcmvirtualkeyboard.cpp | 103 + .../kwinvirtualkeyboard/kcmvirtualkeyboard.h | 57 + .../package/contents/ui/main.qml | 42 + .../virtualkeyboardsettings.kcfg | 10 + .../virtualkeyboardsettings.kcfgc | 7 + src/keyboard_input.cpp | 291 + src/keyboard_input.h | 93 + src/keyboard_layout.cpp | 256 + src/keyboard_layout.h | 108 + src/keyboard_layout_switching.cpp | 342 ++ src/keyboard_layout_switching.h | 147 + src/keyboard_repeat.cpp | 62 + src/keyboard_repeat.h | 44 + src/killwindow.cpp | 50 + src/killwindow.h | 29 + src/kwin.kcfg | 354 ++ src/kwin.notifyrc | 385 ++ src/kwineglutils_p.h | 55 + src/layers.cpp | 750 +++ src/layershellv1integration.cpp | 218 + src/layershellv1integration.h | 37 + src/layershellv1window.cpp | 297 + src/layershellv1window.h | 68 + src/libkwineffects/CMakeLists.txt | 98 + src/libkwineffects/KWinEffectsConfig.cmake.in | 11 + src/libkwineffects/Mainpage.dox | 22 + src/libkwineffects/Messages.sh | 2 + src/libkwineffects/anidata.cpp | 127 + src/libkwineffects/anidata_p.h | 89 + src/libkwineffects/kwinanimationeffect.cpp | 1035 ++++ src/libkwineffects/kwinanimationeffect.h | 550 ++ src/libkwineffects/kwinconfig.h.cmake | 25 + src/libkwineffects/kwineffects.cpp | 1672 ++++++ src/libkwineffects/kwineffects.h | 4172 ++++++++++++++ src/libkwineffects/kwineglimagetexture.cpp | 36 + src/libkwineffects/kwineglimagetexture.h | 33 + src/libkwineffects/kwinglobals.h | 262 + src/libkwineffects/kwinglplatform.cpp | 1369 +++++ src/libkwineffects/kwinglplatform.h | 470 ++ src/libkwineffects/kwingltexture.cpp | 742 +++ src/libkwineffects/kwingltexture.h | 161 + src/libkwineffects/kwingltexture_p.h | 82 + src/libkwineffects/kwinglutils.cpp | 2179 ++++++++ src/libkwineffects/kwinglutils.h | 724 +++ src/libkwineffects/kwinglutils_funcs.cpp | 90 + src/libkwineffects/kwinglutils_funcs.h | 49 + src/libkwineffects/kwinoffscreeneffect.cpp | 368 ++ src/libkwineffects/kwinoffscreeneffect.h | 116 + src/libkwineffects/kwinoffscreenquickview.cpp | 714 +++ src/libkwineffects/kwinoffscreenquickview.h | 199 + src/libkwineffects/kwinquickeffect.cpp | 535 ++ src/libkwineffects/kwinquickeffect.h | 169 + src/libkwineffects/logging.cpp | 11 + src/libkwineffects/logging_p.h | 18 + src/libkwineffects/sharedqmlengine.cpp | 34 + src/libkwineffects/sharedqmlengine.h | 27 + src/linux_dmabuf.cpp | 53 + src/linux_dmabuf.h | 37 + src/main.cpp | 596 ++ src/main.h | 319 ++ src/main_wayland.cpp | 610 ++ src/main_wayland.h | 83 + src/main_x11.cpp | 410 ++ src/main_x11.h | 47 + src/modifier_only_shortcuts.cpp | 96 + src/modifier_only_shortcuts.h | 46 + src/mousebuttons.cpp | 52 + src/mousebuttons.h | 17 + src/moving_client_x11_filter.cpp | 51 + src/moving_client_x11_filter.h | 26 + src/netinfo.cpp | 321 ++ src/netinfo.h | 84 + src/onscreennotification.cpp | 232 + src/onscreennotification.h | 81 + src/options.cpp | 1092 ++++ src/options.h | 1058 ++++ src/org.kde.KWin.Plugins.xml | 32 + src/org.kde.KWin.Session.xml | 27 + src/org.kde.KWin.VirtualDesktopManager.xml | 50 + src/org.kde.KWin.xml | 62 + src/org.kde.kappmenu.xml | 28 + src/org.kde.kwin.Compositing.xml | 19 + src/org.kde.kwin.Effects.xml | 43 + src/osd.cpp | 68 + src/osd.h | 31 + src/outline.cpp | 189 + src/outline.h | 149 + src/placeholderinputeventfilter.cpp | 57 + src/placeholderinputeventfilter.h | 27 + src/placeholderoutput.cpp | 42 + src/placeholderoutput.h | 28 + src/placement.cpp | 979 ++++ src/placement.h | 78 + src/placementtracker.cpp | 197 + src/placementtracker.h | 68 + src/platformsupport/CMakeLists.txt | 2 + src/platformsupport/scenes/CMakeLists.txt | 2 + .../scenes/opengl/CMakeLists.txt | 12 + .../scenes/opengl/abstract_egl_backend.cpp | 480 ++ .../scenes/opengl/abstract_egl_backend.h | 114 + .../basiceglsurfacetexture_internal.cpp | 91 + .../opengl/basiceglsurfacetexture_internal.h | 27 + .../opengl/basiceglsurfacetexture_wayland.cpp | 215 + .../opengl/basiceglsurfacetexture_wayland.h | 57 + .../scenes/opengl/egl_dmabuf.cpp | 377 ++ .../scenes/opengl/egl_dmabuf.h | 82 + .../scenes/opengl/kwineglext.h | 65 + .../scenes/opengl/openglbackend.cpp | 116 + .../scenes/opengl/openglbackend.h | 212 + .../scenes/opengl/openglsurfacetexture.cpp | 37 + .../scenes/opengl/openglsurfacetexture.h | 36 + .../opengl/openglsurfacetexture_internal.cpp | 18 + .../opengl/openglsurfacetexture_internal.h | 25 + .../opengl/openglsurfacetexture_wayland.cpp | 18 + .../opengl/openglsurfacetexture_wayland.h | 25 + .../opengl/openglsurfacetexture_x11.cpp | 18 + .../scenes/opengl/openglsurfacetexture_x11.h | 25 + .../scenes/qpainter/CMakeLists.txt | 7 + .../scenes/qpainter/qpainterbackend.cpp | 49 + .../scenes/qpainter/qpainterbackend.h | 72 + .../qpainter/qpaintersurfacetexture.cpp | 32 + .../scenes/qpainter/qpaintersurfacetexture.h | 36 + .../qpaintersurfacetexture_internal.cpp | 32 + .../qpaintersurfacetexture_internal.h | 28 + .../qpaintersurfacetexture_wayland.cpp | 55 + .../qpainter/qpaintersurfacetexture_wayland.h | 28 + .../vsyncconvenience/CMakeLists.txt | 6 + .../vsyncconvenience/softwarevsyncmonitor.cpp | 58 + .../vsyncconvenience/softwarevsyncmonitor.h | 47 + .../vsyncconvenience/vsyncmonitor.cpp | 14 + .../vsyncconvenience/vsyncmonitor.h | 36 + src/plugin.cpp | 16 + src/plugin.h | 48 + src/pluginmanager.cpp | 180 + src/pluginmanager.h | 51 + src/plugins/CMakeLists.txt | 17 + src/plugins/buttonrebinds/CMakeLists.txt | 18 + .../buttonrebinds/buttonrebindsfilter.cpp | 234 + .../buttonrebinds/buttonrebindsfilter.h | 70 + src/plugins/buttonrebinds/main.cpp | 39 + src/plugins/buttonrebinds/metadata.json | 6 + src/plugins/colord-integration/CMakeLists.txt | 39 + .../colord-integration/colorddevice.cpp | 63 + src/plugins/colord-integration/colorddevice.h | 38 + .../colord-integration/colordintegration.cpp | 133 + .../colord-integration/colordintegration.h | 40 + src/plugins/colord-integration/colordtypes.h | 14 + src/plugins/colord-integration/main.cpp | 39 + src/plugins/colord-integration/metadata.json | 6 + .../org.freedesktop.ColorManager.Device.xml | 40 + .../org.freedesktop.ColorManager.Profile.xml | 26 + .../org.freedesktop.ColorManager.xml | 125 + src/plugins/idletime/CMakeLists.txt | 10 + src/plugins/idletime/kwin.json | 3 + src/plugins/idletime/poller.cpp | 89 + src/plugins/idletime/poller.h | 50 + src/plugins/kdecorations/CMakeLists.txt | 2 + src/plugins/kdecorations/Messages.sh | 4 + src/plugins/kdecorations/aurorae/AUTHORS | 1 + .../kdecorations/aurorae/CMakeLists.txt | 4 + src/plugins/kdecorations/aurorae/README | 6 + src/plugins/kdecorations/aurorae/TODO | 3 + .../kdecorations/aurorae/src/CMakeLists.txt | 73 + .../kdecorations/aurorae/src/aurorae.cpp | 792 +++ .../kdecorations/aurorae/src/aurorae.h | 122 + .../kdecorations/aurorae/src/aurorae.json | 21 + .../aurorae/src/aurorae.knsrc.cmake | 47 + .../kdecorations/aurorae/src/colorhelper.cpp | 51 + .../kdecorations/aurorae/src/colorhelper.h | 230 + .../aurorae/src/decorationoptions.cpp | 254 + .../aurorae/src/decorationoptions.h | 324 ++ .../aurorae/src/decorationplugin.cpp | 17 + .../aurorae/src/decorationplugin.h | 18 + .../aurorae/src/kwindecoration.desktop | 65 + .../aurorae/src/lib/auroraetheme.cpp | 499 ++ .../aurorae/src/lib/auroraetheme.h | 218 + .../aurorae/src/lib/themeconfig.cpp | 186 + .../aurorae/src/lib/themeconfig.h | 500 ++ .../aurorae/src/qml/AppMenuButton.qml | 18 + .../aurorae/src/qml/AuroraeButton.qml | 204 + .../aurorae/src/qml/AuroraeButtonGroup.qml | 49 + .../aurorae/src/qml/AuroraeMaximizeButton.qml | 58 + .../aurorae/src/qml/ButtonGroup.qml | 90 + .../aurorae/src/qml/Decoration.qml | 25 + .../aurorae/src/qml/DecorationButton.qml | 114 + .../aurorae/src/qml/MenuButton.qml | 70 + .../kdecorations/aurorae/src/qml/aurorae.qml | 233 + .../kdecorations/aurorae/src/qml/qmldir | 8 + .../kdecorations/aurorae/theme-description | 166 + .../aurorae/themes/CMakeLists.txt | 1 + .../aurorae/themes/plastik/CMakeLists.txt | 10 + .../themes/plastik/code/CMakeLists.txt | 11 + .../themes/plastik/code/plastikbutton.cpp | 451 ++ .../themes/plastik/code/plastikbutton.h | 73 + .../themes/plastik/code/plastikplugin.cpp | 21 + .../themes/plastik/code/plastikplugin.h | 20 + .../aurorae/themes/plastik/code/qmldir | 2 + .../plastik/package/contents/config/main.xml | 25 + .../package/contents/ui/PlastikButton.qml | 153 + .../plastik/package/contents/ui/config.ui | 88 + .../plastik/package/contents/ui/main.qml | 418 ++ .../themes/plastik/package/metadata.desktop | 153 + src/plugins/kglobalaccel/CMakeLists.txt | 7 + .../kglobalaccel/kglobalaccel_plugin.cpp | 55 + .../kglobalaccel/kglobalaccel_plugin.h | 38 + src/plugins/kglobalaccel/kwin.json | 3 + src/plugins/kpackage/CMakeLists.txt | 5 + src/plugins/kpackage/aurorae/CMakeLists.txt | 9 + src/plugins/kpackage/aurorae/aurorae.cpp | 71 + src/plugins/kpackage/aurorae/aurorae.h | 22 + .../kwin-packagestructure-aurorae.json | 86 + .../kpackage/decoration/CMakeLists.txt | 9 + .../kpackage/decoration/decoration.cpp | 48 + src/plugins/kpackage/decoration/decoration.h | 22 + .../kwin-packagestructure-decoration.json | 87 + src/plugins/kpackage/effect/CMakeLists.txt | 9 + src/plugins/kpackage/effect/effect.cpp | 50 + src/plugins/kpackage/effect/effect.h | 20 + .../effect/kwin-packagestructure-effect.json | 87 + src/plugins/kpackage/scripts/CMakeLists.txt | 9 + .../kwin-packagestructure-scripts.json | 87 + src/plugins/kpackage/scripts/scripts.cpp | 48 + src/plugins/kpackage/scripts/scripts.h | 22 + .../kpackage/windowswitcher/CMakeLists.txt | 9 + .../kwin-packagestructure-windowswitcher.json | 87 + .../windowswitcher/windowswitcher.cpp | 48 + .../kpackage/windowswitcher/windowswitcher.h | 22 + .../krunner-integration/CMakeLists.txt | 12 + src/plugins/krunner-integration/dbusutils_p.h | 134 + .../kwin-runner-windows.desktop | 109 + src/plugins/krunner-integration/main.cpp | 31 + src/plugins/krunner-integration/metadata.json | 6 + .../krunner-integration/org.kde.krunner1.xml | 55 + .../windowsrunnerinterface.cpp | 347 ++ .../windowsrunnerinterface.h | 65 + src/plugins/nightcolor/CMakeLists.txt | 32 + src/plugins/nightcolor/clockskewnotifier.cpp | 78 + src/plugins/nightcolor/clockskewnotifier.h | 63 + .../nightcolor/clockskewnotifierengine.cpp | 29 + .../clockskewnotifierengine_linux.cpp | 56 + .../clockskewnotifierengine_linux.h | 31 + .../nightcolor/clockskewnotifierengine_p.h | 28 + src/plugins/nightcolor/constants.h | 23 + src/plugins/nightcolor/main.cpp | 30 + src/plugins/nightcolor/metadata.json | 6 + .../nightcolor/nightcolordbusinterface.cpp | 301 + .../nightcolor/nightcolordbusinterface.h | 93 + src/plugins/nightcolor/nightcolormanager.cpp | 743 +++ src/plugins/nightcolor/nightcolormanager.h | 312 ++ .../nightcolor/nightcolorsettings.kcfg | 48 + .../nightcolor/nightcolorsettings.kcfgc | 8 + .../nightcolor/org.kde.kwin.ColorCorrect.xml | 121 + src/plugins/nightcolor/suncalc.cpp | 162 + src/plugins/nightcolor/suncalc.h | 31 + src/plugins/qpa/CMakeLists.txt | 37 + src/plugins/qpa/backingstore.cpp | 81 + src/plugins/qpa/backingstore.h | 40 + src/plugins/qpa/eglhelpers.cpp | 124 + src/plugins/qpa/eglhelpers.h | 27 + src/plugins/qpa/eglplatformcontext.cpp | 316 ++ src/plugins/qpa/eglplatformcontext.h | 51 + src/plugins/qpa/integration.cpp | 215 + src/plugins/qpa/integration.h | 71 + src/plugins/qpa/kwin.json | 3 + src/plugins/qpa/main.cpp | 37 + src/plugins/qpa/offscreensurface.cpp | 70 + src/plugins/qpa/offscreensurface.h | 40 + src/plugins/qpa/platformcursor.cpp | 42 + src/plugins/qpa/platformcursor.h | 32 + src/plugins/qpa/screen.cpp | 120 + src/plugins/qpa/screen.h | 61 + src/plugins/qpa/window.cpp | 164 + src/plugins/qpa/window.h | 65 + src/plugins/screencast/CMakeLists.txt | 22 + src/plugins/screencast/eglnativefence.cpp | 50 + src/plugins/screencast/eglnativefence.h | 33 + src/plugins/screencast/main.cpp | 39 + src/plugins/screencast/metadata.json | 6 + .../screencast/outputscreencastsource.cpp | 71 + .../screencast/outputscreencastsource.h | 36 + src/plugins/screencast/pipewirecore.cpp | 111 + src/plugins/screencast/pipewirecore.h | 46 + .../screencast/regionscreencastsource.cpp | 114 + .../screencast/regionscreencastsource.h | 51 + src/plugins/screencast/screencastmanager.cpp | 224 + src/plugins/screencast/screencastmanager.h | 51 + src/plugins/screencast/screencastsource.cpp | 17 + src/plugins/screencast/screencastsource.h | 34 + src/plugins/screencast/screencaststream.cpp | 750 +++ src/plugins/screencast/screencaststream.h | 131 + src/plugins/screencast/screencastutils.h | 58 + .../screencast/windowscreencastsource.cpp | 73 + .../screencast/windowscreencastsource.h | 36 + src/plugins/windowsystem/CMakeLists.txt | 10 + src/plugins/windowsystem/kwindowsystem.json | 3 + src/plugins/windowsystem/plugin.cpp | 38 + src/plugins/windowsystem/plugin.h | 24 + src/plugins/windowsystem/windoweffects.cpp | 143 + src/plugins/windowsystem/windoweffects.h | 37 + src/plugins/windowsystem/windowshadow.cpp | 81 + src/plugins/windowsystem/windowshadow.h | 28 + src/plugins/windowsystem/windowsystem.cpp | 348 ++ src/plugins/windowsystem/windowsystem.h | 75 + src/pointer_input.cpp | 1440 +++++ src/pointer_input.h | 300 + src/popup_input_filter.cpp | 125 + src/popup_input_filter.h | 38 + src/qml/CMakeLists.txt | 3 + src/qml/frames/plasma/frame_none.qml | 39 + src/qml/frames/plasma/frame_styled.qml | 60 + src/qml/frames/plasma/frame_unstyled.qml | 53 + .../plasma/dummydata/osd.qml | 7 + src/qml/onscreennotification/plasma/main.qml | 34 + src/qml/outline/plasma/outline.qml | 121 + src/rootinfo_filter.cpp | 36 + src/rootinfo_filter.h | 31 + src/rulebooksettings.cpp | 181 + src/rulebooksettings.h | 54 + src/rulebooksettingsbase.kcfg | 17 + src/rulebooksettingsbase.kcfgc | 5 + src/rules.cpp | 1078 ++++ src/rules.h | 392 ++ src/rulesettings.kcfg | 435 ++ src/rulesettings.kcfgc | 7 + src/scene.cpp | 640 +++ src/scene.h | 246 + src/scenes/CMakeLists.txt | 2 + src/scenes/opengl/CMakeLists.txt | 3 + src/scenes/opengl/scene_opengl.cpp | 925 ++++ src/scenes/opengl/scene_opengl.h | 155 + src/scenes/qpainter/CMakeLists.txt | 3 + src/scenes/qpainter/scene_qpainter.cpp | 297 + src/scenes/qpainter/scene_qpainter.h | 103 + src/screenedge.cpp | 1582 ++++++ src/screenedge.h | 617 +++ src/screenlockerwatcher.cpp | 88 + src/screenlockerwatcher.h | 49 + src/screens.cpp | 67 + src/screens.h | 72 + src/scripting/CMakeLists.txt | 16 + src/scripting/Messages.sh | 2 + src/scripting/dbuscall.cpp | 48 + src/scripting/dbuscall.h | 137 + src/scripting/desktopbackgrounditem.cpp | 126 + src/scripting/desktopbackgrounditem.h | 59 + src/scripting/documentation-effect-global.xml | 197 + src/scripting/documentation-global.xml | 144 + src/scripting/genericscriptedconfig.cpp | 170 + src/scripting/genericscriptedconfig.h | 88 + src/scripting/genericscriptedconfig.json | 7 + src/scripting/kwinscript.desktop | 72 + src/scripting/org.kde.kwin.Script.xml | 10 + src/scripting/screenedgeitem.cpp | 110 + src/scripting/screenedgeitem.h | 117 + src/scripting/scriptedeffect.cpp | 883 +++ src/scripting/scriptedeffect.h | 226 + src/scripting/scripting.cpp | 895 +++ src/scripting/scripting.h | 402 ++ src/scripting/scripting_logging.cpp | 10 + src/scripting/scripting_logging.h | 15 + src/scripting/scriptingutils.cpp | 76 + src/scripting/scriptingutils.h | 22 + src/scripting/v2/CMakeLists.txt | 6 + src/scripting/v2/clientmodel.cpp | 913 +++ src/scripting/v2/clientmodel.h | 372 ++ src/scripting/v2/qml/DesktopThumbnailItem.qml | 41 + src/scripting/v2/qml/qmldir | 7 + src/scripting/v3/clientmodel.cpp | 333 ++ src/scripting/v3/clientmodel.h | 128 + src/scripting/v3/virtualdesktopmodel.cpp | 91 + src/scripting/v3/virtualdesktopmodel.h | 50 + src/scripting/windowthumbnailitem.cpp | 453 ++ src/scripting/windowthumbnailitem.h | 105 + src/scripting/workspace_wrapper.cpp | 497 ++ src/scripting/workspace_wrapper.h | 460 ++ src/scripts/CMakeLists.txt | 12 + src/scripts/Messages.sh | 4 + .../desktopchangeosd/contents/ui/main.qml | 26 + .../desktopchangeosd/contents/ui/osd.qml | 302 + src/scripts/desktopchangeosd/metadata.desktop | 121 + src/scripts/minimizeall/contents/code/main.js | 82 + src/scripts/minimizeall/metadata.desktop | 108 + .../contents/code/main.js | 23 + .../synchronizeskipswitcher/metadata.desktop | 116 + src/scripts/videowall/contents/code/main.js | 35 + .../videowall/contents/config/main.xml | 22 + src/scripts/videowall/contents/ui/config.ui | 148 + src/scripts/videowall/metadata.desktop | 121 + src/settings.kcfgc | 6 + src/shadow.cpp | 382 ++ src/shadow.h | 160 + src/shadowitem.cpp | 291 + src/shadowitem.h | 44 + src/shortcutdialog.ui | 104 + src/sm.cpp | 403 ++ src/sm.h | 114 + src/surfaceitem.cpp | 199 + src/surfaceitem.h | 100 + src/surfaceitem_internal.cpp | 90 + src/surfaceitem_internal.h | 57 + src/surfaceitem_wayland.cpp | 214 + src/surfaceitem_wayland.h | 93 + src/surfaceitem_x11.cpp | 229 + src/surfaceitem_x11.h | 71 + src/syncalarmx11filter.cpp | 36 + src/syncalarmx11filter.h | 25 + src/tabbox/CMakeLists.txt | 3 + src/tabbox/clientmodel.cpp | 283 + src/tabbox/clientmodel.h | 108 + src/tabbox/desktopchain.cpp | 131 + src/tabbox/desktopchain.h | 137 + src/tabbox/desktopmodel.cpp | 180 + src/tabbox/desktopmodel.h | 80 + src/tabbox/kwindesktopswitcher.desktop | 65 + src/tabbox/kwinwindowswitcher.desktop | 67 + src/tabbox/switcheritem.cpp | 114 + src/tabbox/switcheritem.h | 111 + src/tabbox/tabbox.cpp | 1571 ++++++ src/tabbox/tabbox.h | 363 ++ src/tabbox/tabbox_logging.cpp | 10 + src/tabbox/tabbox_logging.h | 15 + src/tabbox/tabboxconfig.cpp | 213 + src/tabbox/tabboxconfig.h | 351 ++ src/tabbox/tabboxhandler.cpp | 680 +++ src/tabbox/tabboxhandler.h | 391 ++ src/tabbox/x11_filter.cpp | 149 + src/tabbox/x11_filter.h | 36 + src/tablet_input.cpp | 186 + src/tablet_input.h | 80 + src/tabletmodemanager.cpp | 228 + src/tabletmodemanager.h | 63 + src/touch_input.cpp | 211 + src/touch_input.h | 91 + src/unmanaged.cpp | 247 + src/unmanaged.h | 83 + src/useractions.cpp | 1887 +++++++ src/useractions.h | 244 + src/utils/CMakeLists.txt | 13 + ...tract_opengl_context_attribute_builder.cpp | 30 + ...bstract_opengl_context_attribute_builder.h | 132 + src/utils/c_ptr.h | 30 + src/utils/common.cpp | 259 + src/utils/common.h | 163 + src/utils/damagejournal.h | 89 + src/utils/edid.cpp | 244 + src/utils/edid.h | 88 + src/utils/egl_context_attribute_builder.cpp | 79 + src/utils/egl_context_attribute_builder.h | 28 + src/utils/filedescriptor.cpp | 61 + src/utils/filedescriptor.h | 33 + src/utils/ramfile.cpp | 163 + src/utils/ramfile.h | 117 + src/utils/realtime.cpp | 26 + src/utils/realtime.h | 19 + src/utils/serviceutils.h | 67 + src/utils/subsurfacemonitor.cpp | 89 + src/utils/subsurfacemonitor.h | 80 + src/utils/udev.cpp | 283 + src/utils/udev.h | 104 + src/utils/xcbutils.cpp | 656 +++ src/utils/xcbutils.h | 2085 +++++++ src/utils/xcursortheme.cpp | 231 + src/utils/xcursortheme.h | 123 + src/virtualdesktops.cpp | 980 ++++ src/virtualdesktops.h | 756 +++ src/virtualdesktopsdbustypes.cpp | 58 + src/virtualdesktopsdbustypes.h | 37 + src/virtualkeyboard_dbus.cpp | 75 + src/virtualkeyboard_dbus.h | 52 + src/was_user_interaction_x11_filter.cpp | 27 + src/was_user_interaction_x11_filter.h | 26 + src/wayland/CMakeLists.txt | 261 + src/wayland/DESIGN.md | 84 + src/wayland/abstract_data_source.cpp | 14 + src/wayland/abstract_data_source.h | 96 + src/wayland/abstract_drop_handler.cpp | 17 + src/wayland/abstract_drop_handler.h | 26 + src/wayland/appmenu_interface.cpp | 142 + src/wayland/appmenu_interface.h | 97 + src/wayland/autotests/CMakeLists.txt | 8 + src/wayland/autotests/client/CMakeLists.txt | 328 ++ .../autotests/client/test_datadevice.cpp | 565 ++ .../autotests/client/test_datasource.cpp | 262 + .../autotests/client/test_drag_drop.cpp | 536 ++ src/wayland/autotests/client/test_error.cpp | 138 + .../autotests/client/test_fake_input.cpp | 448 ++ .../client/test_plasma_activities.cpp | 196 + .../client/test_plasma_virtual_desktop.cpp | 520 ++ .../autotests/client/test_plasmashell.cpp | 489 ++ .../client/test_pointer_constraints.cpp | 422 ++ .../autotests/client/test_selection.cpp | 247 + .../client/test_server_side_decoration.cpp | 294 + .../test_server_side_decoration_palette.cpp | 168 + src/wayland/autotests/client/test_shadow.cpp | 265 + .../autotests/client/test_shm_pool.cpp | 209 + .../autotests/client/test_text_input_v2.cpp | 769 +++ .../autotests/client/test_wayland_appmenu.cpp | 172 + .../autotests/client/test_wayland_blur.cpp | 188 + .../client/test_wayland_contrast.cpp | 200 + .../autotests/client/test_wayland_filter.cpp | 149 + .../autotests/client/test_wayland_output.cpp | 391 ++ .../autotests/client/test_wayland_seat.cpp | 2018 +++++++ .../autotests/client/test_wayland_slide.cpp | 188 + .../client/test_wayland_subsurface.cpp | 1042 ++++ .../autotests/client/test_wayland_surface.cpp | 1079 ++++ .../client/test_wayland_windowmanagement.cpp | 599 ++ .../autotests/client/test_xdg_decoration.cpp | 227 + .../autotests/client/test_xdg_foreign.cpp | 356 ++ .../autotests/client/test_xdg_output.cpp | 171 + .../autotests/client/test_xdg_shell.cpp | 593 ++ src/wayland/autotests/server/CMakeLists.txt | 186 + .../server/test_datacontrol_interface.cpp | 386 ++ src/wayland/autotests/server/test_display.cpp | 182 + .../server/test_inputmethod_interface.cpp | 627 +++ ...keyboard_shortcuts_inhibitor_interface.cpp | 211 + .../server/test_layershellv1_interface.cpp | 503 ++ .../server/test_no_xdg_runtime_dir.cpp | 41 + .../autotests/server/test_screencast.cpp | 184 + src/wayland/autotests/server/test_seat.cpp | 206 + .../server/test_tablet_interface.cpp | 359 ++ .../server/test_textinputv3_interface.cpp | 655 +++ .../server/test_viewporter_interface.cpp | 191 + src/wayland/blur_interface.cpp | 149 + src/wayland/blur_interface.h | 76 + src/wayland/clientbuffer.cpp | 84 + src/wayland/clientbuffer.h | 77 + src/wayland/clientbuffer_p.h | 25 + src/wayland/clientbufferintegration.cpp | 40 + src/wayland/clientbufferintegration.h | 33 + src/wayland/clientconnection.cpp | 160 + src/wayland/clientconnection.h | 148 + src/wayland/compositor_interface.cpp | 73 + src/wayland/compositor_interface.h | 48 + src/wayland/contrast_interface.cpp | 218 + src/wayland/contrast_interface.h | 78 + .../datacontroldevice_v1_interface.cpp | 175 + src/wayland/datacontroldevice_v1_interface.h | 62 + .../datacontroldevicemanager_v1_interface.cpp | 78 + .../datacontroldevicemanager_v1_interface.h | 42 + src/wayland/datacontroloffer_v1_interface.cpp | 86 + src/wayland/datacontroloffer_v1_interface.h | 47 + .../datacontrolsource_v1_interface.cpp | 94 + src/wayland/datacontrolsource_v1_interface.h | 46 + src/wayland/datadevice_interface.cpp | 343 ++ src/wayland/datadevice_interface.h | 118 + src/wayland/datadevice_interface_p.h | 56 + src/wayland/datadevicemanager_interface.cpp | 76 + src/wayland/datadevicemanager_interface.h | 55 + src/wayland/dataoffer_interface.cpp | 211 + src/wayland/dataoffer_interface.h | 68 + src/wayland/datasource_interface.cpp | 210 + src/wayland/datasource_interface.h | 61 + src/wayland/display.cpp | 300 + src/wayland/display.h | 163 + src/wayland/display_p.h | 60 + src/wayland/dpms_interface.cpp | 171 + src/wayland/dpms_interface.h | 63 + src/wayland/drmclientbuffer.cpp | 94 + src/wayland/drmclientbuffer.h | 51 + src/wayland/drmleasedevice_v1_interface.cpp | 407 ++ src/wayland/drmleasedevice_v1_interface.h | 135 + src/wayland/drmleasedevice_v1_interface_p.h | 108 + src/wayland/fakeinput_interface.cpp | 264 + src/wayland/fakeinput_interface.h | 156 + src/wayland/filtered_display.cpp | 52 + src/wayland/filtered_display.h | 41 + src/wayland/idle_interface.cpp | 76 + src/wayland/idle_interface.h | 49 + src/wayland/idle_interface_p.h | 43 + src/wayland/idleinhibit_v1_interface.cpp | 71 + src/wayland/idleinhibit_v1_interface.h | 36 + src/wayland/idleinhibit_v1_interface_p.h | 38 + src/wayland/inputmethod_v1_interface.cpp | 461 ++ src/wayland/inputmethod_v1_interface.h | 172 + src/wayland/keyboard_interface.cpp | 240 + src/wayland/keyboard_interface.h | 70 + src/wayland/keyboard_interface_p.h | 69 + ...eyboard_shortcuts_inhibit_v1_interface.cpp | 190 + .../keyboard_shortcuts_inhibit_v1_interface.h | 78 + src/wayland/keystate_interface.cpp | 56 + src/wayland/keystate_interface.h | 34 + src/wayland/layershell_v1_interface.cpp | 469 ++ src/wayland/layershell_v1_interface.h | 189 + src/wayland/linuxdmabufv1clientbuffer.cpp | 511 ++ src/wayland/linuxdmabufv1clientbuffer.h | 137 + src/wayland/linuxdmabufv1clientbuffer_p.h | 114 + .../lockscreen_overlay_v1_interface.cpp | 54 + src/wayland/lockscreen_overlay_v1_interface.h | 43 + src/wayland/output_interface.cpp | 412 ++ src/wayland/output_interface.h | 124 + src/wayland/outputdevice_v2_interface.cpp | 654 +++ src/wayland/outputdevice_v2_interface.h | 104 + src/wayland/outputmanagement_v2_interface.cpp | 291 + src/wayland/outputmanagement_v2_interface.h | 44 + src/wayland/plasmashell_interface.cpp | 342 ++ src/wayland/plasmashell_interface.h | 234 + .../plasmavirtualdesktop_interface.cpp | 326 ++ src/wayland/plasmavirtualdesktop_interface.h | 150 + .../plasmawindowmanagement_interface.cpp | 1106 ++++ .../plasmawindowmanagement_interface.h | 304 + src/wayland/pointer_interface.cpp | 363 ++ src/wayland/pointer_interface.h | 122 + src/wayland/pointer_interface_p.h | 57 + .../pointerconstraints_v1_interface.cpp | 321 ++ src/wayland/pointerconstraints_v1_interface.h | 245 + .../pointerconstraints_v1_interface_p.h | 91 + src/wayland/pointergestures_v1_interface.cpp | 326 ++ src/wayland/pointergestures_v1_interface.h | 38 + src/wayland/pointergestures_v1_interface_p.h | 92 + src/wayland/primaryoutput_v1_interface.cpp | 63 + src/wayland/primaryoutput_v1_interface.h | 38 + .../primaryselectiondevice_v1_interface.cpp | 145 + .../primaryselectiondevice_v1_interface.h | 61 + ...aryselectiondevicemanager_v1_interface.cpp | 77 + ...imaryselectiondevicemanager_v1_interface.h | 40 + .../primaryselectionoffer_v1_interface.cpp | 87 + .../primaryselectionoffer_v1_interface.h | 45 + .../primaryselectionsource_v1_interface.cpp | 94 + .../primaryselectionsource_v1_interface.h | 44 + src/wayland/protocols/README.md | 1 + .../wlr-data-control-unstable-v1.xml | 278 + .../protocols/wlr-layer-shell-unstable-v1.xml | 325 ++ src/wayland/region_interface.cpp | 47 + src/wayland/region_interface_p.h | 33 + src/wayland/relativepointer_v1_interface.cpp | 96 + src/wayland/relativepointer_v1_interface.h | 39 + src/wayland/relativepointer_v1_interface_p.h | 45 + src/wayland/screencast_v1_interface.cpp | 146 + src/wayland/screencast_v1_interface.h | 68 + src/wayland/seat_interface.cpp | 1396 +++++ src/wayland/seat_interface.h | 729 +++ src/wayland/seat_interface_p.h | 156 + src/wayland/server_decoration_interface.cpp | 236 + src/wayland/server_decoration_interface.h | 132 + .../server_decoration_palette_interface.cpp | 145 + .../server_decoration_palette_interface.h | 85 + src/wayland/shadow_interface.cpp | 349 ++ src/wayland/shadow_interface.h | 61 + src/wayland/shmclientbuffer.cpp | 199 + src/wayland/shmclientbuffer.h | 50 + src/wayland/slide_interface.cpp | 157 + src/wayland/slide_interface.h | 65 + src/wayland/subcompositor_interface.cpp | 264 + src/wayland/subcompositor_interface.h | 126 + src/wayland/subsurface_interface_p.h | 60 + src/wayland/surface_interface.cpp | 1095 ++++ src/wayland/surface_interface.h | 436 ++ src/wayland/surface_interface_p.h | 158 + src/wayland/surfacerole.cpp | 45 + src/wayland/surfacerole_p.h | 38 + src/wayland/tablet_v2_interface.cpp | 856 +++ src/wayland/tablet_v2_interface.h | 295 + src/wayland/tests/CMakeLists.txt | 65 + src/wayland/tests/copyclient.cpp | 171 + src/wayland/tests/dpmstest.cpp | 167 + src/wayland/tests/fakeoutput.cpp | 71 + src/wayland/tests/fakeoutput.h | 28 + src/wayland/tests/paneltest.cpp | 295 + src/wayland/tests/pasteclient.cpp | 178 + src/wayland/tests/plasmasurfacetest.cpp | 200 + src/wayland/tests/renderingservertest.cpp | 287 + src/wayland/tests/shadowtest.cpp | 161 + src/wayland/tests/subsurfacetest.cpp | 184 + src/wayland/tests/touchclienttest.cpp | 205 + src/wayland/tests/touchclienttest.h | 49 + src/wayland/tests/waylandservertest.cpp | 114 + src/wayland/tests/xdgforeigntest.cpp | 183 + src/wayland/tests/xdgtest.cpp | 205 + src/wayland/textinput.cpp | 7 + src/wayland/textinput.h | 157 + src/wayland/textinput_v2_interface.cpp | 568 ++ src/wayland/textinput_v2_interface.h | 293 + src/wayland/textinput_v2_interface_p.h | 86 + src/wayland/textinput_v3_interface.cpp | 508 ++ src/wayland/textinput_v3_interface.h | 210 + src/wayland/textinput_v3_interface_p.h | 106 + src/wayland/tools/.clang-format | 2 + src/wayland/tools/CMakeLists.txt | 84 + src/wayland/tools/README.md | 7 + src/wayland/tools/qtwaylandscanner.cpp | 1405 +++++ src/wayland/touch_interface.cpp | 129 + src/wayland/touch_interface.h | 48 + src/wayland/touch_interface_p.h | 34 + src/wayland/utils.h | 56 + src/wayland/utils/executable_path.h | 11 + src/wayland/utils/executable_path_proc.cpp | 14 + src/wayland/utils/executable_path_sysctl.cpp | 22 + src/wayland/viewporter_interface.cpp | 147 + src/wayland/viewporter_interface.h | 39 + src/wayland/viewporter_interface_p.h | 34 + src/wayland/xdgactivation_v1_interface.cpp | 141 + src/wayland/xdgactivation_v1_interface.h | 51 + src/wayland/xdgdecoration_v1_interface.cpp | 146 + src/wayland/xdgdecoration_v1_interface.h | 115 + src/wayland/xdgdecoration_v1_interface_p.h | 43 + src/wayland/xdgforeign_v2_interface.cpp | 269 + src/wayland/xdgforeign_v2_interface.h | 60 + src/wayland/xdgforeign_v2_interface_p.h | 125 + src/wayland/xdgoutput_v1_interface.cpp | 267 + src/wayland/xdgoutput_v1_interface.h | 127 + src/wayland/xdgshell_interface.cpp | 1023 ++++ src/wayland/xdgshell_interface.h | 589 ++ src/wayland/xdgshell_interface_p.h | 191 + src/wayland_server.cpp | 788 +++ src/wayland_server.h | 303 + src/waylandoutput.cpp | 73 + src/waylandoutput.h | 37 + src/waylandshellintegration.cpp | 17 + src/waylandshellintegration.h | 25 + src/waylandwindow.cpp | 333 ++ src/waylandwindow.h | 62 + src/window.cpp | 4512 +++++++++++++++ src/window.h | 2376 ++++++++ src/window_property_notify_x11_filter.cpp | 40 + src/window_property_notify_x11_filter.h | 31 + src/windowitem.cpp | 280 + src/windowitem.h | 123 + src/workspace.cpp | 3171 +++++++++++ src/workspace.h | 893 +++ src/x11eventfilter.cpp | 51 + src/x11eventfilter.h | 85 + src/x11syncmanager.cpp | 232 + src/x11syncmanager.h | 80 + src/x11window.cpp | 4887 +++++++++++++++++ src/x11window.h | 649 +++ src/xdgactivationv1.cpp | 150 + src/xdgactivationv1.h | 58 + src/xdgshellintegration.cpp | 78 + src/xdgshellintegration.h | 33 + src/xdgshellwindow.cpp | 2041 +++++++ src/xdgshellwindow.h | 278 + src/xkb.cpp | 640 +++ src/xkb.h | 174 + src/xwayland/CMakeLists.txt | 19 + src/xwayland/clipboard.cpp | 179 + src/xwayland/clipboard.h | 66 + src/xwayland/databridge.cpp | 64 + src/xwayland/databridge.h | 72 + src/xwayland/datasource.cpp | 66 + src/xwayland/datasource.h | 69 + src/xwayland/dnd.cpp | 212 + src/xwayland/dnd.h | 77 + src/xwayland/drag.cpp | 50 + src/xwayland/drag.h | 51 + src/xwayland/drag_wl.cpp | 388 ++ src/xwayland/drag_wl.h | 134 + src/xwayland/drag_x.cpp | 521 ++ src/xwayland/drag_x.h | 151 + src/xwayland/lib/CMakeLists.txt | 18 + src/xwayland/lib/xauthority.cpp | 77 + src/xwayland/lib/xauthority.h | 14 + src/xwayland/lib/xwaylandsocket.cpp | 250 + src/xwayland/lib/xwaylandsocket.h | 40 + src/xwayland/primary.cpp | 183 + src/xwayland/primary.h | 68 + src/xwayland/selection.cpp | 353 ++ src/xwayland/selection.h | 139 + src/xwayland/selection_source.cpp | 287 + src/xwayland/selection_source.h | 153 + src/xwayland/transfer.cpp | 483 ++ src/xwayland/transfer.h | 201 + src/xwayland/xwayland.cpp | 317 ++ src/xwayland/xwayland.h | 93 + src/xwayland/xwayland_interface.h | 56 + src/xwayland/xwaylandlauncher.cpp | 314 ++ src/xwayland/xwaylandlauncher.h | 111 + src/xwayland/xwldrophandler.cpp | 77 + src/xwayland/xwldrophandler.h | 43 + src/xwaylandwindow.cpp | 55 + src/xwaylandwindow.h | 34 + tests/CMakeLists.txt | 63 + tests/cursorhotspottest.cpp | 144 + tests/inputmethodstest.qml | 73 + tests/lockscreenoverlaytest.cpp | 89 + tests/lockscreenoverlaytest.desktop | 9 + tests/normalhintsbasesizetest.cpp | 102 + tests/pointerconstraintstest.cpp | 395 ++ tests/pointerconstraintstest.h | 184 + tests/pointerconstraintstest.qml | 205 + tests/pointergesturestest.cpp | 149 + tests/pointergesturestest.qml | 16 + tests/screenedgeshowtest.cpp | 353 ++ tests/unmapdestroytest.qml | 72 + tests/x11shadowreader.cpp | 121 + tests/xdgactivationtest-qt5.cpp | 90 + tests/xdgactivationtest-qt6.cpp | 27 + 3193 files changed, 1060490 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 .kde-ci.yml create mode 100644 CMakeLists.txt create mode 100644 CONTRIBUTING.md create mode 100644 KWinDBusInterfaceConfig.cmake.in create mode 100644 LICENSES/BSD-3-Clause.txt create mode 100644 LICENSES/CC0-1.0.txt create mode 100644 LICENSES/GPL-2.0-only.txt create mode 100644 LICENSES/GPL-2.0-or-later.txt create mode 100644 LICENSES/GPL-3.0-only.txt create mode 100644 LICENSES/GPL-3.0-or-later.txt create mode 100644 LICENSES/LGPL-2.0-only.txt create mode 100644 LICENSES/LGPL-2.0-or-later.txt create mode 100644 LICENSES/LGPL-2.1-only.txt create mode 100644 LICENSES/LGPL-2.1-or-later.txt create mode 100644 LICENSES/LGPL-3.0-only.txt create mode 100644 LICENSES/LicenseRef-KDE-Accepted-GPL.txt create mode 100644 LICENSES/LicenseRef-KDE-Accepted-LGPL.txt create mode 100644 LICENSES/MIT.txt create mode 100644 Mainpage.dox create mode 100644 README.md create mode 100644 autotests/CMakeLists.txt create mode 100644 autotests/integration/CMakeLists.txt create mode 100644 autotests/integration/activation_test.cpp create mode 100644 autotests/integration/activities_test.cpp create mode 100644 autotests/integration/buffer_size_change_test.cpp create mode 100644 autotests/integration/data/anim-data-delete-effect/effect.js create mode 100644 autotests/integration/data/example.desktop create mode 100644 autotests/integration/data/rules/maximize-vert-apply-initial create mode 100644 autotests/integration/dbus_interface_test.cpp create mode 100644 autotests/integration/debug_console_test.cpp create mode 100644 autotests/integration/decoration_input_test.cpp create mode 100644 autotests/integration/desktop_window_x11_test.cpp create mode 100644 autotests/integration/dont_crash_aurorae_destroy_deco.cpp create mode 100644 autotests/integration/dont_crash_cancel_animation.cpp create mode 100644 autotests/integration/dont_crash_cursor_physical_size_empty.cpp create mode 100644 autotests/integration/dont_crash_empty_deco.cpp create mode 100644 autotests/integration/dont_crash_glxgears.cpp create mode 100644 autotests/integration/dont_crash_no_border.cpp create mode 100644 autotests/integration/dont_crash_reinitialize_compositor.cpp create mode 100644 autotests/integration/dont_crash_useractions_menu.cpp create mode 100644 autotests/integration/effects/CMakeLists.txt create mode 100644 autotests/integration/effects/desktop_switching_animation_test.cpp create mode 100644 autotests/integration/effects/maximize_animation_test.cpp create mode 100644 autotests/integration/effects/minimize_animation_test.cpp create mode 100644 autotests/integration/effects/popup_open_close_animation_test.cpp create mode 100644 autotests/integration/effects/scripted_effects_test.cpp create mode 100644 autotests/integration/effects/scripts/animationTest.js create mode 100644 autotests/integration/effects/scripts/animationTestMulti.js create mode 100644 autotests/integration/effects/scripts/completeTest.js create mode 100644 autotests/integration/effects/scripts/effectContext.js create mode 100644 autotests/integration/effects/scripts/effectsHandler.js create mode 100644 autotests/integration/effects/scripts/fullScreenEffectTest.js create mode 100644 autotests/integration/effects/scripts/fullScreenEffectTestGlobal.js create mode 100644 autotests/integration/effects/scripts/fullScreenEffectTestMulti.js create mode 100644 autotests/integration/effects/scripts/grabAlreadyGrabbedWindowForcedTest_owner.js create mode 100644 autotests/integration/effects/scripts/grabAlreadyGrabbedWindowForcedTest_thief.js create mode 100644 autotests/integration/effects/scripts/grabAlreadyGrabbedWindowTest_grabber.js create mode 100644 autotests/integration/effects/scripts/grabAlreadyGrabbedWindowTest_owner.js create mode 100644 autotests/integration/effects/scripts/grabTest.js create mode 100644 autotests/integration/effects/scripts/keepAliveTest.js create mode 100644 autotests/integration/effects/scripts/keepAliveTestDontKeep.js create mode 100644 autotests/integration/effects/scripts/redirectAnimateDontTerminateTest.js create mode 100644 autotests/integration/effects/scripts/redirectAnimateTerminateTest.js create mode 100644 autotests/integration/effects/scripts/redirectSetDontTerminateTest.js create mode 100644 autotests/integration/effects/scripts/redirectSetTerminateTest.js create mode 100644 autotests/integration/effects/scripts/screenEdgeTest.js create mode 100644 autotests/integration/effects/scripts/screenEdgeTouchTest.js create mode 100644 autotests/integration/effects/scripts/shortcutsTest.js create mode 100644 autotests/integration/effects/scripts/ungrabTest.js create mode 100644 autotests/integration/effects/slidingpopups_test.cpp create mode 100644 autotests/integration/effects/toplevel_open_close_animation_test.cpp create mode 100644 autotests/integration/effects/translucency_test.cpp create mode 100644 autotests/integration/effects/wobbly_shade_test.cpp create mode 100644 autotests/integration/fakes/CMakeLists.txt create mode 100644 autotests/integration/fakes/org.kde.kdecoration2/CMakeLists.txt create mode 100644 autotests/integration/fakes/org.kde.kdecoration2/fakedecoration_with_shadows.cpp create mode 100644 autotests/integration/fakes/org.kde.kdecoration2/fakedecoration_with_shadows.json create mode 100644 autotests/integration/generic_scene_opengl_test.cpp create mode 100644 autotests/integration/generic_scene_opengl_test.h create mode 100644 autotests/integration/globalshortcuts_test.cpp create mode 100644 autotests/integration/helper/CMakeLists.txt create mode 100644 autotests/integration/helper/copy.cpp create mode 100644 autotests/integration/helper/kill.cpp create mode 100644 autotests/integration/helper/paste.cpp create mode 100644 autotests/integration/idle_inhibition_test.cpp create mode 100644 autotests/integration/input_stacking_order.cpp create mode 100644 autotests/integration/inputmethod_test.cpp create mode 100644 autotests/integration/internal_window.cpp create mode 100644 autotests/integration/keyboard_layout_test.cpp create mode 100644 autotests/integration/keymap_creation_failure_test.cpp create mode 100644 autotests/integration/kwin_wayland_test.cpp create mode 100644 autotests/integration/kwin_wayland_test.h create mode 100644 autotests/integration/kwinbindings_test.cpp create mode 100644 autotests/integration/layershellv1window_test.cpp create mode 100644 autotests/integration/lockscreen.cpp create mode 100644 autotests/integration/maximize_test.cpp create mode 100644 autotests/integration/modifier_only_shortcut_test.cpp create mode 100644 autotests/integration/move_resize_window_test.cpp create mode 100644 autotests/integration/nightcolor_test.cpp create mode 100644 autotests/integration/no_global_shortcuts_test.cpp create mode 100644 autotests/integration/outputchanges_test.cpp create mode 100644 autotests/integration/placement_test.cpp create mode 100644 autotests/integration/plasma_surface_test.cpp create mode 100644 autotests/integration/plasmawindow_test.cpp create mode 100644 autotests/integration/platformcursor.cpp create mode 100644 autotests/integration/pointer_constraints_test.cpp create mode 100644 autotests/integration/pointer_input.cpp create mode 100644 autotests/integration/protocols/wlr-layer-shell-unstable-v1.xml create mode 100644 autotests/integration/quick_tiling_test.cpp create mode 100644 autotests/integration/scene_opengl_es_test.cpp create mode 100644 autotests/integration/scene_opengl_test.cpp create mode 100644 autotests/integration/scene_qpainter_test.cpp create mode 100644 autotests/integration/screen_changes_test.cpp create mode 100644 autotests/integration/screenedge_client_show_test.cpp create mode 100644 autotests/integration/screenedges_test.cpp create mode 100644 autotests/integration/screens_test.cpp create mode 100644 autotests/integration/scripting/CMakeLists.txt create mode 100644 autotests/integration/scripting/minimizeall_test.cpp create mode 100644 autotests/integration/scripting/screenedge_test.cpp create mode 100644 autotests/integration/scripting/scripts/screenedge.js create mode 100644 autotests/integration/scripting/scripts/screenedgetouch.qml create mode 100644 autotests/integration/scripting/scripts/screenedgeunregister.js create mode 100644 autotests/integration/scripting/scripts/touchScreenedge.js create mode 100644 autotests/integration/shade_test.cpp create mode 100644 autotests/integration/showing_desktop_test.cpp create mode 100644 autotests/integration/stacking_order_test.cpp create mode 100644 autotests/integration/struts_test.cpp create mode 100644 autotests/integration/tabbox_test.cpp create mode 100644 autotests/integration/test_helpers.cpp create mode 100644 autotests/integration/test_virtualkeyboard_dbus.cpp create mode 100644 autotests/integration/touch_input_test.cpp create mode 100644 autotests/integration/transient_placement.cpp create mode 100644 autotests/integration/virtual_desktop_test.cpp create mode 100644 autotests/integration/window_rules_test.cpp create mode 100644 autotests/integration/window_selection_test.cpp create mode 100644 autotests/integration/x11_window_test.cpp create mode 100644 autotests/integration/xdgshellwindow_rules_test.cpp create mode 100644 autotests/integration/xdgshellwindow_test.cpp create mode 100644 autotests/integration/xwayland_input_test.cpp create mode 100644 autotests/integration/xwayland_selections_test.cpp create mode 100644 autotests/integration/xwaylandserver_crash_test.cpp create mode 100644 autotests/integration/xwaylandserver_restart_test.cpp create mode 100644 autotests/libinput/CMakeLists.txt create mode 100644 autotests/libinput/device_test.cpp create mode 100644 autotests/libinput/gesture_event_test.cpp create mode 100644 autotests/libinput/input_event_test.cpp create mode 100644 autotests/libinput/key_event_test.cpp create mode 100644 autotests/libinput/mock_libinput.cpp create mode 100644 autotests/libinput/mock_libinput.h create mode 100644 autotests/libinput/pointer_event_test.cpp create mode 100644 autotests/libinput/switch_event_test.cpp create mode 100644 autotests/libinput/touch_event_test.cpp create mode 100644 autotests/libkwineffects/CMakeLists.txt create mode 100644 autotests/libkwineffects/data/glplatform/amd-catalyst-radeonhd-7700M-3.1.13399 create mode 100644 autotests/libkwineffects/data/glplatform/amd-gallium-bonaire-3.0 create mode 100644 autotests/libkwineffects/data/glplatform/amd-gallium-cayman-gles-3.0 create mode 100644 autotests/libkwineffects/data/glplatform/amd-gallium-hawaii-3.0 create mode 100644 autotests/libkwineffects/data/glplatform/amd-gallium-navi-4.5 create mode 100644 autotests/libkwineffects/data/glplatform/amd-gallium-radeon-r9-290-4.5 create mode 100644 autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-480-series-4.5 create mode 100644 autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-550-series-3.1 create mode 100644 autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-5700-xt-4.6 create mode 100644 autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-580-series-4.5 create mode 100644 autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-vega-56-4.5 create mode 100644 autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-vega-64-4.5 create mode 100644 autotests/libkwineffects/data/glplatform/amd-gallium-redwood-3.0 create mode 100644 autotests/libkwineffects/data/glplatform/amd-gallium-tonga-4.1 create mode 100644 autotests/libkwineffects/data/glplatform/intel-broadwell-gt2-3.3 create mode 100644 autotests/libkwineffects/data/glplatform/intel-haswell-mobile-3.3 create mode 100644 autotests/libkwineffects/data/glplatform/intel-ivybridge-desktop-3.0 create mode 100644 autotests/libkwineffects/data/glplatform/intel-ivybridge-desktop-3.3 create mode 100644 autotests/libkwineffects/data/glplatform/intel-ivybridge-mobile-3.3 create mode 100644 autotests/libkwineffects/data/glplatform/intel-kabylake-gt2-4.6 create mode 100644 autotests/libkwineffects/data/glplatform/intel-sandybridge-mobile-3.3 create mode 100644 autotests/libkwineffects/data/glplatform/intel-skylake-gt2-3.0 create mode 100644 autotests/libkwineffects/data/glplatform/llvmpipe-10.0 create mode 100644 autotests/libkwineffects/data/glplatform/llvmpipe-3.0 create mode 100644 autotests/libkwineffects/data/glplatform/llvmpipe-5.0 create mode 100644 autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-560-4.5 create mode 100644 autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-660-3.1 create mode 100644 autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-950-4.5 create mode 100644 autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-970-3.1 create mode 100644 autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-970M-3.1 create mode 100644 autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-980-3.1 create mode 100644 autotests/libkwineffects/data/glplatform/panfrost-malit860-desktop-3.0 create mode 100644 autotests/libkwineffects/data/glplatform/qualcomm-adreno-330-libhybris-gles-3.0 create mode 100644 autotests/libkwineffects/data/glplatform/virgl-3.1 create mode 100644 autotests/libkwineffects/kwinglplatformtest.cpp create mode 100644 autotests/libkwineffects/mock_gl.cpp create mode 100644 autotests/libkwineffects/mock_gl.h create mode 100644 autotests/libkwineffects/timelinetest.cpp create mode 100644 autotests/libkwineffects/windowquadlisttest.cpp create mode 100644 autotests/onscreennotificationtest.cpp create mode 100644 autotests/onscreennotificationtest.h create mode 100644 autotests/opengl_context_attribute_builder_test.cpp create mode 100644 autotests/tabbox/CMakeLists.txt create mode 100644 autotests/tabbox/mock_tabboxclient.cpp create mode 100644 autotests/tabbox/mock_tabboxclient.h create mode 100644 autotests/tabbox/mock_tabboxhandler.cpp create mode 100644 autotests/tabbox/mock_tabboxhandler.h create mode 100644 autotests/tabbox/test_desktopchain.cpp create mode 100644 autotests/tabbox/test_tabbox_clientmodel.cpp create mode 100644 autotests/tabbox/test_tabbox_clientmodel.h create mode 100644 autotests/tabbox/test_tabbox_config.cpp create mode 100644 autotests/tabbox/test_tabbox_handler.cpp create mode 100644 autotests/test_client_machine.cpp create mode 100644 autotests/test_ftrace.cpp create mode 100644 autotests/test_gestures.cpp create mode 100644 autotests/test_utils.cpp create mode 100644 autotests/test_virtual_desktops.cpp create mode 100644 autotests/test_window_paint_data.cpp create mode 100644 autotests/test_x11_timestamp_update.cpp create mode 100644 autotests/test_xcb_size_hints.cpp create mode 100644 autotests/test_xcb_window.cpp create mode 100644 autotests/test_xcb_wrapper.cpp create mode 100644 autotests/test_xkb.cpp create mode 100644 autotests/testutils.h create mode 100644 autotests/xcb_scaling_mock.cpp create mode 100644 cmake/modules/FindLibcap.cmake create mode 100644 cmake/modules/FindLibdrm.cmake create mode 100644 cmake/modules/FindXKB.cmake create mode 100644 cmake/modules/FindXwayland.cmake create mode 100644 cmake/modules/Findgbm.cmake create mode 100644 cmake/modules/Findhwdata.cmake create mode 100644 cmake/modules/Findlcms2.cmake create mode 100644 data/CMakeLists.txt create mode 100644 data/icons/16-apps-kwin.png create mode 100644 data/icons/32-apps-kwin.png create mode 100644 data/icons/48-apps-kwin.png create mode 100644 data/icons/CMakeLists.txt create mode 100644 data/icons/sc-apps-kwin.svgz create mode 100644 data/org_kde_kwin.categories create mode 100644 data/update_default_rules.cpp create mode 100644 doc/CMakeLists.txt create mode 100644 doc/TESTING.md create mode 100644 doc/coding-conventions.md create mode 100644 doc/desktop/CMakeLists.txt create mode 100644 doc/desktop/index.docbook create mode 100644 doc/kwindecoration/CMakeLists.txt create mode 100644 doc/kwindecoration/button.png create mode 100644 doc/kwindecoration/configure.png create mode 100644 doc/kwindecoration/decoration.png create mode 100644 doc/kwindecoration/index.docbook create mode 100644 doc/kwindecoration/main.png create mode 100644 doc/kwineffects/CMakeLists.txt create mode 100644 doc/kwineffects/configure-effects.png create mode 100644 doc/kwineffects/dialog-information.png create mode 100644 doc/kwineffects/index.docbook create mode 100644 doc/kwineffects/video.png create mode 100644 doc/kwinscreenedges/CMakeLists.txt create mode 100644 doc/kwinscreenedges/index.docbook create mode 100644 doc/kwintabbox/CMakeLists.txt create mode 100644 doc/kwintabbox/index.docbook create mode 100644 doc/kwintouchscreen/CMakeLists.txt create mode 100644 doc/kwintouchscreen/index.docbook create mode 100644 doc/kwinvirtualkeyboard/CMakeLists.txt create mode 100644 doc/kwinvirtualkeyboard/index.docbook create mode 100644 doc/windowbehaviour/CMakeLists.txt create mode 100644 doc/windowbehaviour/index.docbook create mode 100644 doc/windowspecific/CMakeLists.txt create mode 100644 doc/windowspecific/Face-smile.png create mode 100644 doc/windowspecific/akgregator-info.png create mode 100644 doc/windowspecific/akregator-attributes.png create mode 100644 doc/windowspecific/akregator-fav.png create mode 100644 doc/windowspecific/config-win-behavior.png create mode 100644 doc/windowspecific/emacs-attribute.png create mode 100644 doc/windowspecific/emacs-info.png create mode 100644 doc/windowspecific/focus-stealing-pop2top-attribute.png create mode 100644 doc/windowspecific/index.docbook create mode 100644 doc/windowspecific/knotes-attribute.png create mode 100644 doc/windowspecific/knotes-info.png create mode 100644 doc/windowspecific/kopete-attribute-2.png create mode 100644 doc/windowspecific/kopete-chat-attribute.png create mode 100644 doc/windowspecific/kopete-chat-info.png create mode 100644 doc/windowspecific/kopete-info.png create mode 100644 doc/windowspecific/kwin-detect-window.png create mode 100644 doc/windowspecific/kwin-kopete-rules.png create mode 100644 doc/windowspecific/kwin-rule-editor.png create mode 100644 doc/windowspecific/kwin-rules-main-n-akregator.png create mode 100644 doc/windowspecific/kwin-rules-main.png create mode 100644 doc/windowspecific/kwin-rules-ordering.png create mode 100644 doc/windowspecific/kwin-window-attributes.png create mode 100644 doc/windowspecific/kwin-window-matching.png create mode 100644 doc/windowspecific/pager-4-desktops.png create mode 100644 doc/windowspecific/tbird-compose-attribute.png create mode 100644 doc/windowspecific/tbird-compose-info.png create mode 100644 doc/windowspecific/tbird-main-attribute.png create mode 100644 doc/windowspecific/tbird-main-info.png create mode 100644 doc/windowspecific/tbird-reminder-attribute-2.png create mode 100644 doc/windowspecific/tbird-reminder-info.png create mode 100644 doc/windowspecific/window-matching-emacs.png create mode 100644 doc/windowspecific/window-matching-init.png create mode 100644 doc/windowspecific/window-matching-knotes.png create mode 100644 doc/windowspecific/window-matching-kopete-chat.png create mode 100644 doc/windowspecific/window-matching-kopete.png create mode 100644 doc/windowspecific/window-matching-ready-akregator.png create mode 100644 doc/windowspecific/window-matching-tbird-compose.png create mode 100644 doc/windowspecific/window-matching-tbird-main.png create mode 100644 doc/windowspecific/window-matching-tbird-reminder.png create mode 100644 kconf_update/CMakeLists.txt create mode 100644 kconf_update/kwin-5.16-auto-bordersize.sh create mode 100644 kconf_update/kwin-5.18-move-animspeed.py create mode 100644 kconf_update/kwin-5.21-desktop-grid-click-behavior.py create mode 100644 kconf_update/kwin-5.21-no-swap-encourage.py create mode 100644 kconf_update/kwin-5.23-disable-translucency-effect.sh create mode 100644 kconf_update/kwin-5.23-remove-cover-switch.py create mode 100644 kconf_update/kwin-5.23-remove-cubeslide.py create mode 100644 kconf_update/kwin-5.23-remove-flip-switch.py create mode 100644 kconf_update/kwin-5.23-remove-xrender-backend.py create mode 100644 kconf_update/kwin-5.25-effect-pluginid-config-group.py create mode 100644 kconf_update/kwin.upd create mode 100644 kconf_update/kwinrules-5.19-placement.pl create mode 100644 kconf_update/kwinrules-5.23-virtual-desktop-ids.py create mode 100644 kconf_update/kwinrules.upd create mode 100644 logo.png create mode 100644 plasma-kwin_wayland.service.in create mode 100644 plasma-kwin_x11.service.in create mode 100644 po/af/kcm_kwindecoration.po create mode 100644 po/af/kcm_kwinrules.po create mode 100644 po/af/kcmkwm.po create mode 100644 po/af/kwin.po create mode 100644 po/af/kwin_clients.po create mode 100644 po/ar/kcm_kwin_effects.po create mode 100644 po/ar/kcm_kwin_scripts.po create mode 100644 po/ar/kcm_kwin_virtualdesktops.po create mode 100644 po/ar/kcm_kwindecoration.po create mode 100644 po/ar/kcm_kwinrules.po create mode 100644 po/ar/kcm_kwintabbox.po create mode 100644 po/ar/kcm_virtualkeyboard.po create mode 100644 po/ar/kcmkwincommon.po create mode 100644 po/ar/kcmkwincompositing.po create mode 100644 po/ar/kcmkwinscreenedges.po create mode 100644 po/ar/kcmkwm.po create mode 100644 po/ar/kwin.po create mode 100644 po/ar/kwin_clients.po create mode 100644 po/ar/kwin_effects.po create mode 100644 po/ar/kwin_scripting.po create mode 100644 po/ar/kwin_scripts.po create mode 100644 po/as/kwin.po create mode 100644 po/ast/kcm_kwin_effects.po create mode 100644 po/ast/kcm_kwin_scripts.po create mode 100644 po/ast/kcm_kwin_virtualdesktops.po create mode 100644 po/ast/kcmkwincompositing.po create mode 100644 po/ast/kwin.po create mode 100644 po/ast/kwin_effects.po create mode 100644 po/az/kcm_kwin_effects.po create mode 100644 po/az/kcm_kwin_scripts.po create mode 100644 po/az/kcm_kwin_virtualdesktops.po create mode 100644 po/az/kcm_kwindecoration.po create mode 100644 po/az/kcm_kwinrules.po create mode 100644 po/az/kcm_kwintabbox.po create mode 100644 po/az/kcm_virtualkeyboard.po create mode 100644 po/az/kcmkwincommon.po create mode 100644 po/az/kcmkwincompositing.po create mode 100644 po/az/kcmkwinscreenedges.po create mode 100644 po/az/kcmkwm.po create mode 100644 po/az/kwin.po create mode 100644 po/az/kwin_clients.po create mode 100644 po/az/kwin_effects.po create mode 100644 po/az/kwin_scripting.po create mode 100644 po/az/kwin_scripts.po create mode 100644 po/be/kcm_kwin_virtualdesktops.po create mode 100644 po/be/kcm_kwindecoration.po create mode 100644 po/be/kcm_kwinrules.po create mode 100644 po/be/kcmkwincompositing.po create mode 100644 po/be/kcmkwm.po create mode 100644 po/be/kwin.po create mode 100644 po/be/kwin_clients.po create mode 100644 po/be/kwin_effects.po create mode 100644 po/be@latin/kwin.po create mode 100644 po/bg/kcm_kwin_effects.po create mode 100644 po/bg/kcm_kwin_scripts.po create mode 100644 po/bg/kcm_kwin_virtualdesktops.po create mode 100644 po/bg/kcm_kwindecoration.po create mode 100644 po/bg/kcm_kwinrules.po create mode 100644 po/bg/kcm_kwintabbox.po create mode 100644 po/bg/kcm_virtualkeyboard.po create mode 100644 po/bg/kcmkwincommon.po create mode 100644 po/bg/kcmkwincompositing.po create mode 100644 po/bg/kcmkwinscreenedges.po create mode 100644 po/bg/kcmkwm.po create mode 100644 po/bg/kwin.po create mode 100644 po/bg/kwin_clients.po create mode 100644 po/bg/kwin_effects.po create mode 100644 po/bg/kwin_scripting.po create mode 100644 po/bg/kwin_scripts.po create mode 100644 po/bn/kcmkwm.po create mode 100644 po/bn/kwin.po create mode 100644 po/bn/kwin_effects.po create mode 100644 po/bn_IN/kcm_kwin_virtualdesktops.po create mode 100644 po/bn_IN/kcm_kwindecoration.po create mode 100644 po/bn_IN/kcm_kwinrules.po create mode 100644 po/bn_IN/kcmkwm.po create mode 100644 po/bn_IN/kwin.po create mode 100644 po/bn_IN/kwin_clients.po create mode 100644 po/bn_IN/kwin_effects.po create mode 100644 po/br/kcm_kwindecoration.po create mode 100644 po/br/kcm_kwinrules.po create mode 100644 po/br/kcmkwm.po create mode 100644 po/br/kwin.po create mode 100644 po/br/kwin_clients.po create mode 100644 po/bs/kcm_kwin_scripts.po create mode 100644 po/bs/kcm_kwin_virtualdesktops.po create mode 100644 po/bs/kcm_kwindecoration.po create mode 100644 po/bs/kcm_kwinrules.po create mode 100644 po/bs/kcm_kwintabbox.po create mode 100644 po/bs/kcmkwincompositing.po create mode 100644 po/bs/kcmkwinscreenedges.po create mode 100644 po/bs/kcmkwm.po create mode 100644 po/bs/kwin.po create mode 100644 po/bs/kwin_clients.po create mode 100644 po/bs/kwin_effects.po create mode 100644 po/bs/kwin_scripting.po create mode 100644 po/ca/docs/kcontrol/desktop/index.docbook create mode 100644 po/ca/docs/kcontrol/kwindecoration/button.png create mode 100644 po/ca/docs/kcontrol/kwindecoration/decoration.png create mode 100644 po/ca/docs/kcontrol/kwindecoration/index.docbook create mode 100644 po/ca/docs/kcontrol/kwindecoration/main.png create mode 100644 po/ca/docs/kcontrol/kwineffects/index.docbook create mode 100644 po/ca/docs/kcontrol/kwinscreenedges/index.docbook create mode 100644 po/ca/docs/kcontrol/kwintabbox/index.docbook create mode 100644 po/ca/docs/kcontrol/kwintouchscreen/index.docbook create mode 100644 po/ca/docs/kcontrol/kwinvirtualkeyboard/index.docbook create mode 100644 po/ca/docs/kcontrol/windowbehaviour/index.docbook create mode 100644 po/ca/docs/kcontrol/windowspecific/index.docbook create mode 100644 po/ca/kcm_kwin_effects.po create mode 100644 po/ca/kcm_kwin_scripts.po create mode 100644 po/ca/kcm_kwin_virtualdesktops.po create mode 100644 po/ca/kcm_kwindecoration.po create mode 100644 po/ca/kcm_kwinrules.po create mode 100644 po/ca/kcm_kwintabbox.po create mode 100644 po/ca/kcm_virtualkeyboard.po create mode 100644 po/ca/kcmkwincommon.po create mode 100644 po/ca/kcmkwincompositing.po create mode 100644 po/ca/kcmkwinscreenedges.po create mode 100644 po/ca/kcmkwm.po create mode 100644 po/ca/kwin.po create mode 100644 po/ca/kwin_clients.po create mode 100644 po/ca/kwin_effects.po create mode 100644 po/ca/kwin_scripting.po create mode 100644 po/ca/kwin_scripts.po create mode 100644 po/ca@valencia/kcm_kwin_effects.po create mode 100644 po/ca@valencia/kcm_kwin_scripts.po create mode 100644 po/ca@valencia/kcm_kwin_virtualdesktops.po create mode 100644 po/ca@valencia/kcm_kwindecoration.po create mode 100644 po/ca@valencia/kcm_kwinrules.po create mode 100644 po/ca@valencia/kcm_kwintabbox.po create mode 100644 po/ca@valencia/kcm_virtualkeyboard.po create mode 100644 po/ca@valencia/kcmkwincommon.po create mode 100644 po/ca@valencia/kcmkwincompositing.po create mode 100644 po/ca@valencia/kcmkwinscreenedges.po create mode 100644 po/ca@valencia/kcmkwm.po create mode 100644 po/ca@valencia/kwin.po create mode 100644 po/ca@valencia/kwin_clients.po create mode 100644 po/ca@valencia/kwin_effects.po create mode 100644 po/ca@valencia/kwin_scripting.po create mode 100644 po/ca@valencia/kwin_scripts.po create mode 100644 po/cs/kcm_kwin_effects.po create mode 100644 po/cs/kcm_kwin_scripts.po create mode 100644 po/cs/kcm_kwin_virtualdesktops.po create mode 100644 po/cs/kcm_kwindecoration.po create mode 100644 po/cs/kcm_kwinrules.po create mode 100644 po/cs/kcm_kwintabbox.po create mode 100644 po/cs/kcm_virtualkeyboard.po create mode 100644 po/cs/kcmkwincommon.po create mode 100644 po/cs/kcmkwincompositing.po create mode 100644 po/cs/kcmkwinscreenedges.po create mode 100644 po/cs/kcmkwm.po create mode 100644 po/cs/kwin.po create mode 100644 po/cs/kwin_clients.po create mode 100644 po/cs/kwin_effects.po create mode 100644 po/cs/kwin_scripting.po create mode 100644 po/cs/kwin_scripts.po create mode 100644 po/csb/kcm_kwin_virtualdesktops.po create mode 100644 po/csb/kwin.po create mode 100644 po/csb/kwin_clients.po create mode 100644 po/csb/kwin_effects.po create mode 100644 po/cy/kcm_kwindecoration.po create mode 100644 po/cy/kcm_kwinrules.po create mode 100644 po/cy/kcmkwm.po create mode 100644 po/cy/kwin.po create mode 100644 po/cy/kwin_clients.po create mode 100644 po/da/kcm_kwin_effects.po create mode 100644 po/da/kcm_kwin_scripts.po create mode 100644 po/da/kcm_kwin_virtualdesktops.po create mode 100644 po/da/kcm_kwindecoration.po create mode 100644 po/da/kcm_kwinrules.po create mode 100644 po/da/kcm_kwintabbox.po create mode 100644 po/da/kcmkwincommon.po create mode 100644 po/da/kcmkwincompositing.po create mode 100644 po/da/kcmkwinscreenedges.po create mode 100644 po/da/kcmkwm.po create mode 100644 po/da/kwin.po create mode 100644 po/da/kwin_clients.po create mode 100644 po/da/kwin_effects.po create mode 100644 po/da/kwin_scripting.po create mode 100644 po/da/kwin_scripts.po create mode 100644 po/de/docs/kcontrol/desktop/index.docbook create mode 100644 po/de/docs/kcontrol/kwindecoration/index.docbook create mode 100644 po/de/docs/kcontrol/kwineffects/index.docbook create mode 100644 po/de/docs/kcontrol/kwinscreenedges/index.docbook create mode 100644 po/de/docs/kcontrol/kwintabbox/index.docbook create mode 100644 po/de/docs/kcontrol/kwintouchscreen/index.docbook create mode 100644 po/de/docs/kcontrol/kwinvirtualkeyboard/index.docbook create mode 100644 po/de/docs/kcontrol/windowbehaviour/index.docbook create mode 100644 po/de/docs/kcontrol/windowspecific/index.docbook create mode 100644 po/de/kcm_kwin_effects.po create mode 100644 po/de/kcm_kwin_scripts.po create mode 100644 po/de/kcm_kwin_virtualdesktops.po create mode 100644 po/de/kcm_kwindecoration.po create mode 100644 po/de/kcm_kwinrules.po create mode 100644 po/de/kcm_kwintabbox.po create mode 100644 po/de/kcm_virtualkeyboard.po create mode 100644 po/de/kcmkwincommon.po create mode 100644 po/de/kcmkwincompositing.po create mode 100644 po/de/kcmkwinscreenedges.po create mode 100644 po/de/kcmkwm.po create mode 100644 po/de/kwin.po create mode 100644 po/de/kwin_clients.po create mode 100644 po/de/kwin_effects.po create mode 100644 po/de/kwin_scripting.po create mode 100644 po/de/kwin_scripts.po create mode 100644 po/el/kcm_kwin_scripts.po create mode 100644 po/el/kcm_kwin_virtualdesktops.po create mode 100644 po/el/kcm_kwindecoration.po create mode 100644 po/el/kcm_kwinrules.po create mode 100644 po/el/kcm_kwintabbox.po create mode 100644 po/el/kcmkwincompositing.po create mode 100644 po/el/kcmkwinscreenedges.po create mode 100644 po/el/kcmkwm.po create mode 100644 po/el/kwin.po create mode 100644 po/el/kwin_clients.po create mode 100644 po/el/kwin_effects.po create mode 100644 po/el/kwin_scripting.po create mode 100644 po/el/kwin_scripts.po create mode 100644 po/en_GB/kcm_kwin_effects.po create mode 100644 po/en_GB/kcm_kwin_scripts.po create mode 100644 po/en_GB/kcm_kwin_virtualdesktops.po create mode 100644 po/en_GB/kcm_kwindecoration.po create mode 100644 po/en_GB/kcm_kwinrules.po create mode 100644 po/en_GB/kcm_kwintabbox.po create mode 100644 po/en_GB/kcm_virtualkeyboard.po create mode 100644 po/en_GB/kcmkwincommon.po create mode 100644 po/en_GB/kcmkwincompositing.po create mode 100644 po/en_GB/kcmkwinscreenedges.po create mode 100644 po/en_GB/kcmkwm.po create mode 100644 po/en_GB/kwin.po create mode 100644 po/en_GB/kwin_clients.po create mode 100644 po/en_GB/kwin_effects.po create mode 100644 po/en_GB/kwin_scripting.po create mode 100644 po/en_GB/kwin_scripts.po create mode 100644 po/eo/kcm_kwin_virtualdesktops.po create mode 100644 po/eo/kcm_kwindecoration.po create mode 100644 po/eo/kcm_kwinrules.po create mode 100644 po/eo/kcm_kwintabbox.po create mode 100644 po/eo/kcmkwincompositing.po create mode 100644 po/eo/kcmkwinscreenedges.po create mode 100644 po/eo/kcmkwm.po create mode 100644 po/eo/kwin.po create mode 100644 po/eo/kwin_clients.po create mode 100644 po/eo/kwin_effects.po create mode 100644 po/es/docs/kcontrol/desktop/index.docbook create mode 100644 po/es/kcm_kwin_effects.po create mode 100644 po/es/kcm_kwin_scripts.po create mode 100644 po/es/kcm_kwin_virtualdesktops.po create mode 100644 po/es/kcm_kwindecoration.po create mode 100644 po/es/kcm_kwinrules.po create mode 100644 po/es/kcm_kwintabbox.po create mode 100644 po/es/kcm_virtualkeyboard.po create mode 100644 po/es/kcmkwincommon.po create mode 100644 po/es/kcmkwincompositing.po create mode 100644 po/es/kcmkwinscreenedges.po create mode 100644 po/es/kcmkwm.po create mode 100644 po/es/kwin.po create mode 100644 po/es/kwin_clients.po create mode 100644 po/es/kwin_effects.po create mode 100644 po/es/kwin_scripting.po create mode 100644 po/es/kwin_scripts.po create mode 100644 po/et/kcm_kwin_effects.po create mode 100644 po/et/kcm_kwin_scripts.po create mode 100644 po/et/kcm_kwin_virtualdesktops.po create mode 100644 po/et/kcm_kwindecoration.po create mode 100644 po/et/kcm_kwinrules.po create mode 100644 po/et/kcm_kwintabbox.po create mode 100644 po/et/kcmkwincommon.po create mode 100644 po/et/kcmkwincompositing.po create mode 100644 po/et/kcmkwinscreenedges.po create mode 100644 po/et/kcmkwm.po create mode 100644 po/et/kwin.po create mode 100644 po/et/kwin_clients.po create mode 100644 po/et/kwin_effects.po create mode 100644 po/et/kwin_scripting.po create mode 100644 po/et/kwin_scripts.po create mode 100644 po/eu/kcm_kwin_effects.po create mode 100644 po/eu/kcm_kwin_scripts.po create mode 100644 po/eu/kcm_kwin_virtualdesktops.po create mode 100644 po/eu/kcm_kwindecoration.po create mode 100644 po/eu/kcm_kwinrules.po create mode 100644 po/eu/kcm_kwintabbox.po create mode 100644 po/eu/kcm_virtualkeyboard.po create mode 100644 po/eu/kcmkwincommon.po create mode 100644 po/eu/kcmkwincompositing.po create mode 100644 po/eu/kcmkwinscreenedges.po create mode 100644 po/eu/kcmkwm.po create mode 100644 po/eu/kwin.po create mode 100644 po/eu/kwin_clients.po create mode 100644 po/eu/kwin_effects.po create mode 100644 po/eu/kwin_scripting.po create mode 100644 po/eu/kwin_scripts.po create mode 100644 po/fa/kcm_kwin_scripts.po create mode 100644 po/fa/kcm_kwin_virtualdesktops.po create mode 100644 po/fa/kcm_kwindecoration.po create mode 100644 po/fa/kcm_kwinrules.po create mode 100644 po/fa/kcm_kwintabbox.po create mode 100644 po/fa/kcmkwincompositing.po create mode 100644 po/fa/kcmkwinscreenedges.po create mode 100644 po/fa/kcmkwm.po create mode 100644 po/fa/kwin.po create mode 100644 po/fa/kwin_clients.po create mode 100644 po/fa/kwin_effects.po create mode 100644 po/fi/kcm_kwin_effects.po create mode 100644 po/fi/kcm_kwin_scripts.po create mode 100644 po/fi/kcm_kwin_virtualdesktops.po create mode 100644 po/fi/kcm_kwindecoration.po create mode 100644 po/fi/kcm_kwinrules.po create mode 100644 po/fi/kcm_kwintabbox.po create mode 100644 po/fi/kcm_virtualkeyboard.po create mode 100644 po/fi/kcmkwincommon.po create mode 100644 po/fi/kcmkwincompositing.po create mode 100644 po/fi/kcmkwinscreenedges.po create mode 100644 po/fi/kcmkwm.po create mode 100644 po/fi/kwin.po create mode 100644 po/fi/kwin_clients.po create mode 100644 po/fi/kwin_effects.po create mode 100644 po/fi/kwin_scripting.po create mode 100644 po/fi/kwin_scripts.po create mode 100644 po/fr/docs/kcontrol/desktop/index.docbook create mode 100644 po/fr/docs/kcontrol/kwinscreenedges/index.docbook create mode 100644 po/fr/docs/kcontrol/kwintabbox/index.docbook create mode 100644 po/fr/docs/kcontrol/windowspecific/index.docbook create mode 100644 po/fr/kcm_kwin_effects.po create mode 100644 po/fr/kcm_kwin_scripts.po create mode 100644 po/fr/kcm_kwin_virtualdesktops.po create mode 100644 po/fr/kcm_kwindecoration.po create mode 100644 po/fr/kcm_kwinrules.po create mode 100644 po/fr/kcm_kwintabbox.po create mode 100644 po/fr/kcm_virtualkeyboard.po create mode 100644 po/fr/kcmkwincommon.po create mode 100644 po/fr/kcmkwincompositing.po create mode 100644 po/fr/kcmkwinscreenedges.po create mode 100644 po/fr/kcmkwm.po create mode 100644 po/fr/kwin.po create mode 100644 po/fr/kwin_clients.po create mode 100644 po/fr/kwin_effects.po create mode 100644 po/fr/kwin_scripting.po create mode 100644 po/fr/kwin_scripts.po create mode 100644 po/fy/kcm_kwin_virtualdesktops.po create mode 100644 po/fy/kcm_kwindecoration.po create mode 100644 po/fy/kcm_kwinrules.po create mode 100644 po/fy/kcmkwincompositing.po create mode 100644 po/fy/kcmkwinscreenedges.po create mode 100644 po/fy/kcmkwm.po create mode 100644 po/fy/kwin.po create mode 100644 po/fy/kwin_clients.po create mode 100644 po/fy/kwin_effects.po create mode 100644 po/ga/kcm_kwin_scripts.po create mode 100644 po/ga/kcm_kwin_virtualdesktops.po create mode 100644 po/ga/kcm_kwindecoration.po create mode 100644 po/ga/kcm_kwinrules.po create mode 100644 po/ga/kcm_kwintabbox.po create mode 100644 po/ga/kcmkwincompositing.po create mode 100644 po/ga/kcmkwinscreenedges.po create mode 100644 po/ga/kcmkwm.po create mode 100644 po/ga/kwin.po create mode 100644 po/ga/kwin_clients.po create mode 100644 po/ga/kwin_effects.po create mode 100644 po/gl/kcm_kwin_effects.po create mode 100644 po/gl/kcm_kwin_scripts.po create mode 100644 po/gl/kcm_kwin_virtualdesktops.po create mode 100644 po/gl/kcm_kwindecoration.po create mode 100644 po/gl/kcm_kwinrules.po create mode 100644 po/gl/kcm_kwintabbox.po create mode 100644 po/gl/kcmkwincommon.po create mode 100644 po/gl/kcmkwincompositing.po create mode 100644 po/gl/kcmkwinscreenedges.po create mode 100644 po/gl/kcmkwm.po create mode 100644 po/gl/kwin.po create mode 100644 po/gl/kwin_clients.po create mode 100644 po/gl/kwin_effects.po create mode 100644 po/gl/kwin_scripting.po create mode 100644 po/gl/kwin_scripts.po create mode 100644 po/gu/kcm_kwin_virtualdesktops.po create mode 100644 po/gu/kcm_kwindecoration.po create mode 100644 po/gu/kcm_kwinrules.po create mode 100644 po/gu/kcm_kwintabbox.po create mode 100644 po/gu/kcmkwincompositing.po create mode 100644 po/gu/kcmkwinscreenedges.po create mode 100644 po/gu/kcmkwm.po create mode 100644 po/gu/kwin.po create mode 100644 po/gu/kwin_clients.po create mode 100644 po/gu/kwin_effects.po create mode 100644 po/he/kcm_kwin_scripts.po create mode 100644 po/he/kcm_kwin_virtualdesktops.po create mode 100644 po/he/kcm_kwindecoration.po create mode 100644 po/he/kcm_kwinrules.po create mode 100644 po/he/kcm_kwintabbox.po create mode 100644 po/he/kcmkwincompositing.po create mode 100644 po/he/kcmkwinscreenedges.po create mode 100644 po/he/kcmkwm.po create mode 100644 po/he/kwin.po create mode 100644 po/he/kwin_clients.po create mode 100644 po/he/kwin_effects.po create mode 100644 po/he/kwin_scripting.po create mode 100644 po/he/kwin_scripts.po create mode 100644 po/hi/kcm_kwin_virtualdesktops.po create mode 100644 po/hi/kcm_kwindecoration.po create mode 100644 po/hi/kcm_kwinrules.po create mode 100644 po/hi/kcm_kwintabbox.po create mode 100644 po/hi/kcmkwincompositing.po create mode 100644 po/hi/kcmkwinscreenedges.po create mode 100644 po/hi/kcmkwm.po create mode 100644 po/hi/kwin.po create mode 100644 po/hi/kwin_clients.po create mode 100644 po/hi/kwin_effects.po create mode 100644 po/hne/kcm_kwin_virtualdesktops.po create mode 100644 po/hne/kcm_kwindecoration.po create mode 100644 po/hne/kcm_kwinrules.po create mode 100644 po/hne/kcmkwincompositing.po create mode 100644 po/hne/kcmkwm.po create mode 100644 po/hne/kwin.po create mode 100644 po/hne/kwin_clients.po create mode 100644 po/hne/kwin_effects.po create mode 100644 po/hr/kcm_kwin_virtualdesktops.po create mode 100644 po/hr/kcm_kwindecoration.po create mode 100644 po/hr/kcm_kwinrules.po create mode 100644 po/hr/kcm_kwintabbox.po create mode 100644 po/hr/kcmkwincompositing.po create mode 100644 po/hr/kcmkwinscreenedges.po create mode 100644 po/hr/kcmkwm.po create mode 100644 po/hr/kwin.po create mode 100644 po/hr/kwin_clients.po create mode 100644 po/hr/kwin_effects.po create mode 100644 po/hsb/kcm_kwin_virtualdesktops.po create mode 100644 po/hsb/kcm_kwindecoration.po create mode 100644 po/hsb/kcm_kwinrules.po create mode 100644 po/hsb/kcmkwincompositing.po create mode 100644 po/hsb/kcmkwm.po create mode 100644 po/hsb/kwin.po create mode 100644 po/hsb/kwin_clients.po create mode 100644 po/hsb/kwin_effects.po create mode 100644 po/hu/kcm_kwin_effects.po create mode 100644 po/hu/kcm_kwin_scripts.po create mode 100644 po/hu/kcm_kwin_virtualdesktops.po create mode 100644 po/hu/kcm_kwindecoration.po create mode 100644 po/hu/kcm_kwinrules.po create mode 100644 po/hu/kcm_kwintabbox.po create mode 100644 po/hu/kcm_virtualkeyboard.po create mode 100644 po/hu/kcmkwincommon.po create mode 100644 po/hu/kcmkwincompositing.po create mode 100644 po/hu/kcmkwinscreenedges.po create mode 100644 po/hu/kcmkwm.po create mode 100644 po/hu/kwin.po create mode 100644 po/hu/kwin_clients.po create mode 100644 po/hu/kwin_effects.po create mode 100644 po/hu/kwin_scripting.po create mode 100644 po/hu/kwin_scripts.po create mode 100644 po/ia/kcm_kwin_effects.po create mode 100644 po/ia/kcm_kwin_scripts.po create mode 100644 po/ia/kcm_kwin_virtualdesktops.po create mode 100644 po/ia/kcm_kwindecoration.po create mode 100644 po/ia/kcm_kwinrules.po create mode 100644 po/ia/kcm_kwintabbox.po create mode 100644 po/ia/kcm_virtualkeyboard.po create mode 100644 po/ia/kcmkwincommon.po create mode 100644 po/ia/kcmkwincompositing.po create mode 100644 po/ia/kcmkwinscreenedges.po create mode 100644 po/ia/kcmkwm.po create mode 100644 po/ia/kwin.po create mode 100644 po/ia/kwin_clients.po create mode 100644 po/ia/kwin_effects.po create mode 100644 po/ia/kwin_scripting.po create mode 100644 po/ia/kwin_scripts.po create mode 100644 po/id/docs/kcontrol/desktop/index.docbook create mode 100644 po/id/docs/kcontrol/kwindecoration/index.docbook create mode 100644 po/id/docs/kcontrol/kwineffects/index.docbook create mode 100644 po/id/docs/kcontrol/kwinscreenedges/index.docbook create mode 100644 po/id/docs/kcontrol/kwintabbox/index.docbook create mode 100644 po/id/docs/kcontrol/windowbehaviour/index.docbook create mode 100644 po/id/docs/kcontrol/windowspecific/index.docbook create mode 100644 po/id/kcm_kwin_effects.po create mode 100644 po/id/kcm_kwin_scripts.po create mode 100644 po/id/kcm_kwin_virtualdesktops.po create mode 100644 po/id/kcm_kwindecoration.po create mode 100644 po/id/kcm_kwinrules.po create mode 100644 po/id/kcm_kwintabbox.po create mode 100644 po/id/kcm_virtualkeyboard.po create mode 100644 po/id/kcmkwincommon.po create mode 100644 po/id/kcmkwincompositing.po create mode 100644 po/id/kcmkwinscreenedges.po create mode 100644 po/id/kcmkwm.po create mode 100644 po/id/kwin.po create mode 100644 po/id/kwin_clients.po create mode 100644 po/id/kwin_effects.po create mode 100644 po/id/kwin_scripting.po create mode 100644 po/id/kwin_scripts.po create mode 100644 po/is/kcm_kwin_virtualdesktops.po create mode 100644 po/is/kcm_kwindecoration.po create mode 100644 po/is/kcm_kwinrules.po create mode 100644 po/is/kcm_kwintabbox.po create mode 100644 po/is/kcmkwincompositing.po create mode 100644 po/is/kcmkwinscreenedges.po create mode 100644 po/is/kcmkwm.po create mode 100644 po/is/kwin.po create mode 100644 po/is/kwin_clients.po create mode 100644 po/is/kwin_effects.po create mode 100644 po/it/docs/kcontrol/desktop/index.docbook create mode 100644 po/it/docs/kcontrol/kwindecoration/index.docbook create mode 100644 po/it/docs/kcontrol/kwineffects/index.docbook create mode 100644 po/it/docs/kcontrol/kwinscreenedges/index.docbook create mode 100644 po/it/docs/kcontrol/kwintabbox/index.docbook create mode 100644 po/it/docs/kcontrol/kwintouchscreen/index.docbook create mode 100644 po/it/docs/kcontrol/kwinvirtualkeyboard/index.docbook create mode 100644 po/it/docs/kcontrol/windowbehaviour/index.docbook create mode 100644 po/it/docs/kcontrol/windowspecific/index.docbook create mode 100644 po/it/kcm_kwin_effects.po create mode 100644 po/it/kcm_kwin_scripts.po create mode 100644 po/it/kcm_kwin_virtualdesktops.po create mode 100644 po/it/kcm_kwindecoration.po create mode 100644 po/it/kcm_kwinrules.po create mode 100644 po/it/kcm_kwintabbox.po create mode 100644 po/it/kcm_virtualkeyboard.po create mode 100644 po/it/kcmkwincommon.po create mode 100644 po/it/kcmkwincompositing.po create mode 100644 po/it/kcmkwinscreenedges.po create mode 100644 po/it/kcmkwm.po create mode 100644 po/it/kwin.po create mode 100644 po/it/kwin_clients.po create mode 100644 po/it/kwin_effects.po create mode 100644 po/it/kwin_scripting.po create mode 100644 po/it/kwin_scripts.po create mode 100644 po/ja/kcm_kwin_effects.po create mode 100644 po/ja/kcm_kwin_scripts.po create mode 100644 po/ja/kcm_kwin_virtualdesktops.po create mode 100644 po/ja/kcm_kwindecoration.po create mode 100644 po/ja/kcm_kwinrules.po create mode 100644 po/ja/kcm_kwintabbox.po create mode 100644 po/ja/kcm_virtualkeyboard.po create mode 100644 po/ja/kcmkwincommon.po create mode 100644 po/ja/kcmkwincompositing.po create mode 100644 po/ja/kcmkwinscreenedges.po create mode 100644 po/ja/kcmkwm.po create mode 100644 po/ja/kwin.po create mode 100644 po/ja/kwin_clients.po create mode 100644 po/ja/kwin_effects.po create mode 100644 po/ja/kwin_scripting.po create mode 100644 po/ja/kwin_scripts.po create mode 100644 po/ka/kcm_kwin_effects.po create mode 100644 po/ka/kcm_kwin_scripts.po create mode 100644 po/ka/kcm_kwin_virtualdesktops.po create mode 100644 po/ka/kcm_kwindecoration.po create mode 100644 po/ka/kcm_kwinrules.po create mode 100644 po/ka/kcm_kwintabbox.po create mode 100644 po/ka/kcm_virtualkeyboard.po create mode 100644 po/ka/kcmkwincommon.po create mode 100644 po/ka/kcmkwincompositing.po create mode 100644 po/ka/kcmkwinscreenedges.po create mode 100644 po/ka/kcmkwm.po create mode 100644 po/ka/kwin.po create mode 100644 po/ka/kwin_clients.po create mode 100644 po/ka/kwin_effects.po create mode 100644 po/ka/kwin_scripting.po create mode 100644 po/ka/kwin_scripts.po create mode 100644 po/kk/kcm_kwin_scripts.po create mode 100644 po/kk/kcm_kwin_virtualdesktops.po create mode 100644 po/kk/kcm_kwindecoration.po create mode 100644 po/kk/kcm_kwinrules.po create mode 100644 po/kk/kcm_kwintabbox.po create mode 100644 po/kk/kcmkwincompositing.po create mode 100644 po/kk/kcmkwinscreenedges.po create mode 100644 po/kk/kcmkwm.po create mode 100644 po/kk/kwin.po create mode 100644 po/kk/kwin_clients.po create mode 100644 po/kk/kwin_effects.po create mode 100644 po/kk/kwin_scripting.po create mode 100644 po/km/kcm_kwin_scripts.po create mode 100644 po/km/kcm_kwin_virtualdesktops.po create mode 100644 po/km/kcm_kwindecoration.po create mode 100644 po/km/kcm_kwinrules.po create mode 100644 po/km/kcm_kwintabbox.po create mode 100644 po/km/kcmkwincompositing.po create mode 100644 po/km/kcmkwinscreenedges.po create mode 100644 po/km/kcmkwm.po create mode 100644 po/km/kwin.po create mode 100644 po/km/kwin_clients.po create mode 100644 po/km/kwin_effects.po create mode 100644 po/kn/kcm_kwin_virtualdesktops.po create mode 100644 po/kn/kcm_kwindecoration.po create mode 100644 po/kn/kcm_kwinrules.po create mode 100644 po/kn/kcm_kwintabbox.po create mode 100644 po/kn/kcmkwincompositing.po create mode 100644 po/kn/kcmkwinscreenedges.po create mode 100644 po/kn/kcmkwm.po create mode 100644 po/kn/kwin.po create mode 100644 po/kn/kwin_clients.po create mode 100644 po/kn/kwin_effects.po create mode 100644 po/ko/kcm_kwin_effects.po create mode 100644 po/ko/kcm_kwin_scripts.po create mode 100644 po/ko/kcm_kwin_virtualdesktops.po create mode 100644 po/ko/kcm_kwindecoration.po create mode 100644 po/ko/kcm_kwinrules.po create mode 100644 po/ko/kcm_kwintabbox.po create mode 100644 po/ko/kcm_virtualkeyboard.po create mode 100644 po/ko/kcmkwincommon.po create mode 100644 po/ko/kcmkwincompositing.po create mode 100644 po/ko/kcmkwinscreenedges.po create mode 100644 po/ko/kcmkwm.po create mode 100644 po/ko/kwin.po create mode 100644 po/ko/kwin_clients.po create mode 100644 po/ko/kwin_effects.po create mode 100644 po/ko/kwin_scripting.po create mode 100644 po/ko/kwin_scripts.po create mode 100644 po/ku/kcm_kwin_virtualdesktops.po create mode 100644 po/ku/kcm_kwindecoration.po create mode 100644 po/ku/kcm_kwinrules.po create mode 100644 po/ku/kcmkwincompositing.po create mode 100644 po/ku/kcmkwm.po create mode 100644 po/ku/kwin.po create mode 100644 po/ku/kwin_clients.po create mode 100644 po/ku/kwin_effects.po create mode 100644 po/lt/kcm_kwin_effects.po create mode 100644 po/lt/kcm_kwin_scripts.po create mode 100644 po/lt/kcm_kwin_virtualdesktops.po create mode 100644 po/lt/kcm_kwindecoration.po create mode 100644 po/lt/kcm_kwinrules.po create mode 100644 po/lt/kcm_kwintabbox.po create mode 100644 po/lt/kcmkwincommon.po create mode 100644 po/lt/kcmkwincompositing.po create mode 100644 po/lt/kcmkwinscreenedges.po create mode 100644 po/lt/kcmkwm.po create mode 100644 po/lt/kwin.po create mode 100644 po/lt/kwin_clients.po create mode 100644 po/lt/kwin_effects.po create mode 100644 po/lt/kwin_scripting.po create mode 100644 po/lt/kwin_scripts.po create mode 100644 po/lv/kcm_kwin_virtualdesktops.po create mode 100644 po/lv/kcm_kwindecoration.po create mode 100644 po/lv/kcm_kwinrules.po create mode 100644 po/lv/kcm_kwintabbox.po create mode 100644 po/lv/kcmkwincompositing.po create mode 100644 po/lv/kcmkwinscreenedges.po create mode 100644 po/lv/kcmkwm.po create mode 100644 po/lv/kwin.po create mode 100644 po/lv/kwin_clients.po create mode 100644 po/lv/kwin_effects.po create mode 100644 po/mai/kcm_kwin_virtualdesktops.po create mode 100644 po/mai/kcm_kwindecoration.po create mode 100644 po/mai/kcm_kwinrules.po create mode 100644 po/mai/kcm_kwintabbox.po create mode 100644 po/mai/kcmkwincompositing.po create mode 100644 po/mai/kcmkwinscreenedges.po create mode 100644 po/mai/kcmkwm.po create mode 100644 po/mai/kwin.po create mode 100644 po/mai/kwin_clients.po create mode 100644 po/mai/kwin_effects.po create mode 100644 po/mk/kcm_kwin_virtualdesktops.po create mode 100644 po/mk/kcm_kwindecoration.po create mode 100644 po/mk/kcm_kwinrules.po create mode 100644 po/mk/kcmkwincompositing.po create mode 100644 po/mk/kcmkwm.po create mode 100644 po/mk/kwin.po create mode 100644 po/mk/kwin_clients.po create mode 100644 po/mk/kwin_effects.po create mode 100644 po/ml/kcm_kwin_effects.po create mode 100644 po/ml/kcm_kwin_scripts.po create mode 100644 po/ml/kcm_kwin_virtualdesktops.po create mode 100644 po/ml/kcm_kwindecoration.po create mode 100644 po/ml/kcm_kwinrules.po create mode 100644 po/ml/kcm_kwintabbox.po create mode 100644 po/ml/kcmkwincommon.po create mode 100644 po/ml/kcmkwincompositing.po create mode 100644 po/ml/kcmkwinscreenedges.po create mode 100644 po/ml/kcmkwm.po create mode 100644 po/ml/kwin.po create mode 100644 po/ml/kwin_clients.po create mode 100644 po/ml/kwin_effects.po create mode 100644 po/ml/kwin_scripting.po create mode 100644 po/ml/kwin_scripts.po create mode 100644 po/mr/kcm_kwin_scripts.po create mode 100644 po/mr/kcm_kwin_virtualdesktops.po create mode 100644 po/mr/kcm_kwindecoration.po create mode 100644 po/mr/kcm_kwinrules.po create mode 100644 po/mr/kcm_kwintabbox.po create mode 100644 po/mr/kcmkwincompositing.po create mode 100644 po/mr/kcmkwinscreenedges.po create mode 100644 po/mr/kcmkwm.po create mode 100644 po/mr/kwin.po create mode 100644 po/mr/kwin_clients.po create mode 100644 po/mr/kwin_effects.po create mode 100644 po/mr/kwin_scripting.po create mode 100644 po/ms/kcm_kwin_virtualdesktops.po create mode 100644 po/ms/kcm_kwindecoration.po create mode 100644 po/ms/kcm_kwinrules.po create mode 100644 po/ms/kcm_kwintabbox.po create mode 100644 po/ms/kcmkwincompositing.po create mode 100644 po/ms/kcmkwinscreenedges.po create mode 100644 po/ms/kcmkwm.po create mode 100644 po/ms/kwin.po create mode 100644 po/ms/kwin_clients.po create mode 100644 po/ms/kwin_effects.po create mode 100644 po/nb/kcm_kwin_scripts.po create mode 100644 po/nb/kcm_kwin_virtualdesktops.po create mode 100644 po/nb/kcm_kwindecoration.po create mode 100644 po/nb/kcm_kwinrules.po create mode 100644 po/nb/kcm_kwintabbox.po create mode 100644 po/nb/kcmkwincommon.po create mode 100644 po/nb/kcmkwincompositing.po create mode 100644 po/nb/kcmkwinscreenedges.po create mode 100644 po/nb/kcmkwm.po create mode 100644 po/nb/kwin.po create mode 100644 po/nb/kwin_clients.po create mode 100644 po/nb/kwin_effects.po create mode 100644 po/nb/kwin_scripting.po create mode 100644 po/nds/kcm_kwin_scripts.po create mode 100644 po/nds/kcm_kwin_virtualdesktops.po create mode 100644 po/nds/kcm_kwindecoration.po create mode 100644 po/nds/kcm_kwinrules.po create mode 100644 po/nds/kcm_kwintabbox.po create mode 100644 po/nds/kcmkwincompositing.po create mode 100644 po/nds/kcmkwinscreenedges.po create mode 100644 po/nds/kcmkwm.po create mode 100644 po/nds/kwin.po create mode 100644 po/nds/kwin_clients.po create mode 100644 po/nds/kwin_effects.po create mode 100644 po/nds/kwin_scripting.po create mode 100644 po/ne/kcm_kwin_virtualdesktops.po create mode 100644 po/ne/kcm_kwindecoration.po create mode 100644 po/ne/kcm_kwinrules.po create mode 100644 po/ne/kcmkwincompositing.po create mode 100644 po/ne/kcmkwm.po create mode 100644 po/ne/kwin.po create mode 100644 po/ne/kwin_clients.po create mode 100644 po/nl/docs/kcontrol/desktop/index.docbook create mode 100644 po/nl/docs/kcontrol/kwindecoration/index.docbook create mode 100644 po/nl/docs/kcontrol/kwineffects/index.docbook create mode 100644 po/nl/docs/kcontrol/kwinscreenedges/index.docbook create mode 100644 po/nl/docs/kcontrol/kwintabbox/index.docbook create mode 100644 po/nl/docs/kcontrol/kwintouchscreen/index.docbook create mode 100644 po/nl/docs/kcontrol/kwinvirtualkeyboard/index.docbook create mode 100644 po/nl/docs/kcontrol/windowbehaviour/index.docbook create mode 100644 po/nl/docs/kcontrol/windowspecific/index.docbook create mode 100644 po/nl/kcm_kwin_effects.po create mode 100644 po/nl/kcm_kwin_scripts.po create mode 100644 po/nl/kcm_kwin_virtualdesktops.po create mode 100644 po/nl/kcm_kwindecoration.po create mode 100644 po/nl/kcm_kwinrules.po create mode 100644 po/nl/kcm_kwintabbox.po create mode 100644 po/nl/kcm_virtualkeyboard.po create mode 100644 po/nl/kcmkwincommon.po create mode 100644 po/nl/kcmkwincompositing.po create mode 100644 po/nl/kcmkwinscreenedges.po create mode 100644 po/nl/kcmkwm.po create mode 100644 po/nl/kwin.po create mode 100644 po/nl/kwin_clients.po create mode 100644 po/nl/kwin_effects.po create mode 100644 po/nl/kwin_scripting.po create mode 100644 po/nl/kwin_scripts.po create mode 100644 po/nn/kcm_kwin_effects.po create mode 100644 po/nn/kcm_kwin_scripts.po create mode 100644 po/nn/kcm_kwin_virtualdesktops.po create mode 100644 po/nn/kcm_kwindecoration.po create mode 100644 po/nn/kcm_kwinrules.po create mode 100644 po/nn/kcm_kwintabbox.po create mode 100644 po/nn/kcm_virtualkeyboard.po create mode 100644 po/nn/kcmkwincommon.po create mode 100644 po/nn/kcmkwincompositing.po create mode 100644 po/nn/kcmkwinscreenedges.po create mode 100644 po/nn/kcmkwm.po create mode 100644 po/nn/kwin.po create mode 100644 po/nn/kwin_clients.po create mode 100644 po/nn/kwin_effects.po create mode 100644 po/nn/kwin_scripting.po create mode 100644 po/nn/kwin_scripts.po create mode 100644 po/oc/kcm_kwin_virtualdesktops.po create mode 100644 po/oc/kcm_kwindecoration.po create mode 100644 po/oc/kcm_kwinrules.po create mode 100644 po/oc/kcmkwincompositing.po create mode 100644 po/oc/kcmkwm.po create mode 100644 po/oc/kwin.po create mode 100644 po/oc/kwin_clients.po create mode 100644 po/oc/kwin_effects.po create mode 100644 po/pa/kcm_kwin_effects.po create mode 100644 po/pa/kcm_kwin_scripts.po create mode 100644 po/pa/kcm_kwin_virtualdesktops.po create mode 100644 po/pa/kcm_kwindecoration.po create mode 100644 po/pa/kcm_kwinrules.po create mode 100644 po/pa/kcm_kwintabbox.po create mode 100644 po/pa/kcmkwincommon.po create mode 100644 po/pa/kcmkwincompositing.po create mode 100644 po/pa/kcmkwinscreenedges.po create mode 100644 po/pa/kcmkwm.po create mode 100644 po/pa/kwin.po create mode 100644 po/pa/kwin_clients.po create mode 100644 po/pa/kwin_effects.po create mode 100644 po/pa/kwin_scripting.po create mode 100644 po/pa/kwin_scripts.po create mode 100644 po/pl/kcm_kwin_effects.po create mode 100644 po/pl/kcm_kwin_scripts.po create mode 100644 po/pl/kcm_kwin_virtualdesktops.po create mode 100644 po/pl/kcm_kwindecoration.po create mode 100644 po/pl/kcm_kwinrules.po create mode 100644 po/pl/kcm_kwintabbox.po create mode 100644 po/pl/kcm_virtualkeyboard.po create mode 100644 po/pl/kcmkwincommon.po create mode 100644 po/pl/kcmkwincompositing.po create mode 100644 po/pl/kcmkwinscreenedges.po create mode 100644 po/pl/kcmkwm.po create mode 100644 po/pl/kwin.po create mode 100644 po/pl/kwin_clients.po create mode 100644 po/pl/kwin_effects.po create mode 100644 po/pl/kwin_scripting.po create mode 100644 po/pl/kwin_scripts.po create mode 100644 po/pt/docs/kcontrol/desktop/index.docbook create mode 100644 po/pt/docs/kcontrol/kwindecoration/index.docbook create mode 100644 po/pt/docs/kcontrol/kwineffects/index.docbook create mode 100644 po/pt/docs/kcontrol/kwinscreenedges/index.docbook create mode 100644 po/pt/docs/kcontrol/kwintabbox/index.docbook create mode 100644 po/pt/docs/kcontrol/windowbehaviour/index.docbook create mode 100644 po/pt/docs/kcontrol/windowspecific/index.docbook create mode 100644 po/pt/kcm_kwin_effects.po create mode 100644 po/pt/kcm_kwin_scripts.po create mode 100644 po/pt/kcm_kwin_virtualdesktops.po create mode 100644 po/pt/kcm_kwindecoration.po create mode 100644 po/pt/kcm_kwinrules.po create mode 100644 po/pt/kcm_kwintabbox.po create mode 100644 po/pt/kcm_virtualkeyboard.po create mode 100644 po/pt/kcmkwincommon.po create mode 100644 po/pt/kcmkwincompositing.po create mode 100644 po/pt/kcmkwinscreenedges.po create mode 100644 po/pt/kcmkwm.po create mode 100644 po/pt/kwin.po create mode 100644 po/pt/kwin_clients.po create mode 100644 po/pt/kwin_effects.po create mode 100644 po/pt/kwin_scripting.po create mode 100644 po/pt/kwin_scripts.po create mode 100644 po/pt_BR/docs/kcontrol/desktop/index.docbook create mode 100644 po/pt_BR/docs/kcontrol/kwindecoration/configure.png create mode 100644 po/pt_BR/docs/kcontrol/kwindecoration/index.docbook create mode 100644 po/pt_BR/docs/kcontrol/kwinscreenedges/index.docbook create mode 100644 po/pt_BR/docs/kcontrol/kwintabbox/index.docbook create mode 100644 po/pt_BR/docs/kcontrol/windowbehaviour/index.docbook create mode 100644 po/pt_BR/docs/kcontrol/windowspecific/index.docbook create mode 100644 po/pt_BR/kcm_kwin_effects.po create mode 100644 po/pt_BR/kcm_kwin_scripts.po create mode 100644 po/pt_BR/kcm_kwin_virtualdesktops.po create mode 100644 po/pt_BR/kcm_kwindecoration.po create mode 100644 po/pt_BR/kcm_kwinrules.po create mode 100644 po/pt_BR/kcm_kwintabbox.po create mode 100644 po/pt_BR/kcm_virtualkeyboard.po create mode 100644 po/pt_BR/kcmkwincommon.po create mode 100644 po/pt_BR/kcmkwincompositing.po create mode 100644 po/pt_BR/kcmkwinscreenedges.po create mode 100644 po/pt_BR/kcmkwm.po create mode 100644 po/pt_BR/kwin.po create mode 100644 po/pt_BR/kwin_clients.po create mode 100644 po/pt_BR/kwin_effects.po create mode 100644 po/pt_BR/kwin_scripting.po create mode 100644 po/pt_BR/kwin_scripts.po create mode 100644 po/ro/kcm_kwin_effects.po create mode 100644 po/ro/kcm_kwin_scripts.po create mode 100644 po/ro/kcm_kwin_virtualdesktops.po create mode 100644 po/ro/kcm_kwindecoration.po create mode 100644 po/ro/kcm_kwinrules.po create mode 100644 po/ro/kcm_kwintabbox.po create mode 100644 po/ro/kcm_virtualkeyboard.po create mode 100644 po/ro/kcmkwincommon.po create mode 100644 po/ro/kcmkwincompositing.po create mode 100644 po/ro/kcmkwinscreenedges.po create mode 100644 po/ro/kcmkwm.po create mode 100644 po/ro/kwin.po create mode 100644 po/ro/kwin_clients.po create mode 100644 po/ro/kwin_effects.po create mode 100644 po/ro/kwin_scripting.po create mode 100644 po/ro/kwin_scripts.po create mode 100644 po/ru/docs/kcontrol/desktop/index.docbook create mode 100644 po/ru/docs/kcontrol/kwindecoration/button.png create mode 100644 po/ru/docs/kcontrol/kwindecoration/configure.png create mode 100644 po/ru/docs/kcontrol/kwindecoration/decoration.png create mode 100644 po/ru/docs/kcontrol/kwindecoration/index.docbook create mode 100644 po/ru/docs/kcontrol/kwindecoration/main.png create mode 100644 po/ru/docs/kcontrol/kwineffects/index.docbook create mode 100644 po/ru/docs/kcontrol/kwinscreenedges/index.docbook create mode 100644 po/ru/docs/kcontrol/kwintabbox/index.docbook create mode 100644 po/ru/docs/kcontrol/kwintouchscreen/index.docbook create mode 100644 po/ru/docs/kcontrol/kwinvirtualkeyboard/index.docbook create mode 100644 po/ru/docs/kcontrol/windowbehaviour/index.docbook create mode 100644 po/ru/docs/kcontrol/windowspecific/akgregator-info.png create mode 100644 po/ru/docs/kcontrol/windowspecific/akregator-attributes.png create mode 100644 po/ru/docs/kcontrol/windowspecific/akregator-fav.png create mode 100644 po/ru/docs/kcontrol/windowspecific/config-win-behavior.png create mode 100644 po/ru/docs/kcontrol/windowspecific/emacs-attribute.png create mode 100644 po/ru/docs/kcontrol/windowspecific/emacs-info.png create mode 100644 po/ru/docs/kcontrol/windowspecific/focus-stealing-pop2top-attribute.png create mode 100644 po/ru/docs/kcontrol/windowspecific/index.docbook create mode 100644 po/ru/docs/kcontrol/windowspecific/knotes-attribute.png create mode 100644 po/ru/docs/kcontrol/windowspecific/knotes-info.png create mode 100644 po/ru/docs/kcontrol/windowspecific/kopete-attribute-2.png create mode 100644 po/ru/docs/kcontrol/windowspecific/kopete-chat-attribute.png create mode 100644 po/ru/docs/kcontrol/windowspecific/kopete-chat-info.png create mode 100644 po/ru/docs/kcontrol/windowspecific/kopete-info.png create mode 100644 po/ru/docs/kcontrol/windowspecific/kwin-detect-window.png create mode 100644 po/ru/docs/kcontrol/windowspecific/kwin-kopete-rules.png create mode 100644 po/ru/docs/kcontrol/windowspecific/kwin-rule-editor.png create mode 100644 po/ru/docs/kcontrol/windowspecific/kwin-rules-main-n-akregator.png create mode 100644 po/ru/docs/kcontrol/windowspecific/kwin-rules-main.png create mode 100644 po/ru/docs/kcontrol/windowspecific/kwin-rules-ordering.png create mode 100644 po/ru/docs/kcontrol/windowspecific/kwin-window-attributes.png create mode 100644 po/ru/docs/kcontrol/windowspecific/kwin-window-matching.png create mode 100644 po/ru/docs/kcontrol/windowspecific/tbird-compose-attribute.png create mode 100644 po/ru/docs/kcontrol/windowspecific/tbird-compose-info.png create mode 100644 po/ru/docs/kcontrol/windowspecific/tbird-main-attribute.png create mode 100644 po/ru/docs/kcontrol/windowspecific/tbird-main-info.png create mode 100644 po/ru/docs/kcontrol/windowspecific/tbird-reminder-attribute-2.png create mode 100644 po/ru/docs/kcontrol/windowspecific/tbird-reminder-info.png create mode 100644 po/ru/docs/kcontrol/windowspecific/window-matching-emacs.png create mode 100644 po/ru/docs/kcontrol/windowspecific/window-matching-init.png create mode 100644 po/ru/docs/kcontrol/windowspecific/window-matching-knotes.png create mode 100644 po/ru/docs/kcontrol/windowspecific/window-matching-kopete-chat.png create mode 100644 po/ru/docs/kcontrol/windowspecific/window-matching-kopete.png create mode 100644 po/ru/docs/kcontrol/windowspecific/window-matching-ready-akregator.png create mode 100644 po/ru/docs/kcontrol/windowspecific/window-matching-tbird-compose.png create mode 100644 po/ru/docs/kcontrol/windowspecific/window-matching-tbird-main.png create mode 100644 po/ru/docs/kcontrol/windowspecific/window-matching-tbird-reminder.png create mode 100644 po/ru/kcm_kwin_effects.po create mode 100644 po/ru/kcm_kwin_scripts.po create mode 100644 po/ru/kcm_kwin_virtualdesktops.po create mode 100644 po/ru/kcm_kwindecoration.po create mode 100644 po/ru/kcm_kwinrules.po create mode 100644 po/ru/kcm_kwintabbox.po create mode 100644 po/ru/kcm_virtualkeyboard.po create mode 100644 po/ru/kcmkwincommon.po create mode 100644 po/ru/kcmkwincompositing.po create mode 100644 po/ru/kcmkwinscreenedges.po create mode 100644 po/ru/kcmkwm.po create mode 100644 po/ru/kwin.po create mode 100644 po/ru/kwin_clients.po create mode 100644 po/ru/kwin_effects.po create mode 100644 po/ru/kwin_scripting.po create mode 100644 po/ru/kwin_scripts.po create mode 100644 po/se/kcm_kwin_virtualdesktops.po create mode 100644 po/se/kcm_kwindecoration.po create mode 100644 po/se/kcm_kwinrules.po create mode 100644 po/se/kcmkwincommon.po create mode 100644 po/se/kcmkwincompositing.po create mode 100644 po/se/kcmkwm.po create mode 100644 po/se/kwin.po create mode 100644 po/se/kwin_clients.po create mode 100644 po/si/kcm_kwin_virtualdesktops.po create mode 100644 po/si/kcm_kwindecoration.po create mode 100644 po/si/kcm_kwinrules.po create mode 100644 po/si/kcm_kwintabbox.po create mode 100644 po/si/kcmkwincompositing.po create mode 100644 po/si/kcmkwinscreenedges.po create mode 100644 po/si/kcmkwm.po create mode 100644 po/si/kwin.po create mode 100644 po/si/kwin_clients.po create mode 100644 po/si/kwin_effects.po create mode 100644 po/sk/kcm_kwin_effects.po create mode 100644 po/sk/kcm_kwin_scripts.po create mode 100644 po/sk/kcm_kwin_virtualdesktops.po create mode 100644 po/sk/kcm_kwindecoration.po create mode 100644 po/sk/kcm_kwinrules.po create mode 100644 po/sk/kcm_kwintabbox.po create mode 100644 po/sk/kcm_virtualkeyboard.po create mode 100644 po/sk/kcmkwincommon.po create mode 100644 po/sk/kcmkwincompositing.po create mode 100644 po/sk/kcmkwinscreenedges.po create mode 100644 po/sk/kcmkwm.po create mode 100644 po/sk/kwin.po create mode 100644 po/sk/kwin_clients.po create mode 100644 po/sk/kwin_effects.po create mode 100644 po/sk/kwin_scripting.po create mode 100644 po/sk/kwin_scripts.po create mode 100644 po/sl/kcm_kwin_effects.po create mode 100644 po/sl/kcm_kwin_scripts.po create mode 100644 po/sl/kcm_kwin_virtualdesktops.po create mode 100644 po/sl/kcm_kwindecoration.po create mode 100644 po/sl/kcm_kwinrules.po create mode 100644 po/sl/kcm_kwintabbox.po create mode 100644 po/sl/kcm_virtualkeyboard.po create mode 100644 po/sl/kcmkwincommon.po create mode 100644 po/sl/kcmkwincompositing.po create mode 100644 po/sl/kcmkwinscreenedges.po create mode 100644 po/sl/kcmkwm.po create mode 100644 po/sl/kwin.po create mode 100644 po/sl/kwin_clients.po create mode 100644 po/sl/kwin_effects.po create mode 100644 po/sl/kwin_scripting.po create mode 100644 po/sl/kwin_scripts.po create mode 100644 po/sq/kcm_kwin_virtualdesktops.po create mode 100644 po/sq/kcm_kwindecoration.po create mode 100644 po/sq/kcm_kwinrules.po create mode 100644 po/sq/kcmkwincompositing.po create mode 100644 po/sq/kcmkwinscreenedges.po create mode 100644 po/sq/kcmkwm.po create mode 100644 po/sq/kwin.po create mode 100644 po/sq/kwin_clients.po create mode 100644 po/sq/kwin_effects.po create mode 100644 po/sr/docs/kcontrol/desktop/index.docbook create mode 100644 po/sr/kcm_kwin_scripts.po create mode 100644 po/sr/kcm_kwin_virtualdesktops.po create mode 100644 po/sr/kcm_kwindecoration.po create mode 100644 po/sr/kcm_kwinrules.po create mode 100644 po/sr/kcm_kwintabbox.po create mode 100644 po/sr/kcmkwincompositing.po create mode 100644 po/sr/kcmkwinscreenedges.po create mode 100644 po/sr/kcmkwm.po create mode 100644 po/sr/kwin.po create mode 100644 po/sr/kwin_clients.po create mode 100644 po/sr/kwin_effects.po create mode 100644 po/sr/kwin_scripting.po create mode 100644 po/sr/kwin_scripts.po create mode 100644 po/sr@ijekavian/kcm_kwin_scripts.po create mode 100644 po/sr@ijekavian/kcm_kwin_virtualdesktops.po create mode 100644 po/sr@ijekavian/kcm_kwindecoration.po create mode 100644 po/sr@ijekavian/kcm_kwinrules.po create mode 100644 po/sr@ijekavian/kcm_kwintabbox.po create mode 100644 po/sr@ijekavian/kcmkwincompositing.po create mode 100644 po/sr@ijekavian/kcmkwinscreenedges.po create mode 100644 po/sr@ijekavian/kcmkwm.po create mode 100644 po/sr@ijekavian/kwin.po create mode 100644 po/sr@ijekavian/kwin_clients.po create mode 100644 po/sr@ijekavian/kwin_effects.po create mode 100644 po/sr@ijekavian/kwin_scripting.po create mode 100644 po/sr@ijekavian/kwin_scripts.po create mode 100644 po/sr@ijekavianlatin/kcm_kwin_scripts.po create mode 100644 po/sr@ijekavianlatin/kcm_kwin_virtualdesktops.po create mode 100644 po/sr@ijekavianlatin/kcm_kwindecoration.po create mode 100644 po/sr@ijekavianlatin/kcm_kwinrules.po create mode 100644 po/sr@ijekavianlatin/kcm_kwintabbox.po create mode 100644 po/sr@ijekavianlatin/kcmkwincompositing.po create mode 100644 po/sr@ijekavianlatin/kcmkwinscreenedges.po create mode 100644 po/sr@ijekavianlatin/kcmkwm.po create mode 100644 po/sr@ijekavianlatin/kwin.po create mode 100644 po/sr@ijekavianlatin/kwin_clients.po create mode 100644 po/sr@ijekavianlatin/kwin_effects.po create mode 100644 po/sr@ijekavianlatin/kwin_scripting.po create mode 100644 po/sr@ijekavianlatin/kwin_scripts.po create mode 100644 po/sr@latin/docs/kcontrol/desktop/index.docbook create mode 100644 po/sr@latin/kcm_kwin_scripts.po create mode 100644 po/sr@latin/kcm_kwin_virtualdesktops.po create mode 100644 po/sr@latin/kcm_kwindecoration.po create mode 100644 po/sr@latin/kcm_kwinrules.po create mode 100644 po/sr@latin/kcm_kwintabbox.po create mode 100644 po/sr@latin/kcmkwincompositing.po create mode 100644 po/sr@latin/kcmkwinscreenedges.po create mode 100644 po/sr@latin/kcmkwm.po create mode 100644 po/sr@latin/kwin.po create mode 100644 po/sr@latin/kwin_clients.po create mode 100644 po/sr@latin/kwin_effects.po create mode 100644 po/sr@latin/kwin_scripting.po create mode 100644 po/sr@latin/kwin_scripts.po create mode 100644 po/sv/docs/kcontrol/desktop/index.docbook create mode 100644 po/sv/docs/kcontrol/kwindecoration/index.docbook create mode 100644 po/sv/docs/kcontrol/kwineffects/index.docbook create mode 100644 po/sv/docs/kcontrol/kwinscreenedges/index.docbook create mode 100644 po/sv/docs/kcontrol/kwintabbox/index.docbook create mode 100644 po/sv/docs/kcontrol/kwintouchscreen/index.docbook create mode 100644 po/sv/docs/kcontrol/kwinvirtualkeyboard/index.docbook create mode 100644 po/sv/docs/kcontrol/windowbehaviour/index.docbook create mode 100644 po/sv/docs/kcontrol/windowspecific/index.docbook create mode 100644 po/sv/kcm_kwin_effects.po create mode 100644 po/sv/kcm_kwin_scripts.po create mode 100644 po/sv/kcm_kwin_virtualdesktops.po create mode 100644 po/sv/kcm_kwindecoration.po create mode 100644 po/sv/kcm_kwinrules.po create mode 100644 po/sv/kcm_kwintabbox.po create mode 100644 po/sv/kcm_virtualkeyboard.po create mode 100644 po/sv/kcmkwincommon.po create mode 100644 po/sv/kcmkwincompositing.po create mode 100644 po/sv/kcmkwinscreenedges.po create mode 100644 po/sv/kcmkwm.po create mode 100644 po/sv/kwin.po create mode 100644 po/sv/kwin_clients.po create mode 100644 po/sv/kwin_effects.po create mode 100644 po/sv/kwin_scripting.po create mode 100644 po/sv/kwin_scripts.po create mode 100644 po/ta/kcm_kwin_effects.po create mode 100644 po/ta/kcm_kwin_scripts.po create mode 100644 po/ta/kcm_kwin_virtualdesktops.po create mode 100644 po/ta/kcm_kwindecoration.po create mode 100644 po/ta/kcm_kwinrules.po create mode 100644 po/ta/kcm_kwintabbox.po create mode 100644 po/ta/kcm_virtualkeyboard.po create mode 100644 po/ta/kcmkwincommon.po create mode 100644 po/ta/kcmkwincompositing.po create mode 100644 po/ta/kcmkwinscreenedges.po create mode 100644 po/ta/kcmkwm.po create mode 100644 po/ta/kwin.po create mode 100644 po/ta/kwin_clients.po create mode 100644 po/ta/kwin_effects.po create mode 100644 po/ta/kwin_scripting.po create mode 100644 po/ta/kwin_scripts.po create mode 100644 po/te/kcm_kwin_virtualdesktops.po create mode 100644 po/te/kcm_kwindecoration.po create mode 100644 po/te/kcm_kwinrules.po create mode 100644 po/te/kcmkwincompositing.po create mode 100644 po/te/kcmkwm.po create mode 100644 po/te/kwin.po create mode 100644 po/te/kwin_clients.po create mode 100644 po/te/kwin_effects.po create mode 100644 po/tg/kcm_kwin_virtualdesktops.po create mode 100644 po/tg/kcm_kwindecoration.po create mode 100644 po/tg/kcm_kwinrules.po create mode 100644 po/tg/kcmkwincommon.po create mode 100644 po/tg/kcmkwincompositing.po create mode 100644 po/tg/kcmkwinscreenedges.po create mode 100644 po/tg/kcmkwm.po create mode 100644 po/tg/kwin.po create mode 100644 po/tg/kwin_clients.po create mode 100644 po/tg/kwin_effects.po create mode 100644 po/th/kcm_kwin_virtualdesktops.po create mode 100644 po/th/kcm_kwindecoration.po create mode 100644 po/th/kcm_kwinrules.po create mode 100644 po/th/kcm_kwintabbox.po create mode 100644 po/th/kcmkwincompositing.po create mode 100644 po/th/kcmkwinscreenedges.po create mode 100644 po/th/kcmkwm.po create mode 100644 po/th/kwin.po create mode 100644 po/th/kwin_clients.po create mode 100644 po/th/kwin_effects.po create mode 100644 po/tr/kcm_kwin_effects.po create mode 100644 po/tr/kcm_kwin_scripts.po create mode 100644 po/tr/kcm_kwin_virtualdesktops.po create mode 100644 po/tr/kcm_kwindecoration.po create mode 100644 po/tr/kcm_kwinrules.po create mode 100644 po/tr/kcm_kwintabbox.po create mode 100644 po/tr/kcm_virtualkeyboard.po create mode 100644 po/tr/kcmkwincommon.po create mode 100644 po/tr/kcmkwincompositing.po create mode 100644 po/tr/kcmkwinscreenedges.po create mode 100644 po/tr/kcmkwm.po create mode 100644 po/tr/kwin.po create mode 100644 po/tr/kwin_clients.po create mode 100644 po/tr/kwin_effects.po create mode 100644 po/tr/kwin_scripting.po create mode 100644 po/tr/kwin_scripts.po create mode 100644 po/ug/kcm_kwin_scripts.po create mode 100644 po/ug/kcm_kwin_virtualdesktops.po create mode 100644 po/ug/kcm_kwindecoration.po create mode 100644 po/ug/kcm_kwinrules.po create mode 100644 po/ug/kcm_kwintabbox.po create mode 100644 po/ug/kcmkwincompositing.po create mode 100644 po/ug/kcmkwinscreenedges.po create mode 100644 po/ug/kcmkwm.po create mode 100644 po/ug/kwin.po create mode 100644 po/ug/kwin_clients.po create mode 100644 po/ug/kwin_effects.po create mode 100644 po/ug/kwin_scripting.po create mode 100644 po/uk/docs/kcontrol/desktop/index.docbook create mode 100644 po/uk/docs/kcontrol/kwindecoration/button.png create mode 100644 po/uk/docs/kcontrol/kwindecoration/decoration.png create mode 100644 po/uk/docs/kcontrol/kwindecoration/index.docbook create mode 100644 po/uk/docs/kcontrol/kwindecoration/main.png create mode 100644 po/uk/docs/kcontrol/kwineffects/index.docbook create mode 100644 po/uk/docs/kcontrol/kwinscreenedges/index.docbook create mode 100644 po/uk/docs/kcontrol/kwintabbox/index.docbook create mode 100644 po/uk/docs/kcontrol/kwintouchscreen/index.docbook create mode 100644 po/uk/docs/kcontrol/kwinvirtualkeyboard/index.docbook create mode 100644 po/uk/docs/kcontrol/windowbehaviour/index.docbook create mode 100644 po/uk/docs/kcontrol/windowspecific/index.docbook create mode 100644 po/uk/kcm_kwin_effects.po create mode 100644 po/uk/kcm_kwin_scripts.po create mode 100644 po/uk/kcm_kwin_virtualdesktops.po create mode 100644 po/uk/kcm_kwindecoration.po create mode 100644 po/uk/kcm_kwinrules.po create mode 100644 po/uk/kcm_kwintabbox.po create mode 100644 po/uk/kcm_virtualkeyboard.po create mode 100644 po/uk/kcmkwincommon.po create mode 100644 po/uk/kcmkwincompositing.po create mode 100644 po/uk/kcmkwinscreenedges.po create mode 100644 po/uk/kcmkwm.po create mode 100644 po/uk/kwin.po create mode 100644 po/uk/kwin_clients.po create mode 100644 po/uk/kwin_effects.po create mode 100644 po/uk/kwin_scripting.po create mode 100644 po/uk/kwin_scripts.po create mode 100644 po/uz/kcm_kwindecoration.po create mode 100644 po/uz/kcm_kwinrules.po create mode 100644 po/uz/kcmkwm.po create mode 100644 po/uz/kwin.po create mode 100644 po/uz/kwin_clients.po create mode 100644 po/uz@cyrillic/kcm_kwindecoration.po create mode 100644 po/uz@cyrillic/kcm_kwinrules.po create mode 100644 po/uz@cyrillic/kcmkwm.po create mode 100644 po/uz@cyrillic/kwin.po create mode 100644 po/uz@cyrillic/kwin_clients.po create mode 100644 po/vi/kcm_kwin_effects.po create mode 100644 po/vi/kcm_kwin_scripts.po create mode 100644 po/vi/kcm_kwin_virtualdesktops.po create mode 100644 po/vi/kcm_kwindecoration.po create mode 100644 po/vi/kcm_kwinrules.po create mode 100644 po/vi/kcm_kwintabbox.po create mode 100644 po/vi/kcm_virtualkeyboard.po create mode 100644 po/vi/kcmkwincommon.po create mode 100644 po/vi/kcmkwincompositing.po create mode 100644 po/vi/kcmkwinscreenedges.po create mode 100644 po/vi/kcmkwm.po create mode 100644 po/vi/kwin.po create mode 100644 po/vi/kwin_clients.po create mode 100644 po/vi/kwin_effects.po create mode 100644 po/vi/kwin_scripting.po create mode 100644 po/vi/kwin_scripts.po create mode 100644 po/wa/kcm_kwin_virtualdesktops.po create mode 100644 po/wa/kcm_kwindecoration.po create mode 100644 po/wa/kcm_kwinrules.po create mode 100644 po/wa/kcm_kwintabbox.po create mode 100644 po/wa/kcmkwincompositing.po create mode 100644 po/wa/kcmkwinscreenedges.po create mode 100644 po/wa/kcmkwm.po create mode 100644 po/wa/kwin.po create mode 100644 po/wa/kwin_clients.po create mode 100644 po/wa/kwin_effects.po create mode 100644 po/xh/kcm_kwindecoration.po create mode 100644 po/xh/kcmkwm.po create mode 100644 po/xh/kwin.po create mode 100644 po/zh_CN/kcm_kwin_effects.po create mode 100644 po/zh_CN/kcm_kwin_scripts.po create mode 100644 po/zh_CN/kcm_kwin_virtualdesktops.po create mode 100644 po/zh_CN/kcm_kwindecoration.po create mode 100644 po/zh_CN/kcm_kwinrules.po create mode 100644 po/zh_CN/kcm_kwintabbox.po create mode 100644 po/zh_CN/kcm_virtualkeyboard.po create mode 100644 po/zh_CN/kcmkwincommon.po create mode 100644 po/zh_CN/kcmkwincompositing.po create mode 100644 po/zh_CN/kcmkwinscreenedges.po create mode 100644 po/zh_CN/kcmkwm.po create mode 100644 po/zh_CN/kwin.po create mode 100644 po/zh_CN/kwin_clients.po create mode 100644 po/zh_CN/kwin_effects.po create mode 100644 po/zh_CN/kwin_scripting.po create mode 100644 po/zh_CN/kwin_scripts.po create mode 100644 po/zh_TW/kcm_kwin_effects.po create mode 100644 po/zh_TW/kcm_kwin_scripts.po create mode 100644 po/zh_TW/kcm_kwin_virtualdesktops.po create mode 100644 po/zh_TW/kcm_kwindecoration.po create mode 100644 po/zh_TW/kcm_kwinrules.po create mode 100644 po/zh_TW/kcm_kwintabbox.po create mode 100644 po/zh_TW/kcm_virtualkeyboard.po create mode 100644 po/zh_TW/kcmkwincommon.po create mode 100644 po/zh_TW/kcmkwincompositing.po create mode 100644 po/zh_TW/kcmkwinscreenedges.po create mode 100644 po/zh_TW/kcmkwm.po create mode 100644 po/zh_TW/kwin.po create mode 100644 po/zh_TW/kwin_clients.po create mode 100644 po/zh_TW/kwin_effects.po create mode 100644 po/zh_TW/kwin_scripting.po create mode 100644 po/zh_TW/kwin_scripts.po create mode 100644 src/3rdparty/colortemperature.h create mode 100644 src/3rdparty/xcursor.c create mode 100644 src/3rdparty/xcursor.h create mode 100644 src/CMakeLists.txt create mode 100644 src/Messages.sh create mode 100644 src/activation.cpp create mode 100644 src/activities.cpp create mode 100644 src/activities.h create mode 100644 src/appmenu.cpp create mode 100644 src/appmenu.h create mode 100644 src/atoms.cpp create mode 100644 src/atoms.h create mode 100644 src/backends/CMakeLists.txt create mode 100644 src/backends/drm/CMakeLists.txt create mode 100644 src/backends/drm/drm_abstract_output.cpp create mode 100644 src/backends/drm/drm_abstract_output.h create mode 100644 src/backends/drm/drm_backend.cpp create mode 100644 src/backends/drm/drm_backend.h create mode 100644 src/backends/drm/drm_buffer.cpp create mode 100644 src/backends/drm/drm_buffer.h create mode 100644 src/backends/drm/drm_buffer_gbm.cpp create mode 100644 src/backends/drm/drm_buffer_gbm.h create mode 100644 src/backends/drm/drm_dmabuf_feedback.cpp create mode 100644 src/backends/drm/drm_dmabuf_feedback.h create mode 100644 src/backends/drm/drm_dumb_buffer.cpp create mode 100644 src/backends/drm/drm_dumb_buffer.h create mode 100644 src/backends/drm/drm_dumb_swapchain.cpp create mode 100644 src/backends/drm/drm_dumb_swapchain.h create mode 100644 src/backends/drm/drm_egl_backend.cpp create mode 100644 src/backends/drm/drm_egl_backend.h create mode 100644 src/backends/drm/drm_egl_cursor_layer.cpp create mode 100644 src/backends/drm/drm_egl_cursor_layer.h create mode 100644 src/backends/drm/drm_egl_layer.cpp create mode 100644 src/backends/drm/drm_egl_layer.h create mode 100644 src/backends/drm/drm_egl_layer_surface.cpp create mode 100644 src/backends/drm/drm_egl_layer_surface.h create mode 100644 src/backends/drm/drm_gbm_surface.cpp create mode 100644 src/backends/drm/drm_gbm_surface.h create mode 100644 src/backends/drm/drm_gpu.cpp create mode 100644 src/backends/drm/drm_gpu.h create mode 100644 src/backends/drm/drm_layer.cpp create mode 100644 src/backends/drm/drm_layer.h create mode 100644 src/backends/drm/drm_logging.cpp create mode 100644 src/backends/drm/drm_logging.h create mode 100644 src/backends/drm/drm_object.cpp create mode 100644 src/backends/drm/drm_object.h create mode 100644 src/backends/drm/drm_object_connector.cpp create mode 100644 src/backends/drm/drm_object_connector.h create mode 100644 src/backends/drm/drm_object_crtc.cpp create mode 100644 src/backends/drm/drm_object_crtc.h create mode 100644 src/backends/drm/drm_object_plane.cpp create mode 100644 src/backends/drm/drm_object_plane.h create mode 100644 src/backends/drm/drm_output.cpp create mode 100644 src/backends/drm/drm_output.h create mode 100644 src/backends/drm/drm_pipeline.cpp create mode 100644 src/backends/drm/drm_pipeline.h create mode 100644 src/backends/drm/drm_pipeline_legacy.cpp create mode 100644 src/backends/drm/drm_pointer.h create mode 100644 src/backends/drm/drm_property.cpp create mode 100644 src/backends/drm/drm_property.h create mode 100644 src/backends/drm/drm_qpainter_backend.cpp create mode 100644 src/backends/drm/drm_qpainter_backend.h create mode 100644 src/backends/drm/drm_qpainter_layer.cpp create mode 100644 src/backends/drm/drm_qpainter_layer.h create mode 100644 src/backends/drm/drm_render_backend.h create mode 100644 src/backends/drm/drm_shadow_buffer.cpp create mode 100644 src/backends/drm/drm_shadow_buffer.h create mode 100644 src/backends/drm/drm_virtual_egl_layer.cpp create mode 100644 src/backends/drm/drm_virtual_egl_layer.h create mode 100644 src/backends/drm/drm_virtual_output.cpp create mode 100644 src/backends/drm/drm_virtual_output.h create mode 100644 src/backends/drm/gbm_dmabuf.h create mode 100644 src/backends/drm/overview.md create mode 100644 src/backends/fakeinput/CMakeLists.txt create mode 100644 src/backends/fakeinput/fakeinputbackend.cpp create mode 100644 src/backends/fakeinput/fakeinputbackend.h create mode 100644 src/backends/fakeinput/fakeinputdevice.cpp create mode 100644 src/backends/fakeinput/fakeinputdevice.h create mode 100644 src/backends/libinput/CMakeLists.txt create mode 100644 src/backends/libinput/connection.cpp create mode 100644 src/backends/libinput/connection.h create mode 100644 src/backends/libinput/context.cpp create mode 100644 src/backends/libinput/context.h create mode 100644 src/backends/libinput/device.cpp create mode 100644 src/backends/libinput/device.h create mode 100644 src/backends/libinput/events.cpp create mode 100644 src/backends/libinput/events.h create mode 100644 src/backends/libinput/libinput_logging.cpp create mode 100644 src/backends/libinput/libinput_logging.h create mode 100644 src/backends/libinput/libinputbackend.cpp create mode 100644 src/backends/libinput/libinputbackend.h create mode 100644 src/backends/virtual/CMakeLists.txt create mode 100644 src/backends/virtual/virtual_backend.cpp create mode 100644 src/backends/virtual/virtual_backend.h create mode 100644 src/backends/virtual/virtual_egl_backend.cpp create mode 100644 src/backends/virtual/virtual_egl_backend.h create mode 100644 src/backends/virtual/virtual_logging.cpp create mode 100644 src/backends/virtual/virtual_logging.h create mode 100644 src/backends/virtual/virtual_output.cpp create mode 100644 src/backends/virtual/virtual_output.h create mode 100644 src/backends/virtual/virtual_qpainter_backend.cpp create mode 100644 src/backends/virtual/virtual_qpainter_backend.h create mode 100644 src/backends/wayland/CMakeLists.txt create mode 100644 src/backends/wayland/wayland_backend.cpp create mode 100644 src/backends/wayland/wayland_backend.h create mode 100644 src/backends/wayland/wayland_egl_backend.cpp create mode 100644 src/backends/wayland/wayland_egl_backend.h create mode 100644 src/backends/wayland/wayland_logging.cpp create mode 100644 src/backends/wayland/wayland_logging.h create mode 100644 src/backends/wayland/wayland_output.cpp create mode 100644 src/backends/wayland/wayland_output.h create mode 100644 src/backends/wayland/wayland_qpainter_backend.cpp create mode 100644 src/backends/wayland/wayland_qpainter_backend.h create mode 100644 src/backends/x11/CMakeLists.txt create mode 100644 src/backends/x11/common/CMakeLists.txt create mode 100644 src/backends/x11/common/ge_event_mem_mover.h create mode 100644 src/backends/x11/common/kwinxrenderutils.cpp create mode 100644 src/backends/x11/common/kwinxrenderutils.h create mode 100644 src/backends/x11/common/x11_common_egl_backend.cpp create mode 100644 src/backends/x11/common/x11_common_egl_backend.h create mode 100644 src/backends/x11/common/x11_common_logging.cpp create mode 100644 src/backends/x11/common/x11_common_logging_p.h create mode 100644 src/backends/x11/standalone/CMakeLists.txt create mode 100644 src/backends/x11/standalone/x11_standalone_cursor.cpp create mode 100644 src/backends/x11/standalone/x11_standalone_cursor.h create mode 100644 src/backends/x11/standalone/x11_standalone_edge.cpp create mode 100644 src/backends/x11/standalone/x11_standalone_edge.h create mode 100644 src/backends/x11/standalone/x11_standalone_effects.cpp create mode 100644 src/backends/x11/standalone/x11_standalone_effects.h create mode 100644 src/backends/x11/standalone/x11_standalone_effects_mouse_interception_filter.cpp create mode 100644 src/backends/x11/standalone/x11_standalone_effects_mouse_interception_filter.h create mode 100644 src/backends/x11/standalone/x11_standalone_egl_backend.cpp create mode 100644 src/backends/x11/standalone/x11_standalone_egl_backend.h create mode 100644 src/backends/x11/standalone/x11_standalone_glx_backend.cpp create mode 100644 src/backends/x11/standalone/x11_standalone_glx_backend.h create mode 100644 src/backends/x11/standalone/x11_standalone_glx_context_attribute_builder.cpp create mode 100644 src/backends/x11/standalone/x11_standalone_glx_context_attribute_builder.h create mode 100644 src/backends/x11/standalone/x11_standalone_glxconvenience.cpp create mode 100644 src/backends/x11/standalone/x11_standalone_glxconvenience.h create mode 100644 src/backends/x11/standalone/x11_standalone_logging.cpp create mode 100644 src/backends/x11/standalone/x11_standalone_logging.h create mode 100644 src/backends/x11/standalone/x11_standalone_non_composited_outline.cpp create mode 100644 src/backends/x11/standalone/x11_standalone_non_composited_outline.h create mode 100644 src/backends/x11/standalone/x11_standalone_omlsynccontrolvsyncmonitor.cpp create mode 100644 src/backends/x11/standalone/x11_standalone_omlsynccontrolvsyncmonitor.h create mode 100644 src/backends/x11/standalone/x11_standalone_output.cpp create mode 100644 src/backends/x11/standalone/x11_standalone_output.h create mode 100644 src/backends/x11/standalone/x11_standalone_overlaywindow.cpp create mode 100644 src/backends/x11/standalone/x11_standalone_overlaywindow.h create mode 100644 src/backends/x11/standalone/x11_standalone_placeholderoutput.cpp create mode 100644 src/backends/x11/standalone/x11_standalone_placeholderoutput.h create mode 100644 src/backends/x11/standalone/x11_standalone_platform.cpp create mode 100644 src/backends/x11/standalone/x11_standalone_platform.h create mode 100644 src/backends/x11/standalone/x11_standalone_screenedges_filter.cpp create mode 100644 src/backends/x11/standalone/x11_standalone_screenedges_filter.h create mode 100644 src/backends/x11/standalone/x11_standalone_sgivideosyncvsyncmonitor.cpp create mode 100644 src/backends/x11/standalone/x11_standalone_sgivideosyncvsyncmonitor.h create mode 100644 src/backends/x11/standalone/x11_standalone_windowselector.cpp create mode 100644 src/backends/x11/standalone/x11_standalone_windowselector.h create mode 100644 src/backends/x11/standalone/x11_standalone_xfixes_cursor_event_filter.cpp create mode 100644 src/backends/x11/standalone/x11_standalone_xfixes_cursor_event_filter.h create mode 100644 src/backends/x11/standalone/x11_standalone_xinputintegration.cpp create mode 100644 src/backends/x11/standalone/x11_standalone_xinputintegration.h create mode 100644 src/backends/x11/windowed/CMakeLists.txt create mode 100644 src/backends/x11/windowed/x11.json create mode 100644 src/backends/x11/windowed/x11_windowed_backend.cpp create mode 100644 src/backends/x11/windowed/x11_windowed_backend.h create mode 100644 src/backends/x11/windowed/x11_windowed_egl_backend.cpp create mode 100644 src/backends/x11/windowed/x11_windowed_egl_backend.h create mode 100644 src/backends/x11/windowed/x11_windowed_logging.cpp create mode 100644 src/backends/x11/windowed/x11_windowed_logging.h create mode 100644 src/backends/x11/windowed/x11_windowed_output.cpp create mode 100644 src/backends/x11/windowed/x11_windowed_output.h create mode 100644 src/backends/x11/windowed/x11_windowed_qpainter_backend.cpp create mode 100644 src/backends/x11/windowed/x11_windowed_qpainter_backend.h create mode 100644 src/client_machine.cpp create mode 100644 src/client_machine.h create mode 100644 src/colors/colordevice.cpp create mode 100644 src/colors/colordevice.h create mode 100644 src/colors/colormanager.cpp create mode 100644 src/colors/colormanager.h create mode 100644 src/composite.cpp create mode 100644 src/composite.h create mode 100644 src/config-kwin.h.cmake create mode 100644 src/core/colorlut.cpp create mode 100644 src/core/colorlut.h create mode 100644 src/core/colorpipelinestage.cpp create mode 100644 src/core/colorpipelinestage.h create mode 100644 src/core/colortransformation.cpp create mode 100644 src/core/colortransformation.h create mode 100644 src/core/dmabufattributes.h create mode 100644 src/core/inputbackend.cpp create mode 100644 src/core/inputbackend.h create mode 100644 src/core/inputdevice.cpp create mode 100644 src/core/inputdevice.h create mode 100644 src/core/output.cpp create mode 100644 src/core/output.h create mode 100644 src/core/outputconfiguration.cpp create mode 100644 src/core/outputconfiguration.h create mode 100644 src/core/outputlayer.cpp create mode 100644 src/core/outputlayer.h create mode 100644 src/core/overlaywindow.cpp create mode 100644 src/core/overlaywindow.h create mode 100644 src/core/platform.cpp create mode 100644 src/core/platform.h create mode 100644 src/core/renderbackend.cpp create mode 100644 src/core/renderbackend.h create mode 100644 src/core/renderjournal.cpp create mode 100644 src/core/renderjournal.h create mode 100644 src/core/renderlayer.cpp create mode 100644 src/core/renderlayer.h create mode 100644 src/core/renderlayerdelegate.cpp create mode 100644 src/core/renderlayerdelegate.h create mode 100644 src/core/renderloop.cpp create mode 100644 src/core/renderloop.h create mode 100644 src/core/renderloop_p.h create mode 100644 src/core/rendertarget.cpp create mode 100644 src/core/rendertarget.h create mode 100644 src/core/session.cpp create mode 100644 src/core/session.h create mode 100644 src/core/session_consolekit.cpp create mode 100644 src/core/session_consolekit.h create mode 100644 src/core/session_logind.cpp create mode 100644 src/core/session_logind.h create mode 100644 src/core/session_noop.cpp create mode 100644 src/core/session_noop.h create mode 100644 src/cursor.cpp create mode 100644 src/cursor.h create mode 100644 src/cursordelegate_opengl.cpp create mode 100644 src/cursordelegate_opengl.h create mode 100644 src/cursordelegate_qpainter.cpp create mode 100644 src/cursordelegate_qpainter.h create mode 100644 src/dbusinterface.cpp create mode 100644 src/dbusinterface.h create mode 100644 src/debug_console.cpp create mode 100644 src/debug_console.h create mode 100644 src/debug_console.ui create mode 100644 src/decorationitem.cpp create mode 100644 src/decorationitem.h create mode 100644 src/decorations/decoratedclient.cpp create mode 100644 src/decorations/decoratedclient.h create mode 100644 src/decorations/decorationbridge.cpp create mode 100644 src/decorations/decorationbridge.h create mode 100644 src/decorations/decorationpalette.cpp create mode 100644 src/decorations/decorationpalette.h create mode 100644 src/decorations/decorations_logging.cpp create mode 100644 src/decorations/decorations_logging.h create mode 100644 src/decorations/settings.cpp create mode 100644 src/decorations/settings.h create mode 100644 src/deleted.cpp create mode 100644 src/deleted.h create mode 100644 src/dmabuftexture.cpp create mode 100644 src/dmabuftexture.h create mode 100644 src/dpmsinputeventfilter.cpp create mode 100644 src/dpmsinputeventfilter.h create mode 100644 src/effectloader.cpp create mode 100644 src/effectloader.h create mode 100644 src/effects.cpp create mode 100644 src/effects.h create mode 100644 src/effects/CMakeLists.txt create mode 100644 src/effects/Messages.sh create mode 100644 src/effects/backgroundcontrast/.directory create mode 100644 src/effects/backgroundcontrast/CMakeLists.txt create mode 100644 src/effects/backgroundcontrast/contrast.cpp create mode 100644 src/effects/backgroundcontrast/contrast.h create mode 100644 src/effects/backgroundcontrast/contrastshader.cpp create mode 100644 src/effects/backgroundcontrast/contrastshader.h create mode 100644 src/effects/backgroundcontrast/main.cpp create mode 100644 src/effects/backgroundcontrast/metadata.json create mode 100644 src/effects/blendchanges/CMakeLists.txt create mode 100644 src/effects/blendchanges/blendchanges.cpp create mode 100644 src/effects/blendchanges/blendchanges.h create mode 100644 src/effects/blendchanges/main.cpp create mode 100644 src/effects/blendchanges/metadata.json create mode 100644 src/effects/blur/CMakeLists.txt create mode 100644 src/effects/blur/blur.cpp create mode 100644 src/effects/blur/blur.h create mode 100644 src/effects/blur/blur.kcfg create mode 100644 src/effects/blur/blur.qrc create mode 100644 src/effects/blur/blur_config.cpp create mode 100644 src/effects/blur/blur_config.h create mode 100644 src/effects/blur/blur_config.ui create mode 100644 src/effects/blur/blurconfig.kcfgc create mode 100644 src/effects/blur/blurshader.cpp create mode 100644 src/effects/blur/blurshader.h create mode 100644 src/effects/blur/main.cpp create mode 100644 src/effects/blur/metadata.json create mode 100644 src/effects/blur/shaders/copy.frag create mode 100644 src/effects/blur/shaders/copy_core.frag create mode 100644 src/effects/blur/shaders/downsample.frag create mode 100644 src/effects/blur/shaders/downsample_core.frag create mode 100644 src/effects/blur/shaders/noise.frag create mode 100644 src/effects/blur/shaders/noise_core.frag create mode 100644 src/effects/blur/shaders/upsample.frag create mode 100644 src/effects/blur/shaders/upsample_core.frag create mode 100644 src/effects/blur/shaders/vertex.vert create mode 100644 src/effects/blur/shaders/vertex_core.vert create mode 100644 src/effects/colorpicker/CMakeLists.txt create mode 100644 src/effects/colorpicker/colorpicker.cpp create mode 100644 src/effects/colorpicker/colorpicker.h create mode 100644 src/effects/colorpicker/main.cpp create mode 100644 src/effects/colorpicker/metadata.json create mode 100644 src/effects/desktopgrid/CMakeLists.txt create mode 100644 src/effects/desktopgrid/desktopgrid_config.cpp create mode 100644 src/effects/desktopgrid/desktopgrid_config.h create mode 100644 src/effects/desktopgrid/desktopgrid_config.ui create mode 100644 src/effects/desktopgrid/desktopgridconfig.kcfg create mode 100644 src/effects/desktopgrid/desktopgridconfig.kcfgc create mode 100644 src/effects/desktopgrid/desktopgrideffect.cpp create mode 100644 src/effects/desktopgrid/desktopgrideffect.h create mode 100644 src/effects/desktopgrid/main.cpp create mode 100644 src/effects/desktopgrid/metadata.json create mode 100644 src/effects/desktopgrid/qml/DesktopView.qml create mode 100644 src/effects/desktopgrid/qml/main.qml create mode 100644 src/effects/dialogparent/package/contents/code/main.js create mode 100644 src/effects/dialogparent/package/metadata.desktop create mode 100644 src/effects/diminactive/CMakeLists.txt create mode 100644 src/effects/diminactive/diminactive.cpp create mode 100644 src/effects/diminactive/diminactive.h create mode 100644 src/effects/diminactive/diminactive.kcfg create mode 100644 src/effects/diminactive/diminactive_config.cpp create mode 100644 src/effects/diminactive/diminactive_config.h create mode 100644 src/effects/diminactive/diminactive_config.ui create mode 100644 src/effects/diminactive/diminactiveconfig.kcfgc create mode 100644 src/effects/diminactive/main.cpp create mode 100644 src/effects/diminactive/metadata.json create mode 100644 src/effects/dimscreen/package/contents/code/main.js create mode 100644 src/effects/dimscreen/package/metadata.desktop create mode 100644 src/effects/eyeonscreen/package/contents/code/main.js create mode 100644 src/effects/eyeonscreen/package/metadata.desktop create mode 100644 src/effects/fade/package/contents/code/main.js create mode 100644 src/effects/fade/package/contents/config/main.xml create mode 100644 src/effects/fade/package/metadata.desktop create mode 100644 src/effects/fadedesktop/package/contents/code/main.js create mode 100644 src/effects/fadedesktop/package/metadata.desktop create mode 100644 src/effects/fadingpopups/package/contents/code/main.js create mode 100644 src/effects/fadingpopups/package/metadata.desktop create mode 100644 src/effects/fallapart/CMakeLists.txt create mode 100644 src/effects/fallapart/fallapart.cpp create mode 100644 src/effects/fallapart/fallapart.h create mode 100644 src/effects/fallapart/fallapart.kcfg create mode 100644 src/effects/fallapart/fallapartconfig.kcfgc create mode 100644 src/effects/fallapart/main.cpp create mode 100644 src/effects/fallapart/metadata.json create mode 100644 src/effects/frozenapp/package/contents/code/main.js create mode 100644 src/effects/frozenapp/package/metadata.desktop create mode 100644 src/effects/fullscreen/package/contents/code/fullscreen.js create mode 100644 src/effects/fullscreen/package/metadata.desktop create mode 100644 src/effects/glide/CMakeLists.txt create mode 100644 src/effects/glide/glide.cpp create mode 100644 src/effects/glide/glide.h create mode 100644 src/effects/glide/glide.kcfg create mode 100644 src/effects/glide/glide_config.cpp create mode 100644 src/effects/glide/glide_config.h create mode 100644 src/effects/glide/glide_config.ui create mode 100644 src/effects/glide/glideconfig.kcfgc create mode 100644 src/effects/glide/main.cpp create mode 100644 src/effects/glide/metadata.json create mode 100644 src/effects/highlightwindow/CMakeLists.txt create mode 100644 src/effects/highlightwindow/highlightwindow.cpp create mode 100644 src/effects/highlightwindow/highlightwindow.h create mode 100644 src/effects/highlightwindow/main.cpp create mode 100644 src/effects/highlightwindow/metadata.json create mode 100644 src/effects/invert/CMakeLists.txt create mode 100644 src/effects/invert/invert.cpp create mode 100644 src/effects/invert/invert.h create mode 100644 src/effects/invert/invert.qrc create mode 100644 src/effects/invert/invert_config.cpp create mode 100644 src/effects/invert/invert_config.h create mode 100644 src/effects/invert/main.cpp create mode 100644 src/effects/invert/metadata.json create mode 100644 src/effects/invert/shaders/invert.frag create mode 100644 src/effects/invert/shaders/invert_core.frag create mode 100644 src/effects/kscreen/CMakeLists.txt create mode 100644 src/effects/kscreen/kscreen.cpp create mode 100644 src/effects/kscreen/kscreen.h create mode 100644 src/effects/kscreen/kscreen.kcfg create mode 100644 src/effects/kscreen/kscreenconfig.kcfgc create mode 100644 src/effects/kscreen/main.cpp create mode 100644 src/effects/kscreen/metadata.json create mode 100644 src/effects/kwineffect.desktop create mode 100644 src/effects/login/package/contents/code/main.js create mode 100644 src/effects/login/package/contents/config/main.xml create mode 100644 src/effects/login/package/contents/ui/config.ui create mode 100644 src/effects/login/package/metadata.desktop create mode 100644 src/effects/logout/package/contents/code/main.js create mode 100644 src/effects/logout/package/metadata.desktop create mode 100644 src/effects/magiclamp/CMakeLists.txt create mode 100644 src/effects/magiclamp/magiclamp.cpp create mode 100644 src/effects/magiclamp/magiclamp.h create mode 100644 src/effects/magiclamp/magiclamp.kcfg create mode 100644 src/effects/magiclamp/magiclamp_config.cpp create mode 100644 src/effects/magiclamp/magiclamp_config.h create mode 100644 src/effects/magiclamp/magiclamp_config.ui create mode 100644 src/effects/magiclamp/magiclampconfig.kcfgc create mode 100644 src/effects/magiclamp/main.cpp create mode 100644 src/effects/magiclamp/metadata.json create mode 100644 src/effects/magnifier/CMakeLists.txt create mode 100644 src/effects/magnifier/magnifier.cpp create mode 100644 src/effects/magnifier/magnifier.h create mode 100644 src/effects/magnifier/magnifier.kcfg create mode 100644 src/effects/magnifier/magnifier_config.cpp create mode 100644 src/effects/magnifier/magnifier_config.h create mode 100644 src/effects/magnifier/magnifier_config.ui create mode 100644 src/effects/magnifier/magnifierconfig.kcfgc create mode 100644 src/effects/magnifier/main.cpp create mode 100644 src/effects/magnifier/metadata.json create mode 100644 src/effects/maximize/package/contents/code/maximize.js create mode 100644 src/effects/maximize/package/metadata.desktop create mode 100644 src/effects/morphingpopups/package/contents/code/morphingpopups.js create mode 100644 src/effects/morphingpopups/package/metadata.desktop create mode 100644 src/effects/mouseclick/CMakeLists.txt create mode 100644 src/effects/mouseclick/main.cpp create mode 100644 src/effects/mouseclick/metadata.json create mode 100644 src/effects/mouseclick/mouseclick.cpp create mode 100644 src/effects/mouseclick/mouseclick.h create mode 100644 src/effects/mouseclick/mouseclick.kcfg create mode 100644 src/effects/mouseclick/mouseclick_config.cpp create mode 100644 src/effects/mouseclick/mouseclick_config.h create mode 100644 src/effects/mouseclick/mouseclick_config.ui create mode 100644 src/effects/mouseclick/mouseclickconfig.kcfgc create mode 100644 src/effects/mousemark/CMakeLists.txt create mode 100644 src/effects/mousemark/main.cpp create mode 100644 src/effects/mousemark/metadata.json create mode 100644 src/effects/mousemark/mousemark.cpp create mode 100644 src/effects/mousemark/mousemark.h create mode 100644 src/effects/mousemark/mousemark.kcfg create mode 100644 src/effects/mousemark/mousemark_config.cpp create mode 100644 src/effects/mousemark/mousemark_config.h create mode 100644 src/effects/mousemark/mousemark_config.ui create mode 100644 src/effects/mousemark/mousemarkconfig.kcfgc create mode 100644 src/effects/outputlocator/CMakeLists.txt create mode 100644 src/effects/outputlocator/main.cpp create mode 100644 src/effects/outputlocator/metadata.json create mode 100644 src/effects/outputlocator/outputlocator.cpp create mode 100644 src/effects/outputlocator/outputlocator.h create mode 100644 src/effects/outputlocator/qml/OutputLabel.qml create mode 100644 src/effects/overview/CMakeLists.txt create mode 100644 src/effects/overview/kcm/CMakeLists.txt create mode 100644 src/effects/overview/kcm/overvieweffectkcm.cpp create mode 100644 src/effects/overview/kcm/overvieweffectkcm.h create mode 100644 src/effects/overview/kcm/overvieweffectkcm.ui create mode 100644 src/effects/overview/main.cpp create mode 100644 src/effects/overview/metadata.json create mode 100644 src/effects/overview/overviewconfig.kcfg create mode 100644 src/effects/overview/overviewconfig.kcfgc create mode 100644 src/effects/overview/overvieweffect.cpp create mode 100644 src/effects/overview/overvieweffect.h create mode 100644 src/effects/overview/qml/DesktopBar.qml create mode 100644 src/effects/overview/qml/DesktopView.qml create mode 100644 src/effects/overview/qml/ScreenView.qml create mode 100644 src/effects/private/CMakeLists.txt create mode 100644 src/effects/private/expoarea.cpp create mode 100644 src/effects/private/expoarea.h create mode 100644 src/effects/private/expolayout.cpp create mode 100644 src/effects/private/expolayout.h create mode 100644 src/effects/private/plugin.cpp create mode 100644 src/effects/private/plugin.h create mode 100644 src/effects/private/qml/WindowHeap.qml create mode 100644 src/effects/private/qml/WindowHeapDelegate.qml create mode 100644 src/effects/private/qmldir create mode 100644 src/effects/scale/package/contents/code/main.js create mode 100644 src/effects/scale/package/contents/config/main.xml create mode 100644 src/effects/scale/package/contents/ui/config.ui create mode 100644 src/effects/scale/package/metadata.desktop create mode 100644 src/effects/screenedge/CMakeLists.txt create mode 100644 src/effects/screenedge/main.cpp create mode 100644 src/effects/screenedge/metadata.json create mode 100644 src/effects/screenedge/screenedgeeffect.cpp create mode 100644 src/effects/screenedge/screenedgeeffect.h create mode 100644 src/effects/screenshot/CMakeLists.txt create mode 100644 src/effects/screenshot/main.cpp create mode 100644 src/effects/screenshot/metadata.json create mode 100644 src/effects/screenshot/org.kde.KWin.ScreenShot2.xml create mode 100644 src/effects/screenshot/screenshot.cpp create mode 100644 src/effects/screenshot/screenshot.h create mode 100644 src/effects/screenshot/screenshotdbusinterface1.cpp create mode 100644 src/effects/screenshot/screenshotdbusinterface1.h create mode 100644 src/effects/screenshot/screenshotdbusinterface2.cpp create mode 100644 src/effects/screenshot/screenshotdbusinterface2.h create mode 100644 src/effects/screentransform/CMakeLists.txt create mode 100644 src/effects/screentransform/main.cpp create mode 100644 src/effects/screentransform/metadata.json create mode 100644 src/effects/screentransform/screentransform.cpp create mode 100644 src/effects/screentransform/screentransform.h create mode 100644 src/effects/screentransform/screentransform.qrc create mode 100644 src/effects/screentransform/shaders/crossfade.frag create mode 100644 src/effects/screentransform/shaders/crossfade.vert create mode 100644 src/effects/screentransform/shaders/crossfade_core.frag create mode 100644 src/effects/screentransform/shaders/crossfade_core.vert create mode 100644 src/effects/sessionquit/package/contents/code/main.js create mode 100644 src/effects/sessionquit/package/metadata.desktop create mode 100644 src/effects/sheet/CMakeLists.txt create mode 100644 src/effects/sheet/main.cpp create mode 100644 src/effects/sheet/metadata.json create mode 100644 src/effects/sheet/sheet.cpp create mode 100644 src/effects/sheet/sheet.h create mode 100644 src/effects/sheet/sheet.kcfg create mode 100644 src/effects/sheet/sheetconfig.kcfgc create mode 100644 src/effects/showfps/CMakeLists.txt create mode 100644 src/effects/showfps/main.cpp create mode 100644 src/effects/showfps/metadata.json create mode 100644 src/effects/showfps/qml/main.qml create mode 100644 src/effects/showfps/showfpseffect.cpp create mode 100644 src/effects/showfps/showfpseffect.h create mode 100644 src/effects/showpaint/CMakeLists.txt create mode 100644 src/effects/showpaint/main.cpp create mode 100644 src/effects/showpaint/metadata.json create mode 100644 src/effects/showpaint/showpaint.cpp create mode 100644 src/effects/showpaint/showpaint.h create mode 100644 src/effects/showpaint/showpaint_config.cpp create mode 100644 src/effects/showpaint/showpaint_config.h create mode 100644 src/effects/showpaint/showpaint_config.ui create mode 100644 src/effects/slide/CMakeLists.txt create mode 100644 src/effects/slide/main.cpp create mode 100644 src/effects/slide/metadata.json create mode 100644 src/effects/slide/slide.cpp create mode 100644 src/effects/slide/slide.h create mode 100644 src/effects/slide/slide.kcfg create mode 100644 src/effects/slide/slide_config.cpp create mode 100644 src/effects/slide/slide_config.h create mode 100644 src/effects/slide/slide_config.ui create mode 100644 src/effects/slide/slideconfig.kcfgc create mode 100644 src/effects/slide/springmotion.cpp create mode 100644 src/effects/slide/springmotion.h create mode 100644 src/effects/slideback/CMakeLists.txt create mode 100644 src/effects/slideback/main.cpp create mode 100644 src/effects/slideback/metadata.json create mode 100644 src/effects/slideback/slideback.cpp create mode 100644 src/effects/slideback/slideback.h create mode 100644 src/effects/slidingpopups/CMakeLists.txt create mode 100644 src/effects/slidingpopups/main.cpp create mode 100644 src/effects/slidingpopups/metadata.json create mode 100644 src/effects/slidingpopups/slidingpopups.cpp create mode 100644 src/effects/slidingpopups/slidingpopups.h create mode 100644 src/effects/slidingpopups/slidingpopups.kcfg create mode 100644 src/effects/slidingpopups/slidingpopupsconfig.kcfgc create mode 100644 src/effects/snaphelper/CMakeLists.txt create mode 100644 src/effects/snaphelper/main.cpp create mode 100644 src/effects/snaphelper/metadata.json create mode 100644 src/effects/snaphelper/snaphelper.cpp create mode 100644 src/effects/snaphelper/snaphelper.h create mode 100644 src/effects/squash/package/contents/code/main.js create mode 100644 src/effects/squash/package/metadata.desktop create mode 100644 src/effects/startupfeedback/CMakeLists.txt create mode 100644 src/effects/startupfeedback/main.cpp create mode 100644 src/effects/startupfeedback/metadata.json create mode 100644 src/effects/startupfeedback/shaders/blinking-startup.frag create mode 100644 src/effects/startupfeedback/shaders/blinking-startup_core.frag create mode 100644 src/effects/startupfeedback/startupfeedback.cpp create mode 100644 src/effects/startupfeedback/startupfeedback.h create mode 100644 src/effects/startupfeedback/startupfeedback.qrc create mode 100755 src/effects/strip-effect-metadata.py create mode 100644 src/effects/thumbnailaside/CMakeLists.txt create mode 100644 src/effects/thumbnailaside/main.cpp create mode 100644 src/effects/thumbnailaside/metadata.json create mode 100644 src/effects/thumbnailaside/thumbnailaside.cpp create mode 100644 src/effects/thumbnailaside/thumbnailaside.h create mode 100644 src/effects/thumbnailaside/thumbnailaside.kcfg create mode 100644 src/effects/thumbnailaside/thumbnailaside_config.cpp create mode 100644 src/effects/thumbnailaside/thumbnailaside_config.h create mode 100644 src/effects/thumbnailaside/thumbnailaside_config.ui create mode 100644 src/effects/thumbnailaside/thumbnailasideconfig.kcfgc create mode 100644 src/effects/touchpoints/CMakeLists.txt create mode 100644 src/effects/touchpoints/main.cpp create mode 100644 src/effects/touchpoints/metadata.json create mode 100644 src/effects/touchpoints/touchpoints.cpp create mode 100644 src/effects/touchpoints/touchpoints.h create mode 100644 src/effects/trackmouse/CMakeLists.txt create mode 100644 src/effects/trackmouse/data/tm_inner.png create mode 100644 src/effects/trackmouse/data/tm_outer.png create mode 100644 src/effects/trackmouse/main.cpp create mode 100644 src/effects/trackmouse/metadata.json create mode 100644 src/effects/trackmouse/trackmouse.cpp create mode 100644 src/effects/trackmouse/trackmouse.h create mode 100644 src/effects/trackmouse/trackmouse.kcfg create mode 100644 src/effects/trackmouse/trackmouse_config.cpp create mode 100644 src/effects/trackmouse/trackmouse_config.h create mode 100644 src/effects/trackmouse/trackmouse_config.ui create mode 100644 src/effects/trackmouse/trackmouseconfig.kcfgc create mode 100644 src/effects/translucency/package/contents/code/main.js create mode 100644 src/effects/translucency/package/contents/config/main.xml create mode 100644 src/effects/translucency/package/contents/ui/config.ui create mode 100644 src/effects/translucency/package/metadata.desktop create mode 100644 src/effects/windowaperture/package/contents/code/main.js create mode 100644 src/effects/windowaperture/package/metadata.desktop create mode 100644 src/effects/windowview/CMakeLists.txt create mode 100644 src/effects/windowview/kcm/CMakeLists.txt create mode 100644 src/effects/windowview/kcm/windowvieweffectkcm.cpp create mode 100644 src/effects/windowview/kcm/windowvieweffectkcm.h create mode 100644 src/effects/windowview/kcm/windowvieweffectkcm.ui create mode 100644 src/effects/windowview/main.cpp create mode 100644 src/effects/windowview/metadata.json create mode 100644 src/effects/windowview/org.kde.KWin.Effect.WindowView1.xml create mode 100644 src/effects/windowview/qml/main.qml create mode 100644 src/effects/windowview/windowviewconfig.kcfg create mode 100644 src/effects/windowview/windowviewconfig.kcfgc create mode 100644 src/effects/windowview/windowvieweffect.cpp create mode 100644 src/effects/windowview/windowvieweffect.h create mode 100644 src/effects/wobblywindows/CMakeLists.txt create mode 100644 src/effects/wobblywindows/main.cpp create mode 100644 src/effects/wobblywindows/metadata.json create mode 100644 src/effects/wobblywindows/wobblywindows.cpp create mode 100644 src/effects/wobblywindows/wobblywindows.h create mode 100644 src/effects/wobblywindows/wobblywindows.kcfg create mode 100644 src/effects/wobblywindows/wobblywindows_config.cpp create mode 100644 src/effects/wobblywindows/wobblywindows_config.h create mode 100644 src/effects/wobblywindows/wobblywindows_config.ui create mode 100644 src/effects/wobblywindows/wobblywindowsconfig.kcfgc create mode 100644 src/effects/zoom/CMakeLists.txt create mode 100644 src/effects/zoom/accessibilityintegration.cpp create mode 100644 src/effects/zoom/accessibilityintegration.h create mode 100644 src/effects/zoom/main.cpp create mode 100644 src/effects/zoom/metadata.json create mode 100644 src/effects/zoom/zoom.cpp create mode 100644 src/effects/zoom/zoom.h create mode 100644 src/effects/zoom/zoom.kcfg create mode 100644 src/effects/zoom/zoom_config.cpp create mode 100644 src/effects/zoom/zoom_config.h create mode 100644 src/effects/zoom/zoom_config.ui create mode 100644 src/effects/zoom/zoomconfig.kcfgc create mode 100644 src/events.cpp create mode 100644 src/focuschain.cpp create mode 100644 src/focuschain.h create mode 100644 src/ftrace.cpp create mode 100644 src/ftrace.h create mode 100644 src/gestures.cpp create mode 100644 src/gestures.h create mode 100644 src/globalshortcuts.cpp create mode 100644 src/globalshortcuts.h create mode 100644 src/group.cpp create mode 100644 src/group.h create mode 100644 src/helpers/CMakeLists.txt create mode 100644 src/helpers/killer/CMakeLists.txt create mode 100644 src/helpers/killer/killer.cpp create mode 100644 src/helpers/wayland_wrapper/CMakeLists.txt create mode 100644 src/helpers/wayland_wrapper/kwin_wrapper.cpp create mode 100644 src/helpers/wayland_wrapper/wl-socket.c create mode 100644 src/helpers/wayland_wrapper/wl-socket.h create mode 100644 src/hide_cursor_spy.cpp create mode 100644 src/hide_cursor_spy.h create mode 100644 src/idle_inhibition.cpp create mode 100644 src/idle_inhibition.h create mode 100644 src/idledetector.cpp create mode 100644 src/idledetector.h create mode 100644 src/input.cpp create mode 100644 src/input.h create mode 100644 src/input_event.cpp create mode 100644 src/input_event.h create mode 100644 src/input_event_spy.cpp create mode 100644 src/input_event_spy.h create mode 100644 src/inputmethod.cpp create mode 100644 src/inputmethod.h create mode 100644 src/inputpanelv1integration.cpp create mode 100644 src/inputpanelv1integration.h create mode 100644 src/inputpanelv1window.cpp create mode 100644 src/inputpanelv1window.h create mode 100644 src/internalwindow.cpp create mode 100644 src/internalwindow.h create mode 100644 src/item.cpp create mode 100644 src/item.h create mode 100644 src/kcmkwin/CMakeLists.txt create mode 100644 src/kcmkwin/common/CMakeLists.txt create mode 100644 src/kcmkwin/common/Messages.sh create mode 100644 src/kcmkwin/common/effectsmodel.cpp create mode 100644 src/kcmkwin/common/effectsmodel.h create mode 100644 src/kcmkwin/kwincompositing/CMakeLists.txt create mode 100644 src/kcmkwin/kwincompositing/Messages.sh create mode 100644 src/kcmkwin/kwincompositing/compositing.ui create mode 100644 src/kcmkwin/kwincompositing/kwincompositing.json create mode 100644 src/kcmkwin/kwincompositing/kwincompositing_setting.kcfg create mode 100644 src/kcmkwin/kwincompositing/kwincompositing_setting.kcfgc create mode 100644 src/kcmkwin/kwincompositing/kwincompositingdata.cpp create mode 100644 src/kcmkwin/kwincompositing/kwincompositingdata.h create mode 100644 src/kcmkwin/kwincompositing/main.cpp create mode 100644 src/kcmkwin/kwindecoration/CMakeLists.txt create mode 100644 src/kcmkwin/kwindecoration/Messages.sh create mode 100644 src/kcmkwin/kwindecoration/declarative-plugin/CMakeLists.txt create mode 100644 src/kcmkwin/kwindecoration/declarative-plugin/buttonsmodel.cpp create mode 100644 src/kcmkwin/kwindecoration/declarative-plugin/buttonsmodel.h create mode 100644 src/kcmkwin/kwindecoration/declarative-plugin/plugin.cpp create mode 100644 src/kcmkwin/kwindecoration/declarative-plugin/plugin.h create mode 100644 src/kcmkwin/kwindecoration/declarative-plugin/previewbridge.cpp create mode 100644 src/kcmkwin/kwindecoration/declarative-plugin/previewbridge.h create mode 100644 src/kcmkwin/kwindecoration/declarative-plugin/previewbutton.cpp create mode 100644 src/kcmkwin/kwindecoration/declarative-plugin/previewbutton.h create mode 100644 src/kcmkwin/kwindecoration/declarative-plugin/previewclient.cpp create mode 100644 src/kcmkwin/kwindecoration/declarative-plugin/previewclient.h create mode 100644 src/kcmkwin/kwindecoration/declarative-plugin/previewitem.cpp create mode 100644 src/kcmkwin/kwindecoration/declarative-plugin/previewitem.h create mode 100644 src/kcmkwin/kwindecoration/declarative-plugin/previewsettings.cpp create mode 100644 src/kcmkwin/kwindecoration/declarative-plugin/previewsettings.h create mode 100644 src/kcmkwin/kwindecoration/declarative-plugin/qmldir create mode 100644 src/kcmkwin/kwindecoration/decorationmodel.cpp create mode 100644 src/kcmkwin/kwindecoration/decorationmodel.h create mode 100644 src/kcmkwin/kwindecoration/kcm.cpp create mode 100644 src/kcmkwin/kwindecoration/kcm.h create mode 100644 src/kcmkwin/kwindecoration/kcm_kwindecoration.json create mode 100644 src/kcmkwin/kwindecoration/kwin-applywindowdecoration.cpp create mode 100644 src/kcmkwin/kwindecoration/kwindecorationsettings.kcfg create mode 100644 src/kcmkwin/kwindecoration/kwindecorationsettings.kcfgc create mode 100644 src/kcmkwin/kwindecoration/package/contents/ui/ButtonGroup.qml create mode 100644 src/kcmkwin/kwindecoration/package/contents/ui/Buttons.qml create mode 100644 src/kcmkwin/kwindecoration/package/contents/ui/Themes.qml create mode 100644 src/kcmkwin/kwindecoration/package/contents/ui/main.qml create mode 100644 src/kcmkwin/kwindecoration/utils.cpp create mode 100644 src/kcmkwin/kwindecoration/utils.h create mode 100644 src/kcmkwin/kwindecoration/window-decorations.knsrc.cmake create mode 100644 src/kcmkwin/kwindesktop/CMakeLists.txt create mode 100644 src/kcmkwin/kwindesktop/Messages.sh create mode 100644 src/kcmkwin/kwindesktop/animationsmodel.cpp create mode 100644 src/kcmkwin/kwindesktop/animationsmodel.h create mode 100644 src/kcmkwin/kwindesktop/desktopsmodel.cpp create mode 100644 src/kcmkwin/kwindesktop/desktopsmodel.h create mode 100644 src/kcmkwin/kwindesktop/kcm_kwin_virtualdesktops.json create mode 100644 src/kcmkwin/kwindesktop/package/contents/ui/main.qml create mode 100644 src/kcmkwin/kwindesktop/virtualdesktops.cpp create mode 100644 src/kcmkwin/kwindesktop/virtualdesktops.h create mode 100644 src/kcmkwin/kwindesktop/virtualdesktopsdata.cpp create mode 100644 src/kcmkwin/kwindesktop/virtualdesktopsdata.h create mode 100644 src/kcmkwin/kwindesktop/virtualdesktopssettings.kcfg create mode 100644 src/kcmkwin/kwindesktop/virtualdesktopssettings.kcfgc create mode 100644 src/kcmkwin/kwineffects/CMakeLists.txt create mode 100644 src/kcmkwin/kwineffects/Messages.sh create mode 100644 src/kcmkwin/kwineffects/desktopeffectsdata.cpp create mode 100644 src/kcmkwin/kwineffects/desktopeffectsdata.h create mode 100644 src/kcmkwin/kwineffects/effectsfilterproxymodel.cpp create mode 100644 src/kcmkwin/kwineffects/effectsfilterproxymodel.h create mode 100644 src/kcmkwin/kwineffects/kcm.cpp create mode 100644 src/kcmkwin/kwineffects/kcm.h create mode 100644 src/kcmkwin/kwineffects/kcm_kwin_effects.json create mode 100644 src/kcmkwin/kwineffects/kwineffect.knsrc create mode 100644 src/kcmkwin/kwineffects/package/contents/ui/Effect.qml create mode 100644 src/kcmkwin/kwineffects/package/contents/ui/Video.qml create mode 100644 src/kcmkwin/kwineffects/package/contents/ui/main.qml create mode 100644 src/kcmkwin/kwinoptions/AUTHORS create mode 100644 src/kcmkwin/kwinoptions/CMakeLists.txt create mode 100644 src/kcmkwin/kwinoptions/ChangeLog create mode 100644 src/kcmkwin/kwinoptions/Messages.sh create mode 100644 src/kcmkwin/kwinoptions/actions.ui create mode 100644 src/kcmkwin/kwinoptions/advanced.ui create mode 100644 src/kcmkwin/kwinoptions/focus.ui create mode 100644 src/kcmkwin/kwinoptions/kcm_kwinoptions.json create mode 100644 src/kcmkwin/kwinoptions/kwinoptions_kdeglobals_settings.kcfg create mode 100644 src/kcmkwin/kwinoptions/kwinoptions_kdeglobals_settings.kcfgc create mode 100644 src/kcmkwin/kwinoptions/kwinoptions_settings.kcfg create mode 100644 src/kcmkwin/kwinoptions/kwinoptions_settings.kcfgc create mode 100644 src/kcmkwin/kwinoptions/main.cpp create mode 100644 src/kcmkwin/kwinoptions/main.h create mode 100644 src/kcmkwin/kwinoptions/mouse.cpp create mode 100644 src/kcmkwin/kwinoptions/mouse.h create mode 100644 src/kcmkwin/kwinoptions/mouse.ui create mode 100644 src/kcmkwin/kwinoptions/moving.ui create mode 100644 src/kcmkwin/kwinoptions/windows.cpp create mode 100644 src/kcmkwin/kwinoptions/windows.h create mode 100644 src/kcmkwin/kwinrules/CMakeLists.txt create mode 100644 src/kcmkwin/kwinrules/Messages.sh create mode 100644 src/kcmkwin/kwinrules/kcm_kwinrules.json create mode 100644 src/kcmkwin/kwinrules/kcmrules.cpp create mode 100644 src/kcmkwin/kwinrules/kcmrules.h create mode 100644 src/kcmkwin/kwinrules/kwinsrc.cpp create mode 100644 src/kcmkwin/kwinrules/main.cpp create mode 100644 src/kcmkwin/kwinrules/optionsmodel.cpp create mode 100644 src/kcmkwin/kwinrules/optionsmodel.h create mode 100644 src/kcmkwin/kwinrules/org.kde.kwin_rules_dialog.desktop create mode 100644 src/kcmkwin/kwinrules/package/contents/ui/FileDialogLoader.qml create mode 100644 src/kcmkwin/kwinrules/package/contents/ui/OptionsComboBox.qml create mode 100644 src/kcmkwin/kwinrules/package/contents/ui/RuleItemDelegate.qml create mode 100644 src/kcmkwin/kwinrules/package/contents/ui/RulesEditor.qml create mode 100644 src/kcmkwin/kwinrules/package/contents/ui/ValueEditor.qml create mode 100644 src/kcmkwin/kwinrules/package/contents/ui/main.qml create mode 100644 src/kcmkwin/kwinrules/rulebookmodel.cpp create mode 100644 src/kcmkwin/kwinrules/rulebookmodel.h create mode 100644 src/kcmkwin/kwinrules/ruleitem.cpp create mode 100644 src/kcmkwin/kwinrules/ruleitem.h create mode 100644 src/kcmkwin/kwinrules/rulesmodel.cpp create mode 100644 src/kcmkwin/kwinrules/rulesmodel.h create mode 100644 src/kcmkwin/kwinscreenedges/CMakeLists.txt create mode 100644 src/kcmkwin/kwinscreenedges/Messages.sh create mode 100644 src/kcmkwin/kwinscreenedges/kcm_kwinscreenedges.json create mode 100644 src/kcmkwin/kwinscreenedges/kcm_kwintouchscreen.json create mode 100644 src/kcmkwin/kwinscreenedges/kwinscreenedge.cpp create mode 100644 src/kcmkwin/kwinscreenedges/kwinscreenedge.h create mode 100644 src/kcmkwin/kwinscreenedges/kwinscreenedgeconfigform.cpp create mode 100644 src/kcmkwin/kwinscreenedges/kwinscreenedgeconfigform.h create mode 100644 src/kcmkwin/kwinscreenedges/kwinscreenedgeeffectsettings.kcfg create mode 100644 src/kcmkwin/kwinscreenedges/kwinscreenedgeeffectsettings.kcfgc create mode 100644 src/kcmkwin/kwinscreenedges/kwinscreenedgescriptsettings.kcfg create mode 100644 src/kcmkwin/kwinscreenedges/kwinscreenedgescriptsettings.kcfgc create mode 100644 src/kcmkwin/kwinscreenedges/kwinscreenedgesettings.kcfg create mode 100644 src/kcmkwin/kwinscreenedges/kwinscreenedgesettings.kcfgc create mode 100644 src/kcmkwin/kwinscreenedges/kwintouchscreenedgeconfigform.cpp create mode 100644 src/kcmkwin/kwinscreenedges/kwintouchscreenedgeconfigform.h create mode 100644 src/kcmkwin/kwinscreenedges/kwintouchscreenedgeeffectsettings.kcfg create mode 100644 src/kcmkwin/kwinscreenedges/kwintouchscreenedgeeffectsettings.kcfgc create mode 100644 src/kcmkwin/kwinscreenedges/kwintouchscreenscriptsettings.kcfg create mode 100644 src/kcmkwin/kwinscreenedges/kwintouchscreenscriptsettings.kcfgc create mode 100644 src/kcmkwin/kwinscreenedges/kwintouchscreensettings.kcfg create mode 100644 src/kcmkwin/kwinscreenedges/kwintouchscreensettings.kcfgc create mode 100644 src/kcmkwin/kwinscreenedges/main.cpp create mode 100644 src/kcmkwin/kwinscreenedges/main.h create mode 100644 src/kcmkwin/kwinscreenedges/main.ui create mode 100644 src/kcmkwin/kwinscreenedges/monitor.cpp create mode 100644 src/kcmkwin/kwinscreenedges/monitor.h create mode 100644 src/kcmkwin/kwinscreenedges/screenpreviewwidget.cpp create mode 100644 src/kcmkwin/kwinscreenedges/screenpreviewwidget.h create mode 100644 src/kcmkwin/kwinscreenedges/touch.cpp create mode 100644 src/kcmkwin/kwinscreenedges/touch.h create mode 100644 src/kcmkwin/kwinscreenedges/touch.ui create mode 100644 src/kcmkwin/kwinscripts/CMakeLists.txt create mode 100755 src/kcmkwin/kwinscripts/Messages.sh create mode 100644 src/kcmkwin/kwinscripts/kcm_kwin_scripts.json create mode 100644 src/kcmkwin/kwinscripts/kwinscripts.knsrc create mode 100644 src/kcmkwin/kwinscripts/kwinscriptsdata.cpp create mode 100644 src/kcmkwin/kwinscripts/kwinscriptsdata.h create mode 100644 src/kcmkwin/kwinscripts/module.cpp create mode 100644 src/kcmkwin/kwinscripts/module.h create mode 100644 src/kcmkwin/kwinscripts/package/contents/ui/main.qml create mode 100644 src/kcmkwin/kwintabbox/CMakeLists.txt create mode 100644 src/kcmkwin/kwintabbox/Messages.sh create mode 100644 src/kcmkwin/kwintabbox/kcm_kwintabbox.json create mode 100644 src/kcmkwin/kwintabbox/kwinpluginssettings.kcfg create mode 100644 src/kcmkwin/kwintabbox/kwinpluginssettings.kcfgc create mode 100644 src/kcmkwin/kwintabbox/kwinswitcheffectsettings.kcfg create mode 100644 src/kcmkwin/kwintabbox/kwinswitcheffectsettings.kcfgc create mode 100644 src/kcmkwin/kwintabbox/kwinswitcher.knsrc create mode 100644 src/kcmkwin/kwintabbox/kwintabboxconfigform.cpp create mode 100644 src/kcmkwin/kwintabbox/kwintabboxconfigform.h create mode 100644 src/kcmkwin/kwintabbox/kwintabboxdata.cpp create mode 100644 src/kcmkwin/kwintabbox/kwintabboxdata.h create mode 100644 src/kcmkwin/kwintabbox/kwintabboxsettings.kcfg create mode 100644 src/kcmkwin/kwintabbox/kwintabboxsettings.kcfgc create mode 100644 src/kcmkwin/kwintabbox/layoutpreview.cpp create mode 100644 src/kcmkwin/kwintabbox/layoutpreview.h create mode 100644 src/kcmkwin/kwintabbox/main.cpp create mode 100644 src/kcmkwin/kwintabbox/main.h create mode 100644 src/kcmkwin/kwintabbox/main.ui create mode 100644 src/kcmkwin/kwintabbox/thumbnailitem.cpp create mode 100644 src/kcmkwin/kwintabbox/thumbnailitem.h create mode 100644 src/kcmkwin/kwintabbox/thumbnails/desktop.png create mode 100644 src/kcmkwin/kwintabbox/thumbnails/dolphin.png create mode 100644 src/kcmkwin/kwintabbox/thumbnails/kmail.png create mode 100644 src/kcmkwin/kwintabbox/thumbnails/konqueror.png create mode 100644 src/kcmkwin/kwintabbox/thumbnails/systemsettings.png create mode 100644 src/kcmkwin/kwinvirtualkeyboard/CMakeLists.txt create mode 100644 src/kcmkwin/kwinvirtualkeyboard/Messages.sh create mode 100644 src/kcmkwin/kwinvirtualkeyboard/kcm_virtualkeyboard.json create mode 100644 src/kcmkwin/kwinvirtualkeyboard/kcmvirtualkeyboard.cpp create mode 100644 src/kcmkwin/kwinvirtualkeyboard/kcmvirtualkeyboard.h create mode 100644 src/kcmkwin/kwinvirtualkeyboard/package/contents/ui/main.qml create mode 100644 src/kcmkwin/kwinvirtualkeyboard/virtualkeyboardsettings.kcfg create mode 100644 src/kcmkwin/kwinvirtualkeyboard/virtualkeyboardsettings.kcfgc create mode 100644 src/keyboard_input.cpp create mode 100644 src/keyboard_input.h create mode 100644 src/keyboard_layout.cpp create mode 100644 src/keyboard_layout.h create mode 100644 src/keyboard_layout_switching.cpp create mode 100644 src/keyboard_layout_switching.h create mode 100644 src/keyboard_repeat.cpp create mode 100644 src/keyboard_repeat.h create mode 100644 src/killwindow.cpp create mode 100644 src/killwindow.h create mode 100644 src/kwin.kcfg create mode 100644 src/kwin.notifyrc create mode 100644 src/kwineglutils_p.h create mode 100644 src/layers.cpp create mode 100644 src/layershellv1integration.cpp create mode 100644 src/layershellv1integration.h create mode 100644 src/layershellv1window.cpp create mode 100644 src/layershellv1window.h create mode 100644 src/libkwineffects/CMakeLists.txt create mode 100644 src/libkwineffects/KWinEffectsConfig.cmake.in create mode 100644 src/libkwineffects/Mainpage.dox create mode 100644 src/libkwineffects/Messages.sh create mode 100644 src/libkwineffects/anidata.cpp create mode 100644 src/libkwineffects/anidata_p.h create mode 100644 src/libkwineffects/kwinanimationeffect.cpp create mode 100644 src/libkwineffects/kwinanimationeffect.h create mode 100644 src/libkwineffects/kwinconfig.h.cmake create mode 100644 src/libkwineffects/kwineffects.cpp create mode 100644 src/libkwineffects/kwineffects.h create mode 100644 src/libkwineffects/kwineglimagetexture.cpp create mode 100644 src/libkwineffects/kwineglimagetexture.h create mode 100644 src/libkwineffects/kwinglobals.h create mode 100644 src/libkwineffects/kwinglplatform.cpp create mode 100644 src/libkwineffects/kwinglplatform.h create mode 100644 src/libkwineffects/kwingltexture.cpp create mode 100644 src/libkwineffects/kwingltexture.h create mode 100644 src/libkwineffects/kwingltexture_p.h create mode 100644 src/libkwineffects/kwinglutils.cpp create mode 100644 src/libkwineffects/kwinglutils.h create mode 100644 src/libkwineffects/kwinglutils_funcs.cpp create mode 100644 src/libkwineffects/kwinglutils_funcs.h create mode 100644 src/libkwineffects/kwinoffscreeneffect.cpp create mode 100644 src/libkwineffects/kwinoffscreeneffect.h create mode 100644 src/libkwineffects/kwinoffscreenquickview.cpp create mode 100644 src/libkwineffects/kwinoffscreenquickview.h create mode 100644 src/libkwineffects/kwinquickeffect.cpp create mode 100644 src/libkwineffects/kwinquickeffect.h create mode 100644 src/libkwineffects/logging.cpp create mode 100644 src/libkwineffects/logging_p.h create mode 100644 src/libkwineffects/sharedqmlengine.cpp create mode 100644 src/libkwineffects/sharedqmlengine.h create mode 100644 src/linux_dmabuf.cpp create mode 100644 src/linux_dmabuf.h create mode 100644 src/main.cpp create mode 100644 src/main.h create mode 100644 src/main_wayland.cpp create mode 100644 src/main_wayland.h create mode 100644 src/main_x11.cpp create mode 100644 src/main_x11.h create mode 100644 src/modifier_only_shortcuts.cpp create mode 100644 src/modifier_only_shortcuts.h create mode 100644 src/mousebuttons.cpp create mode 100644 src/mousebuttons.h create mode 100644 src/moving_client_x11_filter.cpp create mode 100644 src/moving_client_x11_filter.h create mode 100644 src/netinfo.cpp create mode 100644 src/netinfo.h create mode 100644 src/onscreennotification.cpp create mode 100644 src/onscreennotification.h create mode 100644 src/options.cpp create mode 100644 src/options.h create mode 100644 src/org.kde.KWin.Plugins.xml create mode 100644 src/org.kde.KWin.Session.xml create mode 100644 src/org.kde.KWin.VirtualDesktopManager.xml create mode 100644 src/org.kde.KWin.xml create mode 100644 src/org.kde.kappmenu.xml create mode 100644 src/org.kde.kwin.Compositing.xml create mode 100644 src/org.kde.kwin.Effects.xml create mode 100644 src/osd.cpp create mode 100644 src/osd.h create mode 100644 src/outline.cpp create mode 100644 src/outline.h create mode 100644 src/placeholderinputeventfilter.cpp create mode 100644 src/placeholderinputeventfilter.h create mode 100644 src/placeholderoutput.cpp create mode 100644 src/placeholderoutput.h create mode 100644 src/placement.cpp create mode 100644 src/placement.h create mode 100644 src/placementtracker.cpp create mode 100644 src/placementtracker.h create mode 100644 src/platformsupport/CMakeLists.txt create mode 100644 src/platformsupport/scenes/CMakeLists.txt create mode 100644 src/platformsupport/scenes/opengl/CMakeLists.txt create mode 100644 src/platformsupport/scenes/opengl/abstract_egl_backend.cpp create mode 100644 src/platformsupport/scenes/opengl/abstract_egl_backend.h create mode 100644 src/platformsupport/scenes/opengl/basiceglsurfacetexture_internal.cpp create mode 100644 src/platformsupport/scenes/opengl/basiceglsurfacetexture_internal.h create mode 100644 src/platformsupport/scenes/opengl/basiceglsurfacetexture_wayland.cpp create mode 100644 src/platformsupport/scenes/opengl/basiceglsurfacetexture_wayland.h create mode 100644 src/platformsupport/scenes/opengl/egl_dmabuf.cpp create mode 100644 src/platformsupport/scenes/opengl/egl_dmabuf.h create mode 100644 src/platformsupport/scenes/opengl/kwineglext.h create mode 100644 src/platformsupport/scenes/opengl/openglbackend.cpp create mode 100644 src/platformsupport/scenes/opengl/openglbackend.h create mode 100644 src/platformsupport/scenes/opengl/openglsurfacetexture.cpp create mode 100644 src/platformsupport/scenes/opengl/openglsurfacetexture.h create mode 100644 src/platformsupport/scenes/opengl/openglsurfacetexture_internal.cpp create mode 100644 src/platformsupport/scenes/opengl/openglsurfacetexture_internal.h create mode 100644 src/platformsupport/scenes/opengl/openglsurfacetexture_wayland.cpp create mode 100644 src/platformsupport/scenes/opengl/openglsurfacetexture_wayland.h create mode 100644 src/platformsupport/scenes/opengl/openglsurfacetexture_x11.cpp create mode 100644 src/platformsupport/scenes/opengl/openglsurfacetexture_x11.h create mode 100644 src/platformsupport/scenes/qpainter/CMakeLists.txt create mode 100644 src/platformsupport/scenes/qpainter/qpainterbackend.cpp create mode 100644 src/platformsupport/scenes/qpainter/qpainterbackend.h create mode 100644 src/platformsupport/scenes/qpainter/qpaintersurfacetexture.cpp create mode 100644 src/platformsupport/scenes/qpainter/qpaintersurfacetexture.h create mode 100644 src/platformsupport/scenes/qpainter/qpaintersurfacetexture_internal.cpp create mode 100644 src/platformsupport/scenes/qpainter/qpaintersurfacetexture_internal.h create mode 100644 src/platformsupport/scenes/qpainter/qpaintersurfacetexture_wayland.cpp create mode 100644 src/platformsupport/scenes/qpainter/qpaintersurfacetexture_wayland.h create mode 100644 src/platformsupport/vsyncconvenience/CMakeLists.txt create mode 100644 src/platformsupport/vsyncconvenience/softwarevsyncmonitor.cpp create mode 100644 src/platformsupport/vsyncconvenience/softwarevsyncmonitor.h create mode 100644 src/platformsupport/vsyncconvenience/vsyncmonitor.cpp create mode 100644 src/platformsupport/vsyncconvenience/vsyncmonitor.h create mode 100644 src/plugin.cpp create mode 100644 src/plugin.h create mode 100644 src/pluginmanager.cpp create mode 100644 src/pluginmanager.h create mode 100644 src/plugins/CMakeLists.txt create mode 100644 src/plugins/buttonrebinds/CMakeLists.txt create mode 100644 src/plugins/buttonrebinds/buttonrebindsfilter.cpp create mode 100644 src/plugins/buttonrebinds/buttonrebindsfilter.h create mode 100644 src/plugins/buttonrebinds/main.cpp create mode 100644 src/plugins/buttonrebinds/metadata.json create mode 100644 src/plugins/colord-integration/CMakeLists.txt create mode 100644 src/plugins/colord-integration/colorddevice.cpp create mode 100644 src/plugins/colord-integration/colorddevice.h create mode 100644 src/plugins/colord-integration/colordintegration.cpp create mode 100644 src/plugins/colord-integration/colordintegration.h create mode 100644 src/plugins/colord-integration/colordtypes.h create mode 100644 src/plugins/colord-integration/main.cpp create mode 100644 src/plugins/colord-integration/metadata.json create mode 100644 src/plugins/colord-integration/org.freedesktop.ColorManager.Device.xml create mode 100644 src/plugins/colord-integration/org.freedesktop.ColorManager.Profile.xml create mode 100644 src/plugins/colord-integration/org.freedesktop.ColorManager.xml create mode 100644 src/plugins/idletime/CMakeLists.txt create mode 100644 src/plugins/idletime/kwin.json create mode 100644 src/plugins/idletime/poller.cpp create mode 100644 src/plugins/idletime/poller.h create mode 100644 src/plugins/kdecorations/CMakeLists.txt create mode 100644 src/plugins/kdecorations/Messages.sh create mode 100644 src/plugins/kdecorations/aurorae/AUTHORS create mode 100644 src/plugins/kdecorations/aurorae/CMakeLists.txt create mode 100644 src/plugins/kdecorations/aurorae/README create mode 100644 src/plugins/kdecorations/aurorae/TODO create mode 100644 src/plugins/kdecorations/aurorae/src/CMakeLists.txt create mode 100644 src/plugins/kdecorations/aurorae/src/aurorae.cpp create mode 100644 src/plugins/kdecorations/aurorae/src/aurorae.h create mode 100644 src/plugins/kdecorations/aurorae/src/aurorae.json create mode 100644 src/plugins/kdecorations/aurorae/src/aurorae.knsrc.cmake create mode 100644 src/plugins/kdecorations/aurorae/src/colorhelper.cpp create mode 100644 src/plugins/kdecorations/aurorae/src/colorhelper.h create mode 100644 src/plugins/kdecorations/aurorae/src/decorationoptions.cpp create mode 100644 src/plugins/kdecorations/aurorae/src/decorationoptions.h create mode 100644 src/plugins/kdecorations/aurorae/src/decorationplugin.cpp create mode 100644 src/plugins/kdecorations/aurorae/src/decorationplugin.h create mode 100644 src/plugins/kdecorations/aurorae/src/kwindecoration.desktop create mode 100644 src/plugins/kdecorations/aurorae/src/lib/auroraetheme.cpp create mode 100644 src/plugins/kdecorations/aurorae/src/lib/auroraetheme.h create mode 100644 src/plugins/kdecorations/aurorae/src/lib/themeconfig.cpp create mode 100644 src/plugins/kdecorations/aurorae/src/lib/themeconfig.h create mode 100644 src/plugins/kdecorations/aurorae/src/qml/AppMenuButton.qml create mode 100644 src/plugins/kdecorations/aurorae/src/qml/AuroraeButton.qml create mode 100644 src/plugins/kdecorations/aurorae/src/qml/AuroraeButtonGroup.qml create mode 100644 src/plugins/kdecorations/aurorae/src/qml/AuroraeMaximizeButton.qml create mode 100644 src/plugins/kdecorations/aurorae/src/qml/ButtonGroup.qml create mode 100644 src/plugins/kdecorations/aurorae/src/qml/Decoration.qml create mode 100644 src/plugins/kdecorations/aurorae/src/qml/DecorationButton.qml create mode 100644 src/plugins/kdecorations/aurorae/src/qml/MenuButton.qml create mode 100644 src/plugins/kdecorations/aurorae/src/qml/aurorae.qml create mode 100644 src/plugins/kdecorations/aurorae/src/qml/qmldir create mode 100644 src/plugins/kdecorations/aurorae/theme-description create mode 100644 src/plugins/kdecorations/aurorae/themes/CMakeLists.txt create mode 100644 src/plugins/kdecorations/aurorae/themes/plastik/CMakeLists.txt create mode 100644 src/plugins/kdecorations/aurorae/themes/plastik/code/CMakeLists.txt create mode 100644 src/plugins/kdecorations/aurorae/themes/plastik/code/plastikbutton.cpp create mode 100644 src/plugins/kdecorations/aurorae/themes/plastik/code/plastikbutton.h create mode 100644 src/plugins/kdecorations/aurorae/themes/plastik/code/plastikplugin.cpp create mode 100644 src/plugins/kdecorations/aurorae/themes/plastik/code/plastikplugin.h create mode 100644 src/plugins/kdecorations/aurorae/themes/plastik/code/qmldir create mode 100644 src/plugins/kdecorations/aurorae/themes/plastik/package/contents/config/main.xml create mode 100644 src/plugins/kdecorations/aurorae/themes/plastik/package/contents/ui/PlastikButton.qml create mode 100644 src/plugins/kdecorations/aurorae/themes/plastik/package/contents/ui/config.ui create mode 100644 src/plugins/kdecorations/aurorae/themes/plastik/package/contents/ui/main.qml create mode 100644 src/plugins/kdecorations/aurorae/themes/plastik/package/metadata.desktop create mode 100644 src/plugins/kglobalaccel/CMakeLists.txt create mode 100644 src/plugins/kglobalaccel/kglobalaccel_plugin.cpp create mode 100644 src/plugins/kglobalaccel/kglobalaccel_plugin.h create mode 100644 src/plugins/kglobalaccel/kwin.json create mode 100644 src/plugins/kpackage/CMakeLists.txt create mode 100644 src/plugins/kpackage/aurorae/CMakeLists.txt create mode 100644 src/plugins/kpackage/aurorae/aurorae.cpp create mode 100644 src/plugins/kpackage/aurorae/aurorae.h create mode 100644 src/plugins/kpackage/aurorae/kwin-packagestructure-aurorae.json create mode 100644 src/plugins/kpackage/decoration/CMakeLists.txt create mode 100644 src/plugins/kpackage/decoration/decoration.cpp create mode 100644 src/plugins/kpackage/decoration/decoration.h create mode 100644 src/plugins/kpackage/decoration/kwin-packagestructure-decoration.json create mode 100644 src/plugins/kpackage/effect/CMakeLists.txt create mode 100644 src/plugins/kpackage/effect/effect.cpp create mode 100644 src/plugins/kpackage/effect/effect.h create mode 100644 src/plugins/kpackage/effect/kwin-packagestructure-effect.json create mode 100644 src/plugins/kpackage/scripts/CMakeLists.txt create mode 100644 src/plugins/kpackage/scripts/kwin-packagestructure-scripts.json create mode 100644 src/plugins/kpackage/scripts/scripts.cpp create mode 100644 src/plugins/kpackage/scripts/scripts.h create mode 100644 src/plugins/kpackage/windowswitcher/CMakeLists.txt create mode 100644 src/plugins/kpackage/windowswitcher/kwin-packagestructure-windowswitcher.json create mode 100644 src/plugins/kpackage/windowswitcher/windowswitcher.cpp create mode 100644 src/plugins/kpackage/windowswitcher/windowswitcher.h create mode 100644 src/plugins/krunner-integration/CMakeLists.txt create mode 100644 src/plugins/krunner-integration/dbusutils_p.h create mode 100644 src/plugins/krunner-integration/kwin-runner-windows.desktop create mode 100644 src/plugins/krunner-integration/main.cpp create mode 100644 src/plugins/krunner-integration/metadata.json create mode 100644 src/plugins/krunner-integration/org.kde.krunner1.xml create mode 100644 src/plugins/krunner-integration/windowsrunnerinterface.cpp create mode 100644 src/plugins/krunner-integration/windowsrunnerinterface.h create mode 100644 src/plugins/nightcolor/CMakeLists.txt create mode 100644 src/plugins/nightcolor/clockskewnotifier.cpp create mode 100644 src/plugins/nightcolor/clockskewnotifier.h create mode 100644 src/plugins/nightcolor/clockskewnotifierengine.cpp create mode 100644 src/plugins/nightcolor/clockskewnotifierengine_linux.cpp create mode 100644 src/plugins/nightcolor/clockskewnotifierengine_linux.h create mode 100644 src/plugins/nightcolor/clockskewnotifierengine_p.h create mode 100644 src/plugins/nightcolor/constants.h create mode 100644 src/plugins/nightcolor/main.cpp create mode 100644 src/plugins/nightcolor/metadata.json create mode 100644 src/plugins/nightcolor/nightcolordbusinterface.cpp create mode 100644 src/plugins/nightcolor/nightcolordbusinterface.h create mode 100644 src/plugins/nightcolor/nightcolormanager.cpp create mode 100644 src/plugins/nightcolor/nightcolormanager.h create mode 100644 src/plugins/nightcolor/nightcolorsettings.kcfg create mode 100644 src/plugins/nightcolor/nightcolorsettings.kcfgc create mode 100644 src/plugins/nightcolor/org.kde.kwin.ColorCorrect.xml create mode 100644 src/plugins/nightcolor/suncalc.cpp create mode 100644 src/plugins/nightcolor/suncalc.h create mode 100644 src/plugins/qpa/CMakeLists.txt create mode 100644 src/plugins/qpa/backingstore.cpp create mode 100644 src/plugins/qpa/backingstore.h create mode 100644 src/plugins/qpa/eglhelpers.cpp create mode 100644 src/plugins/qpa/eglhelpers.h create mode 100644 src/plugins/qpa/eglplatformcontext.cpp create mode 100644 src/plugins/qpa/eglplatformcontext.h create mode 100644 src/plugins/qpa/integration.cpp create mode 100644 src/plugins/qpa/integration.h create mode 100644 src/plugins/qpa/kwin.json create mode 100644 src/plugins/qpa/main.cpp create mode 100644 src/plugins/qpa/offscreensurface.cpp create mode 100644 src/plugins/qpa/offscreensurface.h create mode 100644 src/plugins/qpa/platformcursor.cpp create mode 100644 src/plugins/qpa/platformcursor.h create mode 100644 src/plugins/qpa/screen.cpp create mode 100644 src/plugins/qpa/screen.h create mode 100644 src/plugins/qpa/window.cpp create mode 100644 src/plugins/qpa/window.h create mode 100644 src/plugins/screencast/CMakeLists.txt create mode 100644 src/plugins/screencast/eglnativefence.cpp create mode 100644 src/plugins/screencast/eglnativefence.h create mode 100644 src/plugins/screencast/main.cpp create mode 100644 src/plugins/screencast/metadata.json create mode 100644 src/plugins/screencast/outputscreencastsource.cpp create mode 100644 src/plugins/screencast/outputscreencastsource.h create mode 100644 src/plugins/screencast/pipewirecore.cpp create mode 100644 src/plugins/screencast/pipewirecore.h create mode 100644 src/plugins/screencast/regionscreencastsource.cpp create mode 100644 src/plugins/screencast/regionscreencastsource.h create mode 100644 src/plugins/screencast/screencastmanager.cpp create mode 100644 src/plugins/screencast/screencastmanager.h create mode 100644 src/plugins/screencast/screencastsource.cpp create mode 100644 src/plugins/screencast/screencastsource.h create mode 100644 src/plugins/screencast/screencaststream.cpp create mode 100644 src/plugins/screencast/screencaststream.h create mode 100644 src/plugins/screencast/screencastutils.h create mode 100644 src/plugins/screencast/windowscreencastsource.cpp create mode 100644 src/plugins/screencast/windowscreencastsource.h create mode 100644 src/plugins/windowsystem/CMakeLists.txt create mode 100644 src/plugins/windowsystem/kwindowsystem.json create mode 100644 src/plugins/windowsystem/plugin.cpp create mode 100644 src/plugins/windowsystem/plugin.h create mode 100644 src/plugins/windowsystem/windoweffects.cpp create mode 100644 src/plugins/windowsystem/windoweffects.h create mode 100644 src/plugins/windowsystem/windowshadow.cpp create mode 100644 src/plugins/windowsystem/windowshadow.h create mode 100644 src/plugins/windowsystem/windowsystem.cpp create mode 100644 src/plugins/windowsystem/windowsystem.h create mode 100644 src/pointer_input.cpp create mode 100644 src/pointer_input.h create mode 100644 src/popup_input_filter.cpp create mode 100644 src/popup_input_filter.h create mode 100644 src/qml/CMakeLists.txt create mode 100644 src/qml/frames/plasma/frame_none.qml create mode 100644 src/qml/frames/plasma/frame_styled.qml create mode 100644 src/qml/frames/plasma/frame_unstyled.qml create mode 100644 src/qml/onscreennotification/plasma/dummydata/osd.qml create mode 100644 src/qml/onscreennotification/plasma/main.qml create mode 100644 src/qml/outline/plasma/outline.qml create mode 100644 src/rootinfo_filter.cpp create mode 100644 src/rootinfo_filter.h create mode 100644 src/rulebooksettings.cpp create mode 100644 src/rulebooksettings.h create mode 100644 src/rulebooksettingsbase.kcfg create mode 100644 src/rulebooksettingsbase.kcfgc create mode 100644 src/rules.cpp create mode 100644 src/rules.h create mode 100644 src/rulesettings.kcfg create mode 100644 src/rulesettings.kcfgc create mode 100644 src/scene.cpp create mode 100644 src/scene.h create mode 100644 src/scenes/CMakeLists.txt create mode 100644 src/scenes/opengl/CMakeLists.txt create mode 100644 src/scenes/opengl/scene_opengl.cpp create mode 100644 src/scenes/opengl/scene_opengl.h create mode 100644 src/scenes/qpainter/CMakeLists.txt create mode 100644 src/scenes/qpainter/scene_qpainter.cpp create mode 100644 src/scenes/qpainter/scene_qpainter.h create mode 100644 src/screenedge.cpp create mode 100644 src/screenedge.h create mode 100644 src/screenlockerwatcher.cpp create mode 100644 src/screenlockerwatcher.h create mode 100644 src/screens.cpp create mode 100644 src/screens.h create mode 100644 src/scripting/CMakeLists.txt create mode 100644 src/scripting/Messages.sh create mode 100644 src/scripting/dbuscall.cpp create mode 100644 src/scripting/dbuscall.h create mode 100644 src/scripting/desktopbackgrounditem.cpp create mode 100644 src/scripting/desktopbackgrounditem.h create mode 100644 src/scripting/documentation-effect-global.xml create mode 100644 src/scripting/documentation-global.xml create mode 100644 src/scripting/genericscriptedconfig.cpp create mode 100644 src/scripting/genericscriptedconfig.h create mode 100644 src/scripting/genericscriptedconfig.json create mode 100644 src/scripting/kwinscript.desktop create mode 100644 src/scripting/org.kde.kwin.Script.xml create mode 100644 src/scripting/screenedgeitem.cpp create mode 100644 src/scripting/screenedgeitem.h create mode 100644 src/scripting/scriptedeffect.cpp create mode 100644 src/scripting/scriptedeffect.h create mode 100644 src/scripting/scripting.cpp create mode 100644 src/scripting/scripting.h create mode 100644 src/scripting/scripting_logging.cpp create mode 100644 src/scripting/scripting_logging.h create mode 100644 src/scripting/scriptingutils.cpp create mode 100644 src/scripting/scriptingutils.h create mode 100644 src/scripting/v2/CMakeLists.txt create mode 100644 src/scripting/v2/clientmodel.cpp create mode 100644 src/scripting/v2/clientmodel.h create mode 100644 src/scripting/v2/qml/DesktopThumbnailItem.qml create mode 100644 src/scripting/v2/qml/qmldir create mode 100644 src/scripting/v3/clientmodel.cpp create mode 100644 src/scripting/v3/clientmodel.h create mode 100644 src/scripting/v3/virtualdesktopmodel.cpp create mode 100644 src/scripting/v3/virtualdesktopmodel.h create mode 100644 src/scripting/windowthumbnailitem.cpp create mode 100644 src/scripting/windowthumbnailitem.h create mode 100644 src/scripting/workspace_wrapper.cpp create mode 100644 src/scripting/workspace_wrapper.h create mode 100644 src/scripts/CMakeLists.txt create mode 100644 src/scripts/Messages.sh create mode 100644 src/scripts/desktopchangeosd/contents/ui/main.qml create mode 100644 src/scripts/desktopchangeosd/contents/ui/osd.qml create mode 100644 src/scripts/desktopchangeosd/metadata.desktop create mode 100644 src/scripts/minimizeall/contents/code/main.js create mode 100644 src/scripts/minimizeall/metadata.desktop create mode 100644 src/scripts/synchronizeskipswitcher/contents/code/main.js create mode 100644 src/scripts/synchronizeskipswitcher/metadata.desktop create mode 100644 src/scripts/videowall/contents/code/main.js create mode 100644 src/scripts/videowall/contents/config/main.xml create mode 100644 src/scripts/videowall/contents/ui/config.ui create mode 100644 src/scripts/videowall/metadata.desktop create mode 100644 src/settings.kcfgc create mode 100644 src/shadow.cpp create mode 100644 src/shadow.h create mode 100644 src/shadowitem.cpp create mode 100644 src/shadowitem.h create mode 100644 src/shortcutdialog.ui create mode 100644 src/sm.cpp create mode 100644 src/sm.h create mode 100644 src/surfaceitem.cpp create mode 100644 src/surfaceitem.h create mode 100644 src/surfaceitem_internal.cpp create mode 100644 src/surfaceitem_internal.h create mode 100644 src/surfaceitem_wayland.cpp create mode 100644 src/surfaceitem_wayland.h create mode 100644 src/surfaceitem_x11.cpp create mode 100644 src/surfaceitem_x11.h create mode 100644 src/syncalarmx11filter.cpp create mode 100644 src/syncalarmx11filter.h create mode 100644 src/tabbox/CMakeLists.txt create mode 100644 src/tabbox/clientmodel.cpp create mode 100644 src/tabbox/clientmodel.h create mode 100644 src/tabbox/desktopchain.cpp create mode 100644 src/tabbox/desktopchain.h create mode 100644 src/tabbox/desktopmodel.cpp create mode 100644 src/tabbox/desktopmodel.h create mode 100644 src/tabbox/kwindesktopswitcher.desktop create mode 100644 src/tabbox/kwinwindowswitcher.desktop create mode 100644 src/tabbox/switcheritem.cpp create mode 100644 src/tabbox/switcheritem.h create mode 100644 src/tabbox/tabbox.cpp create mode 100644 src/tabbox/tabbox.h create mode 100644 src/tabbox/tabbox_logging.cpp create mode 100644 src/tabbox/tabbox_logging.h create mode 100644 src/tabbox/tabboxconfig.cpp create mode 100644 src/tabbox/tabboxconfig.h create mode 100644 src/tabbox/tabboxhandler.cpp create mode 100644 src/tabbox/tabboxhandler.h create mode 100644 src/tabbox/x11_filter.cpp create mode 100644 src/tabbox/x11_filter.h create mode 100644 src/tablet_input.cpp create mode 100644 src/tablet_input.h create mode 100644 src/tabletmodemanager.cpp create mode 100644 src/tabletmodemanager.h create mode 100644 src/touch_input.cpp create mode 100644 src/touch_input.h create mode 100644 src/unmanaged.cpp create mode 100644 src/unmanaged.h create mode 100644 src/useractions.cpp create mode 100644 src/useractions.h create mode 100644 src/utils/CMakeLists.txt create mode 100644 src/utils/abstract_opengl_context_attribute_builder.cpp create mode 100644 src/utils/abstract_opengl_context_attribute_builder.h create mode 100644 src/utils/c_ptr.h create mode 100644 src/utils/common.cpp create mode 100644 src/utils/common.h create mode 100644 src/utils/damagejournal.h create mode 100644 src/utils/edid.cpp create mode 100644 src/utils/edid.h create mode 100644 src/utils/egl_context_attribute_builder.cpp create mode 100644 src/utils/egl_context_attribute_builder.h create mode 100644 src/utils/filedescriptor.cpp create mode 100644 src/utils/filedescriptor.h create mode 100644 src/utils/ramfile.cpp create mode 100644 src/utils/ramfile.h create mode 100644 src/utils/realtime.cpp create mode 100644 src/utils/realtime.h create mode 100644 src/utils/serviceutils.h create mode 100644 src/utils/subsurfacemonitor.cpp create mode 100644 src/utils/subsurfacemonitor.h create mode 100644 src/utils/udev.cpp create mode 100644 src/utils/udev.h create mode 100644 src/utils/xcbutils.cpp create mode 100644 src/utils/xcbutils.h create mode 100644 src/utils/xcursortheme.cpp create mode 100644 src/utils/xcursortheme.h create mode 100644 src/virtualdesktops.cpp create mode 100644 src/virtualdesktops.h create mode 100644 src/virtualdesktopsdbustypes.cpp create mode 100644 src/virtualdesktopsdbustypes.h create mode 100644 src/virtualkeyboard_dbus.cpp create mode 100644 src/virtualkeyboard_dbus.h create mode 100644 src/was_user_interaction_x11_filter.cpp create mode 100644 src/was_user_interaction_x11_filter.h create mode 100644 src/wayland/CMakeLists.txt create mode 100644 src/wayland/DESIGN.md create mode 100644 src/wayland/abstract_data_source.cpp create mode 100644 src/wayland/abstract_data_source.h create mode 100644 src/wayland/abstract_drop_handler.cpp create mode 100644 src/wayland/abstract_drop_handler.h create mode 100644 src/wayland/appmenu_interface.cpp create mode 100644 src/wayland/appmenu_interface.h create mode 100644 src/wayland/autotests/CMakeLists.txt create mode 100644 src/wayland/autotests/client/CMakeLists.txt create mode 100644 src/wayland/autotests/client/test_datadevice.cpp create mode 100644 src/wayland/autotests/client/test_datasource.cpp create mode 100644 src/wayland/autotests/client/test_drag_drop.cpp create mode 100644 src/wayland/autotests/client/test_error.cpp create mode 100644 src/wayland/autotests/client/test_fake_input.cpp create mode 100644 src/wayland/autotests/client/test_plasma_activities.cpp create mode 100644 src/wayland/autotests/client/test_plasma_virtual_desktop.cpp create mode 100644 src/wayland/autotests/client/test_plasmashell.cpp create mode 100644 src/wayland/autotests/client/test_pointer_constraints.cpp create mode 100644 src/wayland/autotests/client/test_selection.cpp create mode 100644 src/wayland/autotests/client/test_server_side_decoration.cpp create mode 100644 src/wayland/autotests/client/test_server_side_decoration_palette.cpp create mode 100644 src/wayland/autotests/client/test_shadow.cpp create mode 100644 src/wayland/autotests/client/test_shm_pool.cpp create mode 100644 src/wayland/autotests/client/test_text_input_v2.cpp create mode 100644 src/wayland/autotests/client/test_wayland_appmenu.cpp create mode 100644 src/wayland/autotests/client/test_wayland_blur.cpp create mode 100644 src/wayland/autotests/client/test_wayland_contrast.cpp create mode 100644 src/wayland/autotests/client/test_wayland_filter.cpp create mode 100644 src/wayland/autotests/client/test_wayland_output.cpp create mode 100644 src/wayland/autotests/client/test_wayland_seat.cpp create mode 100644 src/wayland/autotests/client/test_wayland_slide.cpp create mode 100644 src/wayland/autotests/client/test_wayland_subsurface.cpp create mode 100644 src/wayland/autotests/client/test_wayland_surface.cpp create mode 100644 src/wayland/autotests/client/test_wayland_windowmanagement.cpp create mode 100644 src/wayland/autotests/client/test_xdg_decoration.cpp create mode 100644 src/wayland/autotests/client/test_xdg_foreign.cpp create mode 100644 src/wayland/autotests/client/test_xdg_output.cpp create mode 100644 src/wayland/autotests/client/test_xdg_shell.cpp create mode 100644 src/wayland/autotests/server/CMakeLists.txt create mode 100644 src/wayland/autotests/server/test_datacontrol_interface.cpp create mode 100644 src/wayland/autotests/server/test_display.cpp create mode 100644 src/wayland/autotests/server/test_inputmethod_interface.cpp create mode 100644 src/wayland/autotests/server/test_keyboard_shortcuts_inhibitor_interface.cpp create mode 100644 src/wayland/autotests/server/test_layershellv1_interface.cpp create mode 100644 src/wayland/autotests/server/test_no_xdg_runtime_dir.cpp create mode 100644 src/wayland/autotests/server/test_screencast.cpp create mode 100644 src/wayland/autotests/server/test_seat.cpp create mode 100644 src/wayland/autotests/server/test_tablet_interface.cpp create mode 100644 src/wayland/autotests/server/test_textinputv3_interface.cpp create mode 100644 src/wayland/autotests/server/test_viewporter_interface.cpp create mode 100644 src/wayland/blur_interface.cpp create mode 100644 src/wayland/blur_interface.h create mode 100644 src/wayland/clientbuffer.cpp create mode 100644 src/wayland/clientbuffer.h create mode 100644 src/wayland/clientbuffer_p.h create mode 100644 src/wayland/clientbufferintegration.cpp create mode 100644 src/wayland/clientbufferintegration.h create mode 100644 src/wayland/clientconnection.cpp create mode 100644 src/wayland/clientconnection.h create mode 100644 src/wayland/compositor_interface.cpp create mode 100644 src/wayland/compositor_interface.h create mode 100644 src/wayland/contrast_interface.cpp create mode 100644 src/wayland/contrast_interface.h create mode 100644 src/wayland/datacontroldevice_v1_interface.cpp create mode 100644 src/wayland/datacontroldevice_v1_interface.h create mode 100644 src/wayland/datacontroldevicemanager_v1_interface.cpp create mode 100644 src/wayland/datacontroldevicemanager_v1_interface.h create mode 100644 src/wayland/datacontroloffer_v1_interface.cpp create mode 100644 src/wayland/datacontroloffer_v1_interface.h create mode 100644 src/wayland/datacontrolsource_v1_interface.cpp create mode 100644 src/wayland/datacontrolsource_v1_interface.h create mode 100644 src/wayland/datadevice_interface.cpp create mode 100644 src/wayland/datadevice_interface.h create mode 100644 src/wayland/datadevice_interface_p.h create mode 100644 src/wayland/datadevicemanager_interface.cpp create mode 100644 src/wayland/datadevicemanager_interface.h create mode 100644 src/wayland/dataoffer_interface.cpp create mode 100644 src/wayland/dataoffer_interface.h create mode 100644 src/wayland/datasource_interface.cpp create mode 100644 src/wayland/datasource_interface.h create mode 100644 src/wayland/display.cpp create mode 100644 src/wayland/display.h create mode 100644 src/wayland/display_p.h create mode 100644 src/wayland/dpms_interface.cpp create mode 100644 src/wayland/dpms_interface.h create mode 100644 src/wayland/drmclientbuffer.cpp create mode 100644 src/wayland/drmclientbuffer.h create mode 100644 src/wayland/drmleasedevice_v1_interface.cpp create mode 100644 src/wayland/drmleasedevice_v1_interface.h create mode 100644 src/wayland/drmleasedevice_v1_interface_p.h create mode 100644 src/wayland/fakeinput_interface.cpp create mode 100644 src/wayland/fakeinput_interface.h create mode 100644 src/wayland/filtered_display.cpp create mode 100644 src/wayland/filtered_display.h create mode 100644 src/wayland/idle_interface.cpp create mode 100644 src/wayland/idle_interface.h create mode 100644 src/wayland/idle_interface_p.h create mode 100644 src/wayland/idleinhibit_v1_interface.cpp create mode 100644 src/wayland/idleinhibit_v1_interface.h create mode 100644 src/wayland/idleinhibit_v1_interface_p.h create mode 100644 src/wayland/inputmethod_v1_interface.cpp create mode 100644 src/wayland/inputmethod_v1_interface.h create mode 100644 src/wayland/keyboard_interface.cpp create mode 100644 src/wayland/keyboard_interface.h create mode 100644 src/wayland/keyboard_interface_p.h create mode 100644 src/wayland/keyboard_shortcuts_inhibit_v1_interface.cpp create mode 100644 src/wayland/keyboard_shortcuts_inhibit_v1_interface.h create mode 100644 src/wayland/keystate_interface.cpp create mode 100644 src/wayland/keystate_interface.h create mode 100644 src/wayland/layershell_v1_interface.cpp create mode 100644 src/wayland/layershell_v1_interface.h create mode 100644 src/wayland/linuxdmabufv1clientbuffer.cpp create mode 100644 src/wayland/linuxdmabufv1clientbuffer.h create mode 100644 src/wayland/linuxdmabufv1clientbuffer_p.h create mode 100644 src/wayland/lockscreen_overlay_v1_interface.cpp create mode 100644 src/wayland/lockscreen_overlay_v1_interface.h create mode 100644 src/wayland/output_interface.cpp create mode 100644 src/wayland/output_interface.h create mode 100644 src/wayland/outputdevice_v2_interface.cpp create mode 100644 src/wayland/outputdevice_v2_interface.h create mode 100644 src/wayland/outputmanagement_v2_interface.cpp create mode 100644 src/wayland/outputmanagement_v2_interface.h create mode 100644 src/wayland/plasmashell_interface.cpp create mode 100644 src/wayland/plasmashell_interface.h create mode 100644 src/wayland/plasmavirtualdesktop_interface.cpp create mode 100644 src/wayland/plasmavirtualdesktop_interface.h create mode 100644 src/wayland/plasmawindowmanagement_interface.cpp create mode 100644 src/wayland/plasmawindowmanagement_interface.h create mode 100644 src/wayland/pointer_interface.cpp create mode 100644 src/wayland/pointer_interface.h create mode 100644 src/wayland/pointer_interface_p.h create mode 100644 src/wayland/pointerconstraints_v1_interface.cpp create mode 100644 src/wayland/pointerconstraints_v1_interface.h create mode 100644 src/wayland/pointerconstraints_v1_interface_p.h create mode 100644 src/wayland/pointergestures_v1_interface.cpp create mode 100644 src/wayland/pointergestures_v1_interface.h create mode 100644 src/wayland/pointergestures_v1_interface_p.h create mode 100644 src/wayland/primaryoutput_v1_interface.cpp create mode 100644 src/wayland/primaryoutput_v1_interface.h create mode 100644 src/wayland/primaryselectiondevice_v1_interface.cpp create mode 100644 src/wayland/primaryselectiondevice_v1_interface.h create mode 100644 src/wayland/primaryselectiondevicemanager_v1_interface.cpp create mode 100644 src/wayland/primaryselectiondevicemanager_v1_interface.h create mode 100644 src/wayland/primaryselectionoffer_v1_interface.cpp create mode 100644 src/wayland/primaryselectionoffer_v1_interface.h create mode 100644 src/wayland/primaryselectionsource_v1_interface.cpp create mode 100644 src/wayland/primaryselectionsource_v1_interface.h create mode 100644 src/wayland/protocols/README.md create mode 100644 src/wayland/protocols/wlr-data-control-unstable-v1.xml create mode 100644 src/wayland/protocols/wlr-layer-shell-unstable-v1.xml create mode 100644 src/wayland/region_interface.cpp create mode 100644 src/wayland/region_interface_p.h create mode 100644 src/wayland/relativepointer_v1_interface.cpp create mode 100644 src/wayland/relativepointer_v1_interface.h create mode 100644 src/wayland/relativepointer_v1_interface_p.h create mode 100644 src/wayland/screencast_v1_interface.cpp create mode 100644 src/wayland/screencast_v1_interface.h create mode 100644 src/wayland/seat_interface.cpp create mode 100644 src/wayland/seat_interface.h create mode 100644 src/wayland/seat_interface_p.h create mode 100644 src/wayland/server_decoration_interface.cpp create mode 100644 src/wayland/server_decoration_interface.h create mode 100644 src/wayland/server_decoration_palette_interface.cpp create mode 100644 src/wayland/server_decoration_palette_interface.h create mode 100644 src/wayland/shadow_interface.cpp create mode 100644 src/wayland/shadow_interface.h create mode 100644 src/wayland/shmclientbuffer.cpp create mode 100644 src/wayland/shmclientbuffer.h create mode 100644 src/wayland/slide_interface.cpp create mode 100644 src/wayland/slide_interface.h create mode 100644 src/wayland/subcompositor_interface.cpp create mode 100644 src/wayland/subcompositor_interface.h create mode 100644 src/wayland/subsurface_interface_p.h create mode 100644 src/wayland/surface_interface.cpp create mode 100644 src/wayland/surface_interface.h create mode 100644 src/wayland/surface_interface_p.h create mode 100644 src/wayland/surfacerole.cpp create mode 100644 src/wayland/surfacerole_p.h create mode 100644 src/wayland/tablet_v2_interface.cpp create mode 100644 src/wayland/tablet_v2_interface.h create mode 100644 src/wayland/tests/CMakeLists.txt create mode 100644 src/wayland/tests/copyclient.cpp create mode 100644 src/wayland/tests/dpmstest.cpp create mode 100644 src/wayland/tests/fakeoutput.cpp create mode 100644 src/wayland/tests/fakeoutput.h create mode 100644 src/wayland/tests/paneltest.cpp create mode 100644 src/wayland/tests/pasteclient.cpp create mode 100644 src/wayland/tests/plasmasurfacetest.cpp create mode 100644 src/wayland/tests/renderingservertest.cpp create mode 100644 src/wayland/tests/shadowtest.cpp create mode 100644 src/wayland/tests/subsurfacetest.cpp create mode 100644 src/wayland/tests/touchclienttest.cpp create mode 100644 src/wayland/tests/touchclienttest.h create mode 100644 src/wayland/tests/waylandservertest.cpp create mode 100644 src/wayland/tests/xdgforeigntest.cpp create mode 100644 src/wayland/tests/xdgtest.cpp create mode 100644 src/wayland/textinput.cpp create mode 100644 src/wayland/textinput.h create mode 100644 src/wayland/textinput_v2_interface.cpp create mode 100644 src/wayland/textinput_v2_interface.h create mode 100644 src/wayland/textinput_v2_interface_p.h create mode 100644 src/wayland/textinput_v3_interface.cpp create mode 100644 src/wayland/textinput_v3_interface.h create mode 100644 src/wayland/textinput_v3_interface_p.h create mode 100644 src/wayland/tools/.clang-format create mode 100644 src/wayland/tools/CMakeLists.txt create mode 100644 src/wayland/tools/README.md create mode 100644 src/wayland/tools/qtwaylandscanner.cpp create mode 100644 src/wayland/touch_interface.cpp create mode 100644 src/wayland/touch_interface.h create mode 100644 src/wayland/touch_interface_p.h create mode 100644 src/wayland/utils.h create mode 100644 src/wayland/utils/executable_path.h create mode 100644 src/wayland/utils/executable_path_proc.cpp create mode 100644 src/wayland/utils/executable_path_sysctl.cpp create mode 100644 src/wayland/viewporter_interface.cpp create mode 100644 src/wayland/viewporter_interface.h create mode 100644 src/wayland/viewporter_interface_p.h create mode 100644 src/wayland/xdgactivation_v1_interface.cpp create mode 100644 src/wayland/xdgactivation_v1_interface.h create mode 100644 src/wayland/xdgdecoration_v1_interface.cpp create mode 100644 src/wayland/xdgdecoration_v1_interface.h create mode 100644 src/wayland/xdgdecoration_v1_interface_p.h create mode 100644 src/wayland/xdgforeign_v2_interface.cpp create mode 100644 src/wayland/xdgforeign_v2_interface.h create mode 100644 src/wayland/xdgforeign_v2_interface_p.h create mode 100644 src/wayland/xdgoutput_v1_interface.cpp create mode 100644 src/wayland/xdgoutput_v1_interface.h create mode 100644 src/wayland/xdgshell_interface.cpp create mode 100644 src/wayland/xdgshell_interface.h create mode 100644 src/wayland/xdgshell_interface_p.h create mode 100644 src/wayland_server.cpp create mode 100644 src/wayland_server.h create mode 100644 src/waylandoutput.cpp create mode 100644 src/waylandoutput.h create mode 100644 src/waylandshellintegration.cpp create mode 100644 src/waylandshellintegration.h create mode 100644 src/waylandwindow.cpp create mode 100644 src/waylandwindow.h create mode 100644 src/window.cpp create mode 100644 src/window.h create mode 100644 src/window_property_notify_x11_filter.cpp create mode 100644 src/window_property_notify_x11_filter.h create mode 100644 src/windowitem.cpp create mode 100644 src/windowitem.h create mode 100644 src/workspace.cpp create mode 100644 src/workspace.h create mode 100644 src/x11eventfilter.cpp create mode 100644 src/x11eventfilter.h create mode 100644 src/x11syncmanager.cpp create mode 100644 src/x11syncmanager.h create mode 100644 src/x11window.cpp create mode 100644 src/x11window.h create mode 100644 src/xdgactivationv1.cpp create mode 100644 src/xdgactivationv1.h create mode 100644 src/xdgshellintegration.cpp create mode 100644 src/xdgshellintegration.h create mode 100644 src/xdgshellwindow.cpp create mode 100644 src/xdgshellwindow.h create mode 100644 src/xkb.cpp create mode 100644 src/xkb.h create mode 100644 src/xwayland/CMakeLists.txt create mode 100644 src/xwayland/clipboard.cpp create mode 100644 src/xwayland/clipboard.h create mode 100644 src/xwayland/databridge.cpp create mode 100644 src/xwayland/databridge.h create mode 100644 src/xwayland/datasource.cpp create mode 100644 src/xwayland/datasource.h create mode 100644 src/xwayland/dnd.cpp create mode 100644 src/xwayland/dnd.h create mode 100644 src/xwayland/drag.cpp create mode 100644 src/xwayland/drag.h create mode 100644 src/xwayland/drag_wl.cpp create mode 100644 src/xwayland/drag_wl.h create mode 100644 src/xwayland/drag_x.cpp create mode 100644 src/xwayland/drag_x.h create mode 100644 src/xwayland/lib/CMakeLists.txt create mode 100644 src/xwayland/lib/xauthority.cpp create mode 100644 src/xwayland/lib/xauthority.h create mode 100644 src/xwayland/lib/xwaylandsocket.cpp create mode 100644 src/xwayland/lib/xwaylandsocket.h create mode 100644 src/xwayland/primary.cpp create mode 100644 src/xwayland/primary.h create mode 100644 src/xwayland/selection.cpp create mode 100644 src/xwayland/selection.h create mode 100644 src/xwayland/selection_source.cpp create mode 100644 src/xwayland/selection_source.h create mode 100644 src/xwayland/transfer.cpp create mode 100644 src/xwayland/transfer.h create mode 100644 src/xwayland/xwayland.cpp create mode 100644 src/xwayland/xwayland.h create mode 100644 src/xwayland/xwayland_interface.h create mode 100644 src/xwayland/xwaylandlauncher.cpp create mode 100644 src/xwayland/xwaylandlauncher.h create mode 100644 src/xwayland/xwldrophandler.cpp create mode 100644 src/xwayland/xwldrophandler.h create mode 100644 src/xwaylandwindow.cpp create mode 100644 src/xwaylandwindow.h create mode 100644 tests/CMakeLists.txt create mode 100644 tests/cursorhotspottest.cpp create mode 100644 tests/inputmethodstest.qml create mode 100644 tests/lockscreenoverlaytest.cpp create mode 100644 tests/lockscreenoverlaytest.desktop create mode 100644 tests/normalhintsbasesizetest.cpp create mode 100644 tests/pointerconstraintstest.cpp create mode 100644 tests/pointerconstraintstest.h create mode 100644 tests/pointerconstraintstest.qml create mode 100644 tests/pointergesturestest.cpp create mode 100644 tests/pointergesturestest.qml create mode 100644 tests/screenedgeshowtest.cpp create mode 100644 tests/unmapdestroytest.qml create mode 100644 tests/x11shadowreader.cpp create mode 100644 tests/xdgactivationtest-qt5.cpp create mode 100644 tests/xdgactivationtest-qt6.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..18d3a68 --- /dev/null +++ b/.clang-format @@ -0,0 +1,83 @@ +--- +--- +# SPDX-FileCopyrightText: 2019 Christoph Cullmann +# SPDX-FileCopyrightText: 2019 Gernot Gebhard +# +# SPDX-License-Identifier: MIT + +--- +Language: JavaScript +DisableFormat: true +--- + +# Style for C++ +Language: Cpp +Standard: c++17 + +# base is WebKit coding style: https://webkit.org/code-style-guidelines/ +# below are only things set that diverge from this style! +BasedOnStyle: WebKit + +# 4 spaces indent +TabWidth: 4 + +# No line limit +ColumnLimit: 0 + +# sort includes inside line separated groups +SortIncludes: true + +# Braces are usually attached, but not after functions or class declarations. +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: true + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: true + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + +# CrlInstruction *a; +PointerAlignment: Right + +# horizontally aligns arguments after an open bracket. +AlignAfterOpenBracket: Align + +# don't move all parameters to new line +AllowAllParametersOfDeclarationOnNextLine: false + +# no single line functions +AllowShortFunctionsOnASingleLine: None + +# In case we have an if statement with multiple lines the operator should be at the beginning of the line +# but we do not want to break assignments +BreakBeforeBinaryOperators: NonAssignment + +# format C++11 braced lists like function calls +Cpp11BracedListStyle: true + +# do not put a space before C++11 braced lists +SpaceBeforeCpp11BracedList: false + +# no namespace indentation to keep indent level low +NamespaceIndentation: None + +# we use template< without space. +SpaceAfterTemplateKeyword: false + +# Always break after template declaration +AlwaysBreakTemplateDeclarations: true + +# macros for which the opening brace stays attached. +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE , wl_resource_for_each, wl_resource_for_each_safe ] + +# keep lambda formatting multi-line if not empty +AllowShortLambdasOnASingleLine: Empty + +# We do not want clang-format to put all arguments on a new line +AllowAllArgumentsOnNextLine: false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..513215a --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# Ignore the following files +.vscode +*~ +*.[oa] +*.diff +*.kate-swp +*.kdev4 +.kdev_include_paths +*.kdevelop.pcs +*.moc +*.moc.cpp +*.orig +*.user +.*.swp +.swp.* +Doxyfile +Makefile +avail +random_seed +/build*/ +CMakeLists.txt.user* +*.unc-backup* +/compile_commands.json +.clangd +.idea +/cmake-build* +.cache diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..24b5342 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: None +# SPDX-License-Identifier: CC0-1.0 + +include: + - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux.yml + - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd.yml + +suse_tumbleweed_qt515_reduced_featureset: + extends: suse_tumbleweed_qt515 + script: + - git config --global --add safe.directory $CI_PROJECT_DIR + - python3 -u ci-utilities/run-ci-build.py --project $CI_PROJECT_NAME --branch $CI_COMMIT_REF_NAME --platform Linux --extra-cmake-args="-DKWIN_BUILD_KCMS=OFF -DKWIN_BUILD_SCREENLOCKER=OFF -DKWIN_BUILD_TABBOX=OFF -DKWIN_BUILD_ACTIVITIES=OFF -DKWIN_BUILD_RUNNERS=OFF -DKWIN_BUILD_NOTIFICATIONS=OFF" --skip-publishing diff --git a/.kde-ci.yml b/.kde-ci.yml new file mode 100644 index 0000000..76e651b --- /dev/null +++ b/.kde-ci.yml @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: None +# SPDX-License-Identifier: CC0-1.0 + +Dependencies: +- 'on': ['@all'] + 'require': + 'frameworks/breeze-icons': '@stable' + 'frameworks/extra-cmake-modules': '@stable' + 'frameworks/kconfig': '@stable' + 'frameworks/kconfigwidgets': '@stable' + 'frameworks/kcoreaddons': '@stable' + 'frameworks/kcrash': '@stable' + 'frameworks/kglobalaccel': '@stable' + 'frameworks/ki18n': '@stable' + 'frameworks/kidletime': '@stable' + 'frameworks/knotifications': '@stable' + 'frameworks/kpackage': '@stable' + 'frameworks/plasma-framework': '@stable' + 'frameworks/kwayland': '@stable' + 'frameworks/kwidgetsaddons': '@stable' + 'frameworks/kwindowsystem': '@stable' + 'frameworks/kcompletion': '@stable' + 'frameworks/kdeclarative': '@stable' + 'frameworks/kcmutils': '@stable' + 'frameworks/knewstuff': '@stable' + 'frameworks/kservice': '@stable' + 'frameworks/ktextwidgets': '@stable' + 'frameworks/kxmlgui': '@stable' + 'frameworks/kactivities': '@stable' + 'frameworks/kdoctools': '@stable' + 'frameworks/krunner': '@stable' + 'frameworks/kirigami': '@stable' + 'libraries/libqaccessibilityclient': '@latest' + 'plasma/breeze': '@same' + 'plasma/kdecoration': '@same' + 'plasma/kscreenlocker': '@same' + +Options: + use-ccache: True + require-passing-tests-on: [ 'Linux'] diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8d4e2f4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,490 @@ +cmake_minimum_required(VERSION 3.16) + +set(PROJECT_VERSION "5.26.5") # Handled by release scripts +project(KWin VERSION ${PROJECT_VERSION}) + +set(CMAKE_C_STANDARD 99) + +set(QT_MIN_VERSION "5.15.2") +set(KF5_MIN_VERSION "5.98.0") +set(KDE_COMPILERSETTINGS_LEVEL "5.82") + +find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) + +include(FeatureSummary) +include(WriteBasicConfigVersionFile) +include(GenerateExportHeader) + +# where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is checked +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PATH}) + +include(KDEInstallDirs) +include(KDECMakeSettings) +include(KDECompilerSettings NO_POLICY_SCOPE) +include(KDEClangFormat) +include(KDEGitCommitHooks) + +include(ECMInstallIcons) +include(ECMOptionalAddSubdirectory) +include(ECMConfiguredInstall) +include(ECMQtDeclareLoggingCategory) + +find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS + Concurrent + Core + DBus + Quick + UiTools + Widgets +) +if (QT_MAJOR_VERSION EQUAL "5") + find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS X11Extras) + find_package(Qt5XkbCommonSupport REQUIRED) +else() + find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS WaylandClient ShaderTools) +endif() + +find_package(Qt${QT_MAJOR_VERSION}Test ${QT_MIN_VERSION} CONFIG QUIET) +set_package_properties(Qt${QT_MAJOR_VERSION}Test PROPERTIES + PURPOSE "Required for tests" + TYPE OPTIONAL +) +add_feature_info("Qt${QT_MAJOR_VERSION}Test" Qt${QT_MAJOR_VERSION}Test_FOUND "Required for building tests") +if (NOT Qt${QT_MAJOR_VERSION}Test_FOUND) + set(BUILD_TESTING OFF CACHE BOOL "Build the testing tree.") +endif() + +add_definitions( + -DQT_DISABLE_DEPRECATED_BEFORE=0 + + -DQT_NO_KEYWORDS + + -DQT_USE_QSTRINGBUILDER + -DQT_NO_URL_CAST_FROM_STRING + -DQT_NO_CAST_TO_ASCII + -DQT_NO_FOREACH +) + +# Prevent EGL headers from including platform headers, in particular Xlib.h. +add_definitions(-DMESA_EGL_NO_X11_HEADERS) +add_definitions(-DEGL_NO_X11) +add_definitions(-DEGL_NO_PLATFORM_SPECIFIC_TYPES) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# required frameworks by Core +find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS + Auth + Config + ConfigWidgets + CoreAddons + Crash + DBusAddons + GlobalAccel + I18n + IdleTime + Package + Plasma + WidgetsAddons + WindowSystem +) +# required frameworks by config modules +find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS + Declarative + KCMUtils + NewStuff + Service + XmlGui + ItemViews +) + +find_package(Threads) +set_package_properties(Threads PROPERTIES + PURPOSE "Needed for VirtualTerminal support in KWin Wayland" + TYPE REQUIRED +) + +find_package(KF5Wayland ${KF5_MIN_VERSION} CONFIG) +set_package_properties(KF5Wayland PROPERTIES + PURPOSE "Required to build wayland platform plugin and tests" + TYPE REQUIRED +) + +# optional frameworks +find_package(KF5Activities ${KF5_MIN_VERSION} CONFIG) +set_package_properties(KF5Activities PROPERTIES + PURPOSE "Enable building of KWin with kactivities support" + TYPE OPTIONAL +) +add_feature_info("KF5Activities" KF5Activities_FOUND "Enable building of KWin with kactivities support") + +find_package(KF5DocTools ${KF5_MIN_VERSION} CONFIG) +set_package_properties(KF5DocTools PROPERTIES + PURPOSE "Enable building documentation" + TYPE OPTIONAL +) +add_feature_info("KF5DocTools" KF5DocTools_FOUND "Enable building documentation") + +find_package(KF5Runner ${KF5_MIN_VERSION} CONFIG) +set_package_properties(KF5Runner PROPERTIES + PURPOSE "Enable building of KWin with krunner support" + TYPE OPTIONAL + ) +add_feature_info("KF5Runner" KF5Runner_FOUND "Enable building of KWin with krunner support") + +find_package(KF5Kirigami2 ${KF5_MIN_VERSION} CONFIG) +set_package_properties(KF5Kirigami2 PROPERTIES + DESCRIPTION "A QtQuick based components set" + PURPOSE "Required at runtime for Virtual desktop KCM" + TYPE RUNTIME +) + +find_package(KDecoration2 ${PROJECT_VERSION} CONFIG REQUIRED) + +find_package(Breeze 5.9.0 CONFIG) +set_package_properties(Breeze PROPERTIES + TYPE OPTIONAL + PURPOSE "For setting the default window decoration plugin" +) +if (${Breeze_FOUND}) + if (${BREEZE_WITH_KDECORATION}) + set(HAVE_BREEZE_DECO true) + else() + set(HAVE_BREEZE_DECO FALSE) + endif() +else() + set(HAVE_BREEZE_DECO FALSE) +endif() +add_feature_info("Breeze-Decoration" HAVE_BREEZE_DECO "Default decoration plugin Breeze") + +find_package(EGL) +set_package_properties(EGL PROPERTIES + TYPE REQUIRED + PURPOSE "Required to build KWin with EGL support" +) + +find_package(epoxy 1.3) +set_package_properties(epoxy PROPERTIES + DESCRIPTION "libepoxy" + URL "https://github.com/anholt/libepoxy" + TYPE REQUIRED + PURPOSE "OpenGL dispatch library" +) + +set(HAVE_DL_LIBRARY FALSE) +if (epoxy_HAS_GLX) + find_library(DL_LIBRARY dl) + if (DL_LIBRARY) + set(HAVE_DL_LIBRARY TRUE) + endif() +endif() + +check_cxx_source_compiles(" +#include +#include +#include + +int main() { + const int size = 10; + int fd = memfd_create(\"test\", MFD_CLOEXEC | MFD_ALLOW_SEALING); + ftruncate(fd, size); + fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL); + mmap(nullptr, size, PROT_WRITE, MAP_SHARED, fd, 0); +}" HAVE_MEMFD) + +find_package(Wayland 1.2 OPTIONAL_COMPONENTS Egl) +set_package_properties(Wayland PROPERTIES + TYPE REQUIRED + PURPOSE "Required for building KWin with Wayland support" +) +add_feature_info("Wayland::EGL" Wayland_Egl_FOUND "Enable building of Wayland backend.") +set(HAVE_WAYLAND_EGL FALSE) +if (Wayland_Egl_FOUND) + set(HAVE_WAYLAND_EGL TRUE) +endif() + +find_package(Wayland 1.20 REQUIRED COMPONENTS + Server +) + +find_package(WaylandProtocols 1.25) +set_package_properties(WaylandProtocols PROPERTIES + TYPE REQUIRED + PURPOSE "Collection of Wayland protocols that add functionality not available in the Wayland core protocol" + URL "https://gitlab.freedesktop.org/wayland/wayland-protocols/" +) + +find_package(PlasmaWaylandProtocols 1.9.0 CONFIG) +set_package_properties(PlasmaWaylandProtocols PROPERTIES + TYPE REQUIRED + PURPOSE "Collection of Plasma-specific Wayland protocols" + URL "https://invent.kde.org/libraries/plasma-wayland-protocols/" +) + +if (QT_MAJOR_VERSION EQUAL "5") + find_package(QtWaylandScanner) + set_package_properties(QtWaylandScanner PROPERTIES + TYPE REQUIRED + PURPOSE "Required for building KWin with Wayland support" + ) +endif() + +find_package(XKB 0.7.0) +set_package_properties(XKB PROPERTIES + TYPE REQUIRED + PURPOSE "Required for building KWin with Wayland support" +) + +find_package(Libinput 1.19) +set_package_properties(Libinput PROPERTIES TYPE REQUIRED PURPOSE "Required for input handling on Wayland.") + +find_package(UDev) +set_package_properties(UDev PROPERTIES + URL "https://www.freedesktop.org/wiki/Software/systemd/" + DESCRIPTION "Linux device library." + TYPE REQUIRED + PURPOSE "Required for input handling on Wayland." +) + +find_package(Libdrm 2.4.108) +set_package_properties(Libdrm PROPERTIES TYPE REQUIRED PURPOSE "Required for drm output on Wayland.") + +find_package(gbm) +set_package_properties(gbm PROPERTIES TYPE REQUIRED PURPOSE "Required for egl output of drm backend.") +if (${gbm_VERSION} GREATER_EQUAL 21.1) + set(HAVE_GBM_BO_GET_FD_FOR_PLANE 1) +else() + set(HAVE_GBM_BO_GET_FD_FOR_PLANE 0) +endif() +if (${gbm_VERSION} GREATER_EQUAL 21.3) + set(HAVE_GBM_BO_CREATE_WITH_MODIFIERS2 1) +else() + set(HAVE_GBM_BO_CREATE_WITH_MODIFIERS2 0) +endif() + +pkg_check_modules(Libxcvt IMPORTED_TARGET libxcvt>=0.1.1 REQUIRED) +add_feature_info(Libxcvt Libxcvt_FOUND "Required for generating modes in the drm backend") + +find_package(X11) +set_package_properties(X11 PROPERTIES + DESCRIPTION "X11 libraries" + URL "https://www.x.org" + TYPE REQUIRED +) +add_feature_info("XInput" X11_Xi_FOUND "Required for poll-free mouse cursor updates") +set(HAVE_X11_XINPUT ${X11_Xinput_FOUND}) + +find_package(lcms2) +set_package_properties(lcms2 PROPERTIES + DESCRIPTION "Small-footprint color management engine" + URL "http://www.littlecms.com" + TYPE REQUIRED + PURPOSE "Required for the color management system" +) + +# All the required XCB components +find_package(XCB 1.10 REQUIRED COMPONENTS + COMPOSITE + CURSOR + DAMAGE + GLX + ICCCM + IMAGE + KEYSYMS + RANDR + RENDER + SHAPE + SHM + SYNC + XCB + XFIXES + XINERAMA +) +set_package_properties(XCB PROPERTIES TYPE REQUIRED) + +# and the optional XCB dependencies +if (XCB_ICCCM_VERSION VERSION_LESS "0.4") + set(XCB_ICCCM_FOUND FALSE) +endif() +add_feature_info("XCB-ICCCM" XCB_ICCCM_FOUND "Required for building test applications for KWin") + +find_package(X11_XCB) +set_package_properties(X11_XCB PROPERTIES + PURPOSE "Required for building X11 windowed backend of kwin_wayland" + TYPE OPTIONAL +) + +# dependencies for QPA plugin +if (QT_MAJOR_VERSION EQUAL "5") + find_package(Qt5FontDatabaseSupport REQUIRED) + find_package(Qt5ThemeSupport REQUIRED) + find_package(Qt5ServiceSupport REQUIRED) + find_package(Qt5EventDispatcherSupport REQUIRED) +endif() + +find_package(Freetype REQUIRED) +set_package_properties(Freetype PROPERTIES + DESCRIPTION "A font rendering engine" + URL "https://www.freetype.org" + TYPE REQUIRED + PURPOSE "Needed for KWin's QPA plugin." +) +find_package(Fontconfig REQUIRED) +set_package_properties(Fontconfig PROPERTIES + TYPE REQUIRED + PURPOSE "Needed for KWin's QPA plugin." +) + +find_package(Xwayland) +set_package_properties(Xwayland PROPERTIES + URL "https://x.org" + DESCRIPTION "Xwayland X server" + TYPE RUNTIME + PURPOSE "Needed for running kwin_wayland" +) +set(HAVE_XWAYLAND_LISTENFD ${Xwayland_HAVE_LISTENFD}) + +find_package(Libcap) +set_package_properties(Libcap PROPERTIES + TYPE OPTIONAL + PURPOSE "Needed for running kwin_wayland with real-time scheduling policy" +) +set(HAVE_LIBCAP ${Libcap_FOUND}) + +find_package(hwdata) +set_package_properties(hwdata PROPERTIES + TYPE RUNTIME + PURPOSE "Runtime-only dependency needed for mapping monitor hardware vendor IDs to full names" + URL "https://github.com/vcrhonek/hwdata" +) + +find_package(QAccessibilityClient CONFIG) +set_package_properties(QAccessibilityClient PROPERTIES + URL "https://www.kde.org" + DESCRIPTION "KDE client-side accessibility library" + TYPE OPTIONAL + PURPOSE "Required to enable accessibility features" +) +set(HAVE_ACCESSIBILITY ${QAccessibilityClient_FOUND}) + +include(ECMFindQmlModule) +ecm_find_qmlmodule(QtQuick 2.3) +ecm_find_qmlmodule(QtQuick.Controls 2.15) +ecm_find_qmlmodule(QtQuick.Layouts 1.3) +ecm_find_qmlmodule(QtQuick.Window 2.1) +ecm_find_qmlmodule(QtMultimedia 5.0) +ecm_find_qmlmodule(org.kde.kquickcontrolsaddons 2.0) +ecm_find_qmlmodule(org.kde.plasma.core 2.0) +ecm_find_qmlmodule(org.kde.plasma.components 2.0) + +########### configure tests ############### +include(CMakeDependentOption) + +option(KWIN_BUILD_DECORATIONS "Enable building of KWin decorations." ON) +option(KWIN_BUILD_KCMS "Enable building of KWin configuration modules." ON) +option(KWIN_BUILD_NOTIFICATIONS "Enable building of KWin with knotifications support" ON) +option(KWIN_BUILD_SCREENLOCKER "Enable building of KWin lockscreen functionality" ON) +option(KWIN_BUILD_TABBOX "Enable building of KWin Tabbox functionality" ON) +cmake_dependent_option(KWIN_BUILD_ACTIVITIES "Enable building of KWin with kactivities support" ON "KF5Activities_FOUND" OFF) +cmake_dependent_option(KWIN_BUILD_RUNNERS "Enable building of KWin with krunner support" ON "KF5Runner_FOUND" OFF) + +# Binary name of KWin +set(KWIN_NAME "kwin") +set(KWIN_INTERNAL_NAME_X11 "kwin_x11") +set(KWIN_INTERNAL_NAME_WAYLAND "kwin_wayland") + +include_directories(${XKB_INCLUDE_DIR}) + +set(HAVE_EPOXY_GLX ${epoxy_HAS_GLX}) + +# for kwin internal things +set(HAVE_X11_XCB ${X11_XCB_FOUND}) + +include(CheckIncludeFile) +include(CheckIncludeFiles) +include(CheckSymbolExists) + +check_symbol_exists(SCHED_RESET_ON_FORK "sched.h" HAVE_SCHED_RESET_ON_FORK) +add_feature_info("SCHED_RESET_ON_FORK" + HAVE_SCHED_RESET_ON_FORK + "Required for running kwin_wayland with real-time scheduling") + + +pkg_check_modules(PipeWire IMPORTED_TARGET libpipewire-0.3>=0.3.29) +add_feature_info(PipeWire PipeWire_FOUND "Required for Wayland screencasting") + +if (KWIN_BUILD_NOTIFICATIONS) + find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Notifications) +endif() + +if (KWIN_BUILD_SCREENLOCKER) + find_package(KScreenLocker CONFIG REQUIRED) + set_package_properties(KScreenLocker PROPERTIES + TYPE REQUIRED + PURPOSE "For screenlocker integration in kwin_wayland" + ) +endif() + +########### global ############### + +include_directories(BEFORE + ${CMAKE_CURRENT_BINARY_DIR}/src/libkwineffects + ${CMAKE_CURRENT_BINARY_DIR}/src/wayland + ${CMAKE_CURRENT_BINARY_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR}/src/libkwineffects + ${CMAKE_CURRENT_SOURCE_DIR}/src/effects + ${CMAKE_CURRENT_SOURCE_DIR}/src/tabbox + ${CMAKE_CURRENT_SOURCE_DIR}/src/platformsupport + ${CMAKE_CURRENT_SOURCE_DIR}/src/colors +) + +if (KF5DocTools_FOUND) + add_subdirectory(doc) + kdoctools_install(po) +endif() + +include(ECMSetupQtPluginMacroNames) +ecm_setup_qtplugin_macro_names( + JSON_ARG2 + "KWIN_EFFECT_FACTORY" + JSON_ARG3 + "KWIN_EFFECT_FACTORY_ENABLED" + "KWIN_EFFECT_FACTORY_SUPPORTED" + JSON_ARG4 + "KWIN_EFFECT_FACTORY_SUPPORTED_ENABLED" + CONFIG_CODE_VARIABLE + PACKAGE_SETUP_AUTOMOC_VARIABLES +) + +add_subdirectory(data) +add_subdirectory(kconf_update) +add_subdirectory(src) + +if (BUILD_TESTING) + find_package(Wayland REQUIRED COMPONENTS Client) + + add_subdirectory(autotests) + add_subdirectory(tests) +endif() + +# add clang-format target for all our real source files +file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h) +kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) +kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT) + +feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) + +include(CMakePackageConfigHelpers) +set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KWinDBusInterface") +configure_package_config_file(KWinDBusInterfaceConfig.cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/KWinDBusInterfaceConfig.cmake" + PATH_VARS KDE_INSTALL_DBUSINTERFACEDIR + INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/KWinDBusInterfaceConfig.cmake + DESTINATION ${CMAKECONFIG_INSTALL_DIR}) + +ecm_install_configured_files(INPUT plasma-kwin_x11.service.in plasma-kwin_wayland.service.in @ONLY + DESTINATION ${KDE_INSTALL_SYSTEMDUSERUNITDIR}) + +ki18n_install(po) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..86d66f9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,125 @@ +# Contributing to KWin + +## Chatting + +Come on by and ask about anything you run into when hacking on KWin! + +KWin's Matrix room on our instance is located here: https://matrix.to/#/#kwin:kde.org. +You can grab an Matrix account at https://webchat.kde.org/ if you don't already have one from us or another provider. + +The Matrix room is bridged to `#kde-kwin` on Libera, allowing IRC users to access it. + +## What Needs Doing + +There's a large amount of bugs open for KWin on our [Bugzilla instance](https://bugs.kde.org/describecomponents.cgi?product=kwin). + +## Where Stuff Is + +Everything codewise for KWin itself is located in the `src` directory. + +### Settings Pages / KCMs + +All the settings pages for KWin found in System Settings are located in `src/kcmkwin`. + +### Default Decorations + +The Breeze decorations theme is not located in the KWin repository, and is in fact part of the [Breeze repository here](https://invent.kde.org/plasma/breeze), in `kdecoration`. + +### Tab Switcher + +The default visual appearance of the tab switcher is not located in the KWin repository, and is in fact part of [Plasma Workspace](https://invent.kde.org/plasma/plasma-workspace), located at `lookandfeel/contents/windowswitcher`. + +Other window switchers usually shipped by default are located in [Plasma Addons](https://invent.kde.org/plasma/kdeplasma-addons), located in the `windowswitchers` directory. + +### Window Management + +Most window management stuff (layouting, movement, properties, communication between client<->server) is defined in files ending with `client`, such as `x11client.cpp` and `xdgshellclient.cpp`. + +### Window Effects + +Window effects are located in the `effects` folder in `src`, with one folder per effect. +Not everything here is an effect as exposed in the configuration UI, such as the colour picker in `src/effects/colorpicker`. + +### Scripting API + +Many objects in KWin are exposed directly to the scripting API; scriptable properties are marked with Q_PROPERTY and functions that scripts can invoke on them. + +Other scripting stuff is located in `src/scripting`. + +## Conventions + +### Coding Conventions + +KWin's coding conventions are located [here](doc/coding-conventions.md). + +KWin additionally follows [KDE's Frameworks Coding Style]((https://techbase.kde.org/Policies/Frameworks_Coding_Style)). + +### Commits + +We usually use this convention for commits in KWin: + +``` +component/subcomponent: Do a thing + +This is a body of the commit message, +elaborating on why we're doing thing. +``` + +While this isn't a hard rule, it's appreciated for easy scanning of commits by their messages. + +## Contributing + +KWin uses KDE's GitLab instance for submitting code. + +You can read about the [KDE workflow here](https://community.kde.org/Infrastructure/GitLab). + +## Running KWin From Source + +KWin uses CMake like most KDE projects, so you can build it like this: + +```bash +mkdir _build +cd _build +cmake .. +make +``` + +People hacking on much KDE software may want to set up kdesrc-build. + +Once built, you can either install it over your system KWin (not recommended) or run it from the build directory directly. + +Running it from your build directory looks like this: +```bash +# from the root of your build directory + +cd bin + +# for wayland, starts nested session: with console + +env QT_PLUGIN_PATH=`pwd` dbus-run-session ./kwin_wayland --xwayland konsole + +# or for x11, replaces current kwin instance: + +env QT_PLUGIN_PATH=`pwd` ./kwin_x11 --replace + +``` + +QT_PLUGIN_PATH tells Qt to load KWin's plugins from the build directory, and not from your system KWin. + +The dbus-run-session is needed to prevent the nested KWin instance from conflicting with your session KWin instance when exporting objects onto the bus, or with stuff like global shortcuts. + +## Using A Debugger + +Trying to attach a debugger to a running KWin instance from within itself will likely be the last thing you do in the session, as KWin will freeze until you resume it from your debugger, which you need KWin to interact with. + +Instead, either attach a debugger to a nested KWin instance or debug over SSH. + +## Tests + +KWin has a series of unit tests and integration tests that ensure everything is running as expected. + +If you're adding substantial new code, it's expected that you'll write tests for it to ensure that it's working as expected. + +If you're fixing a bug, it's appreciated, but not expected, that you add a test case for the bug you fix. + +You can read more about [KWin's testing infrastructure here](doc/TESTING.md). diff --git a/KWinDBusInterfaceConfig.cmake.in b/KWinDBusInterfaceConfig.cmake.in new file mode 100644 index 0000000..e3fe390 --- /dev/null +++ b/KWinDBusInterfaceConfig.cmake.in @@ -0,0 +1,10 @@ +@PACKAGE_INIT@ + +set(KWIN_INTERFACE "@PACKAGE_KDE_INSTALL_DBUSINTERFACEDIR@/org.kde.KWin.xml") +set(KWIN_COMPOSITING_INTERFACE "@PACKAGE_KDE_INSTALL_DBUSINTERFACEDIR@/org.kde.kwin.Compositing.xml") +set(KWIN_EFFECTS_INTERFACE "@PACKAGE_KDE_INSTALL_DBUSINTERFACEDIR@/org.kde.kwin.Effects.xml") +set(KWIN_VIRTUALKEYBOARD_INTERFACE "@PACKAGE_KDE_INSTALL_DBUSINTERFACEDIR@/org.kde.kwin.VirtualKeyboard.xml") +set(KWIN_TABLETMODE_INTERFACE "@PACKAGE_KDE_INSTALL_DBUSINTERFACEDIR@/org.kde.KWin.TabletModeManager.xml") +set(KWIN_INPUTDEVICE_INTERFACE "@PACKAGE_KDE_INSTALL_DBUSINTERFACEDIR@/org.kde.kwin.InputDevice.xml") +set(KWIN_COLORCORRECT_INTERFACE "@PACKAGE_KDE_INSTALL_DBUSINTERFACEDIR@/org.kde.kwin.ColorCorrect.xml") +set(KWIN_WAYLAND_BIN_PATH "@CMAKE_INSTALL_FULL_BINDIR@/kwin_wayland") diff --git a/LICENSES/BSD-3-Clause.txt b/LICENSES/BSD-3-Clause.txt new file mode 100644 index 0000000..0741db7 --- /dev/null +++ b/LICENSES/BSD-3-Clause.txt @@ -0,0 +1,26 @@ +Copyright (c) . All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSES/CC0-1.0.txt b/LICENSES/CC0-1.0.txt new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/LICENSES/CC0-1.0.txt @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/LICENSES/GPL-2.0-only.txt b/LICENSES/GPL-2.0-only.txt new file mode 100644 index 0000000..0f3d641 --- /dev/null +++ b/LICENSES/GPL-2.0-only.txt @@ -0,0 +1,319 @@ +GNU GENERAL PUBLIC LICENSE + +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. + +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public License is intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. This General Public License applies to +most of the Free Software Foundation's software and to any other program whose +authors commit to using it. (Some other Free Software Foundation software +is covered by the GNU Lesser General Public License instead.) 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 this service 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 make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or +for a fee, you must give the recipients all the rights that you have. 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. + +We protect your rights with two steps: (1) copyright the software, and (2) +offer you this license which gives you legal permission to copy, distribute +and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If +the software is modified by someone else and passed on, we want its recipients +to know that what they have is not the original, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that redistributors of a free program will individually +obtain patent licenses, in effect making the program proprietary. To prevent +this, we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms +of this General Public License. The "Program", below, refers to any such program +or work, and a "work based on the Program" means either the Program or any +derivative work under copyright law: that is to say, a work containing the +Program or a portion of it, either verbatim or with modifications and/or translated +into another language. (Hereinafter, translation is included without limitation +in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running the Program +is not restricted, and the output from the Program is covered only if its +contents constitute a work based on the Program (independent of having been +made by running the Program). Whether that is true depends on what the Program +does. + +1. You may copy and distribute 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 and disclaimer of warranty; +keep intact all the notices that refer to this License and to the absence +of any warranty; and give any other recipients of the Program a copy of this +License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, +thus forming a work based on the Program, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + +a) You must cause the modified files to carry prominent notices stating that +you changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in whole or +in part contains or is derived from the Program or any part thereof, to be +licensed as a whole at no charge to all third parties under the terms of this +License. + +c) If the modified program normally reads commands interactively when run, +you must cause it, when started running for such interactive use in the most +ordinary way, to print or display an announcement including an appropriate +copyright notice and a notice that there is no warranty (or else, saying that +you provide a warranty) and that users may redistribute the program under +these conditions, and telling the user how to view a copy of this License. +(Exception: if the Program itself is interactive but does not normally print +such an announcement, your work based on the Program is not required to print +an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Program, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Program. + +In addition, mere aggregation of another work not based on the Program with +the Program (or with a work based on the Program) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may copy and distribute the Program (or a work based on it, under Section +2) in object code or executable form under the terms of Sections 1 and 2 above +provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable source code, +which must be distributed under the terms of Sections 1 and 2 above on a medium +customarily used for software interchange; or, + +b) Accompany it with a written offer, valid for at least three years, to give +any third party, for a charge no more than your cost of physically performing +source distribution, a complete machine-readable copy of the corresponding +source code, to be distributed under the terms of Sections 1 and 2 above on +a medium customarily used for software interchange; or, + +c) Accompany it with the information you received as to the offer to distribute +corresponding source code. (This alternative is allowed only for noncommercial +distribution and only if you received the program in object code or executable +form with such an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for making +modifications to it. For an executable work, complete source code means all +the source code for all modules it contains, plus any associated interface +definition files, plus the scripts used to control compilation and installation +of the executable. However, as a special exception, the source code distributed +need not include anything that is normally distributed (in either source or +binary form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component itself +accompanies the executable. + +If distribution of executable or object code is made by offering access to +copy from a designated place, then offering equivalent access to copy the +source code from the same place counts as distribution of the source code, +even though third parties are not compelled to copy the source along with +the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except +as expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense or distribute the Program is void, and will automatically terminate +your rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses terminated +so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Program or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Program +(or any work based on the Program), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), +the recipient automatically receives a license from the original licensor +to copy, distribute or modify the Program subject to these terms and conditions. +You may not impose any further restrictions on the recipients' exercise of +the rights granted herein. You are not responsible for enforcing compliance +by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Program at all. For example, if a +patent license would not permit royalty-free redistribution of the Program +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and +the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system, which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Program under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of +the 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 +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Program does not specify a version number of this License, you may choose +any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software Foundation, +write to the Free Software Foundation; we sometimes make exceptions for this. +Our decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing and reuse +of software generally. + + NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +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. +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 convey 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)< yyyy> + +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 2 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, write to the Free Software Foundation, Inc., 51 Franklin +Street, Fifth Floor, Boston, MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when +it starts in an interactive mode: + +Gnomovision version 69, Copyright (C) year name of author Gnomovision 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, the commands you use may be +called something other than `show w' and `show c'; they could even be mouse-clicks +or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the program, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' +(which makes passes at compilers) written by James Hacker. + +, 1 April 1989 Ty Coon, President of Vice This 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. diff --git a/LICENSES/GPL-2.0-or-later.txt b/LICENSES/GPL-2.0-or-later.txt new file mode 100644 index 0000000..1d80ac3 --- /dev/null +++ b/LICENSES/GPL-2.0-or-later.txt @@ -0,0 +1,319 @@ +GNU GENERAL PUBLIC LICENSE + +Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. + +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public License is intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. This General Public License applies to +most of the Free Software Foundation's software and to any other program whose +authors commit to using it. (Some other Free Software Foundation software +is covered by the GNU Lesser General Public License instead.) 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 this service 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 make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or +for a fee, you must give the recipients all the rights that you have. 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. + +We protect your rights with two steps: (1) copyright the software, and (2) +offer you this license which gives you legal permission to copy, distribute +and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If +the software is modified by someone else and passed on, we want its recipients +to know that what they have is not the original, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that redistributors of a free program will individually +obtain patent licenses, in effect making the program proprietary. To prevent +this, we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms +of this General Public License. The "Program", below, refers to any such program +or work, and a "work based on the Program" means either the Program or any +derivative work under copyright law: that is to say, a work containing the +Program or a portion of it, either verbatim or with modifications and/or translated +into another language. (Hereinafter, translation is included without limitation +in the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running the Program +is not restricted, and the output from the Program is covered only if its +contents constitute a work based on the Program (independent of having been +made by running the Program). Whether that is true depends on what the Program +does. + +1. You may copy and distribute 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 and disclaimer of warranty; +keep intact all the notices that refer to this License and to the absence +of any warranty; and give any other recipients of the Program a copy of this +License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, +thus forming a work based on the Program, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + +a) You must cause the modified files to carry prominent notices stating that +you changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in whole or +in part contains or is derived from the Program or any part thereof, to be +licensed as a whole at no charge to all third parties under the terms of this +License. + +c) If the modified program normally reads commands interactively when run, +you must cause it, when started running for such interactive use in the most +ordinary way, to print or display an announcement including an appropriate +copyright notice and a notice that there is no warranty (or else, saying that +you provide a warranty) and that users may redistribute the program under +these conditions, and telling the user how to view a copy of this License. +(Exception: if the Program itself is interactive but does not normally print +such an announcement, your work based on the Program is not required to print +an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Program, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Program. + +In addition, mere aggregation of another work not based on the Program with +the Program (or with a work based on the Program) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may copy and distribute the Program (or a work based on it, under Section +2) in object code or executable form under the terms of Sections 1 and 2 above +provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable source code, +which must be distributed under the terms of Sections 1 and 2 above on a medium +customarily used for software interchange; or, + +b) Accompany it with a written offer, valid for at least three years, to give +any third party, for a charge no more than your cost of physically performing +source distribution, a complete machine-readable copy of the corresponding +source code, to be distributed under the terms of Sections 1 and 2 above on +a medium customarily used for software interchange; or, + +c) Accompany it with the information you received as to the offer to distribute +corresponding source code. (This alternative is allowed only for noncommercial +distribution and only if you received the program in object code or executable +form with such an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for making +modifications to it. For an executable work, complete source code means all +the source code for all modules it contains, plus any associated interface +definition files, plus the scripts used to control compilation and installation +of the executable. However, as a special exception, the source code distributed +need not include anything that is normally distributed (in either source or +binary form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component itself +accompanies the executable. + +If distribution of executable or object code is made by offering access to +copy from a designated place, then offering equivalent access to copy the +source code from the same place counts as distribution of the source code, +even though third parties are not compelled to copy the source along with +the object code. + +4. You may not copy, modify, sublicense, or distribute the Program except +as expressly provided under this License. Any attempt otherwise to copy, modify, +sublicense or distribute the Program is void, and will automatically terminate +your rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses terminated +so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Program or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Program +(or any work based on the Program), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the Program), +the recipient automatically receives a license from the original licensor +to copy, distribute or modify the Program subject to these terms and conditions. +You may not impose any further restrictions on the recipients' exercise of +the rights granted herein. You are not responsible for enforcing compliance +by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Program at all. For example, if a +patent license would not permit royalty-free redistribution of the Program +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and +the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system, which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Program under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions of +the 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 +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Program does not specify a version number of this License, you may choose +any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software Foundation, +write to the Free Software Foundation; we sometimes make exceptions for this. +Our decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing and reuse +of software generally. + + NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +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. +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 convey 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 2 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, write to the Free Software Foundation, Inc., 51 Franklin +Street, Fifth Floor, Boston, MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this when +it starts in an interactive mode: + +Gnomovision version 69, Copyright (C) year name of author Gnomovision 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, the commands you use may be +called something other than `show w' and `show c'; they could even be mouse-clicks +or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the program, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' +(which makes passes at compilers) written by James Hacker. + +, 1 April 1989 Ty Coon, President of Vice This 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. diff --git a/LICENSES/GPL-3.0-only.txt b/LICENSES/GPL-3.0-only.txt new file mode 100644 index 0000000..e142a52 --- /dev/null +++ b/LICENSES/GPL-3.0-only.txt @@ -0,0 +1,625 @@ +GNU GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright © 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 . diff --git a/LICENSES/GPL-3.0-or-later.txt b/LICENSES/GPL-3.0-or-later.txt new file mode 100644 index 0000000..e142a52 --- /dev/null +++ b/LICENSES/GPL-3.0-or-later.txt @@ -0,0 +1,625 @@ +GNU GENERAL PUBLIC LICENSE + +Version 3, 29 June 2007 + +Copyright © 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 . diff --git a/LICENSES/LGPL-2.0-only.txt b/LICENSES/LGPL-2.0-only.txt new file mode 100644 index 0000000..5c96471 --- /dev/null +++ b/LICENSES/LGPL-2.0-only.txt @@ -0,0 +1,446 @@ +GNU LIBRARY GENERAL PUBLIC LICENSE + +Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc. + +51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is numbered 2 because +it goes with version 2 of the ordinary GPL.] + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public Licenses are intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. + +This license, the Library General Public License, applies to some specially +designated Free Software Foundation software, and to any other libraries whose +authors decide to use it. You can use it for your libraries, 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 this service 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 make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the library, or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for +a fee, you must give the recipients all the rights that we gave you. You must +make sure that they, too, receive or can get the source code. If you link +a program with the library, you must provide complete object files to the +recipients so that they can relink them with the library, after making changes +to the library and recompiling it. And you must show them these terms so they +know their rights. + +Our method of protecting your rights has two steps: (1) copyright the library, +and (2) offer you this license which gives you legal permission to copy, distribute +and/or modify the library. + +Also, for each distributor's protection, we want to make certain that everyone +understands that there is no warranty for this free library. If the library +is modified by someone else and passed on, we want its recipients to know +that what they have is not the original version, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that companies distributing free software will individually +obtain patent licenses, thus in effect transforming the program into proprietary +software. To prevent this, we have made it clear that any patent must be licensed +for everyone's free use or not licensed at all. + +Most GNU software, including some libraries, is covered by the ordinary GNU +General Public License, which was designed for utility programs. This license, +the GNU Library General Public License, applies to certain designated libraries. +This license is quite different from the ordinary one; be sure to read it +in full, and don't assume that anything in it is the same as in the ordinary +license. + +The reason we have a separate public license for some libraries is that they +blur the distinction we usually make between modifying or adding to a program +and simply using it. Linking a program with a library, without changing the +library, is in some sense simply using the library, and is analogous to running +a utility program or application program. However, in a textual and legal +sense, the linked executable is a combined work, a derivative of the original +library, and the ordinary General Public License treats it as such. + +Because of this blurred distinction, using the ordinary General Public License +for libraries did not effectively promote software sharing, because most developers +did not use the libraries. We concluded that weaker conditions might promote +sharing better. + +However, unrestricted linking of non-free programs would deprive the users +of those programs of all benefit from the free status of the libraries themselves. +This Library General Public License is intended to permit developers of non-free +programs to use free libraries, while preserving your freedom as a user of +such programs to change the free libraries that are incorporated in them. +(We have not seen how to achieve this as regards changes in header files, +but we have achieved it as regards changes in the actual functions of the +Library.) The hope is that this will lead to faster development of free libraries. + +The precise terms and conditions for copying, distribution and modification +follow. Pay close attention to the difference between a "work based on the +library" and a "work that uses the library". The former contains code derived +from the library, while the latter only works together with the library. + +Note that it is possible for a library to be covered by the ordinary General +Public License rather than by this special one. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library which contains a +notice placed by the copyright holder or other authorized party saying it +may be distributed under the terms of this Library General Public License +(also called "this License"). Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data prepared +so as to be conveniently linked with application programs (which use some +of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has +been distributed under these terms. A "work based on the Library" means either +the Library or any derivative work under copyright law: that is to say, a +work containing the Library or a portion of it, either verbatim or with modifications +and/or translated straightforwardly into another language. (Hereinafter, translation +is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications +to it. For a library, complete source code means all the source code for all +modules it contains, plus any associated interface definition files, plus +the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running a program +using the Library is not restricted, and output from such a program is covered +only if its contents constitute a work based on the Library (independent of +the use of the Library in a tool for writing it). Whether that is true depends +on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and disclaimer +of warranty; keep intact all the notices that refer to this License and to +the absence of any warranty; and distribute a copy of this License along with +the Library. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, +thus forming a work based on the Library, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + + a) The modified work must itself be a software library. + +b) You must cause the files modified to carry prominent notices stating that +you changed the files and the date of any change. + +c) You must cause the whole of the work to be licensed at no charge to all +third parties under the terms of this License. + +d) If a facility in the modified Library refers to a function or a table of +data to be supplied by an application program that uses the facility, other +than as an argument passed when the facility is invoked, then you must make +a good faith effort to ensure that, in the event an application does not supply +such function or table, the facility still operates, and performs whatever +part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose +that is entirely well-defined independent of the application. Therefore, Subsection +2d requires that any application-supplied function or table used by this function +must be optional: if the application does not supply it, the square root function +must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Library, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Library, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Library. + +In addition, mere aggregation of another work not based on the Library with +the Library (or with a work based on the Library) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may opt to apply the terms of the ordinary GNU General Public License +instead of this License to a given copy of the Library. To do this, you must +alter all the notices that refer to this License, so that they refer to the +ordinary GNU General Public License, version 2, instead of to this License. +(If a newer version than version 2 of the ordinary GNU General Public License +has appeared, then you can specify that version instead if you wish.) Do not +make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, +so the ordinary GNU General Public License applies to all subsequent copies +and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library +into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of +it, under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you accompany it with the complete corresponding +machine-readable source code, which must be distributed under the terms of +Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated +place, then offering equivalent access to copy the source code from the same +place satisfies the requirement to distribute the source code, even though +third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but +is designed to work with the Library by being compiled or linked with it, +is called a "work that uses the Library". Such a work, in isolation, is not +a derivative work of the Library, and therefore falls outside the scope of +this License. + +However, linking a "work that uses the Library" with the Library creates an +executable that is a derivative of the Library (because it contains portions +of the Library), rather than a "work that uses the library". The executable +is therefore covered by this License. Section 6 states terms for distribution +of such executables. + +When a "work that uses the Library" uses material from a header file that +is part of the Library, the object code for the work may be a derivative work +of the Library even though the source code is not. Whether this is true is +especially significant if the work can be linked without the Library, or if +the work is itself a library. The threshold for this to be true is not precisely +defined by law. + +If such an object file uses only numerical parameters, data structure layouts +and accessors, and small macros and small inline functions (ten lines or less +in length), then the use of the object file is unrestricted, regardless of +whether it is legally a derivative work. (Executables containing this object +code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute +the object code for the work under the terms of Section 6. Any executables +containing that work also fall under Section 6, whether or not they are linked +directly with the Library itself. + +6. As an exception to the Sections above, you may also compile or link a "work +that uses the Library" with the Library to produce a work containing portions +of the Library, and distribute that work under terms of your choice, provided +that the terms permit modification of the work for the customer's own use +and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library +is used in it and that the Library and its use are covered by this License. +You must supply a copy of this License. If the work during execution displays +copyright notices, you must include the copyright notice for the Library among +them, as well as a reference directing the user to the copy of this License. +Also, you must do one of these things: + +a) Accompany the work with the complete corresponding machine-readable source +code for the Library including whatever changes were used in the work (which +must be distributed under Sections 1 and 2 above); and, if the work is an +executable linked with the Library, with the complete machine-readable "work +that uses the Library", as object code and/or source code, so that the user +can modify the Library and then relink to produce a modified executable containing +the modified Library. (It is understood that the user who changes the contents +of definitions files in the Library will not necessarily be able to recompile +the application to use the modified definitions.) + +b) Accompany the work with a written offer, valid for at least three years, +to give the same user the materials specified in Subsection 6a, above, for +a charge no more than the cost of performing this distribution. + +c) If distribution of the work is made by offering access to copy from a designated +place, offer equivalent access to copy the above specified materials from +the same place. + +d) Verify that the user has already received a copy of these materials or +that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must +include any data and utility programs needed for reproducing the executable +from it. However, as a special exception, the source code distributed need +not include anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the operating +system on which the executable runs, unless that component itself accompanies +the executable. + +It may happen that this requirement contradicts the license restrictions of +other proprietary libraries that do not normally accompany the operating system. +Such a contradiction means you cannot use both them and the Library together +in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side +in a single library together with other library facilities not covered by +this License, and distribute such a combined library, provided that the separate +distribution of the work based on the Library and of the other library facilities +is otherwise permitted, and provided that you do these two things: + +a) Accompany the combined library with a copy of the same work based on the +Library, uncombined with any other library facilities. This must be distributed +under the terms of the Sections above. + +b) Give prominent notice with the combined library of the fact that part of +it is a work based on the Library, and explaining where to find the accompanying +uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library +except as expressly provided under this License. Any attempt otherwise to +copy, modify, sublicense, link with, or distribute the Library is void, and +will automatically terminate your rights under this License. However, parties +who have received copies, or rights, from you under this License will not +have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Library or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Library +(or any work based on the Library), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), +the recipient automatically receives a license from the original licensor +to copy, distribute, link with or modify the Library subject to these terms +and conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties to this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Library at all. For example, if a +patent license would not permit royalty-free redistribution of the Library +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Library under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of +the Library 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 Library specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Library does not specify a license version number, you may choose any version +ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs +whose distribution conditions are incompatible with these, write to the author +to ask for permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make exceptions +for this. Our decision will be guided by the two goals of preserving the free +status of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY +"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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +THE LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH +HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible +use to the public, we recommend making it free software that everyone can +redistribute and change. You can do so by permitting redistribution under +these terms (or, alternatively, under the terms of the ordinary General Public +License). + +To apply these terms, attach the following notices to the library. It is safest +to attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the "copyright" +line and a pointer to where the full notice is found. + +one line to give the library's name and an idea of what it does. + +Copyright (C) year name of author + +This library is free software; you can redistribute it and/or modify it under +the terms of the GNU Library General Public License as published by the Free +Software Foundation; either version 2 of the License, or (at your option) +any later version. + +This library 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 Library General Public License for more +details. + +You should have received a copy of the GNU Library General Public License +along with this library; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the library, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in + +the library `Frob' (a library for tweaking knobs) written + +by James Random Hacker. + +signature of Ty Coon, 1 April 1990 + +Ty Coon, President of Vice + +That's all there is to it! diff --git a/LICENSES/LGPL-2.0-or-later.txt b/LICENSES/LGPL-2.0-or-later.txt new file mode 100644 index 0000000..5c96471 --- /dev/null +++ b/LICENSES/LGPL-2.0-or-later.txt @@ -0,0 +1,446 @@ +GNU LIBRARY GENERAL PUBLIC LICENSE + +Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc. + +51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is numbered 2 because +it goes with version 2 of the ordinary GPL.] + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public Licenses are intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. + +This license, the Library General Public License, applies to some specially +designated Free Software Foundation software, and to any other libraries whose +authors decide to use it. You can use it for your libraries, 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 this service 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 make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the library, or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for +a fee, you must give the recipients all the rights that we gave you. You must +make sure that they, too, receive or can get the source code. If you link +a program with the library, you must provide complete object files to the +recipients so that they can relink them with the library, after making changes +to the library and recompiling it. And you must show them these terms so they +know their rights. + +Our method of protecting your rights has two steps: (1) copyright the library, +and (2) offer you this license which gives you legal permission to copy, distribute +and/or modify the library. + +Also, for each distributor's protection, we want to make certain that everyone +understands that there is no warranty for this free library. If the library +is modified by someone else and passed on, we want its recipients to know +that what they have is not the original version, so that any problems introduced +by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that companies distributing free software will individually +obtain patent licenses, thus in effect transforming the program into proprietary +software. To prevent this, we have made it clear that any patent must be licensed +for everyone's free use or not licensed at all. + +Most GNU software, including some libraries, is covered by the ordinary GNU +General Public License, which was designed for utility programs. This license, +the GNU Library General Public License, applies to certain designated libraries. +This license is quite different from the ordinary one; be sure to read it +in full, and don't assume that anything in it is the same as in the ordinary +license. + +The reason we have a separate public license for some libraries is that they +blur the distinction we usually make between modifying or adding to a program +and simply using it. Linking a program with a library, without changing the +library, is in some sense simply using the library, and is analogous to running +a utility program or application program. However, in a textual and legal +sense, the linked executable is a combined work, a derivative of the original +library, and the ordinary General Public License treats it as such. + +Because of this blurred distinction, using the ordinary General Public License +for libraries did not effectively promote software sharing, because most developers +did not use the libraries. We concluded that weaker conditions might promote +sharing better. + +However, unrestricted linking of non-free programs would deprive the users +of those programs of all benefit from the free status of the libraries themselves. +This Library General Public License is intended to permit developers of non-free +programs to use free libraries, while preserving your freedom as a user of +such programs to change the free libraries that are incorporated in them. +(We have not seen how to achieve this as regards changes in header files, +but we have achieved it as regards changes in the actual functions of the +Library.) The hope is that this will lead to faster development of free libraries. + +The precise terms and conditions for copying, distribution and modification +follow. Pay close attention to the difference between a "work based on the +library" and a "work that uses the library". The former contains code derived +from the library, while the latter only works together with the library. + +Note that it is possible for a library to be covered by the ordinary General +Public License rather than by this special one. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library which contains a +notice placed by the copyright holder or other authorized party saying it +may be distributed under the terms of this Library General Public License +(also called "this License"). Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data prepared +so as to be conveniently linked with application programs (which use some +of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has +been distributed under these terms. A "work based on the Library" means either +the Library or any derivative work under copyright law: that is to say, a +work containing the Library or a portion of it, either verbatim or with modifications +and/or translated straightforwardly into another language. (Hereinafter, translation +is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications +to it. For a library, complete source code means all the source code for all +modules it contains, plus any associated interface definition files, plus +the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running a program +using the Library is not restricted, and output from such a program is covered +only if its contents constitute a work based on the Library (independent of +the use of the Library in a tool for writing it). Whether that is true depends +on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and disclaimer +of warranty; keep intact all the notices that refer to this License and to +the absence of any warranty; and distribute a copy of this License along with +the Library. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, +thus forming a work based on the Library, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + + a) The modified work must itself be a software library. + +b) You must cause the files modified to carry prominent notices stating that +you changed the files and the date of any change. + +c) You must cause the whole of the work to be licensed at no charge to all +third parties under the terms of this License. + +d) If a facility in the modified Library refers to a function or a table of +data to be supplied by an application program that uses the facility, other +than as an argument passed when the facility is invoked, then you must make +a good faith effort to ensure that, in the event an application does not supply +such function or table, the facility still operates, and performs whatever +part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose +that is entirely well-defined independent of the application. Therefore, Subsection +2d requires that any application-supplied function or table used by this function +must be optional: if the application does not supply it, the square root function +must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Library, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Library, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Library. + +In addition, mere aggregation of another work not based on the Library with +the Library (or with a work based on the Library) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may opt to apply the terms of the ordinary GNU General Public License +instead of this License to a given copy of the Library. To do this, you must +alter all the notices that refer to this License, so that they refer to the +ordinary GNU General Public License, version 2, instead of to this License. +(If a newer version than version 2 of the ordinary GNU General Public License +has appeared, then you can specify that version instead if you wish.) Do not +make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, +so the ordinary GNU General Public License applies to all subsequent copies +and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library +into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of +it, under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you accompany it with the complete corresponding +machine-readable source code, which must be distributed under the terms of +Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated +place, then offering equivalent access to copy the source code from the same +place satisfies the requirement to distribute the source code, even though +third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but +is designed to work with the Library by being compiled or linked with it, +is called a "work that uses the Library". Such a work, in isolation, is not +a derivative work of the Library, and therefore falls outside the scope of +this License. + +However, linking a "work that uses the Library" with the Library creates an +executable that is a derivative of the Library (because it contains portions +of the Library), rather than a "work that uses the library". The executable +is therefore covered by this License. Section 6 states terms for distribution +of such executables. + +When a "work that uses the Library" uses material from a header file that +is part of the Library, the object code for the work may be a derivative work +of the Library even though the source code is not. Whether this is true is +especially significant if the work can be linked without the Library, or if +the work is itself a library. The threshold for this to be true is not precisely +defined by law. + +If such an object file uses only numerical parameters, data structure layouts +and accessors, and small macros and small inline functions (ten lines or less +in length), then the use of the object file is unrestricted, regardless of +whether it is legally a derivative work. (Executables containing this object +code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute +the object code for the work under the terms of Section 6. Any executables +containing that work also fall under Section 6, whether or not they are linked +directly with the Library itself. + +6. As an exception to the Sections above, you may also compile or link a "work +that uses the Library" with the Library to produce a work containing portions +of the Library, and distribute that work under terms of your choice, provided +that the terms permit modification of the work for the customer's own use +and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library +is used in it and that the Library and its use are covered by this License. +You must supply a copy of this License. If the work during execution displays +copyright notices, you must include the copyright notice for the Library among +them, as well as a reference directing the user to the copy of this License. +Also, you must do one of these things: + +a) Accompany the work with the complete corresponding machine-readable source +code for the Library including whatever changes were used in the work (which +must be distributed under Sections 1 and 2 above); and, if the work is an +executable linked with the Library, with the complete machine-readable "work +that uses the Library", as object code and/or source code, so that the user +can modify the Library and then relink to produce a modified executable containing +the modified Library. (It is understood that the user who changes the contents +of definitions files in the Library will not necessarily be able to recompile +the application to use the modified definitions.) + +b) Accompany the work with a written offer, valid for at least three years, +to give the same user the materials specified in Subsection 6a, above, for +a charge no more than the cost of performing this distribution. + +c) If distribution of the work is made by offering access to copy from a designated +place, offer equivalent access to copy the above specified materials from +the same place. + +d) Verify that the user has already received a copy of these materials or +that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must +include any data and utility programs needed for reproducing the executable +from it. However, as a special exception, the source code distributed need +not include anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the operating +system on which the executable runs, unless that component itself accompanies +the executable. + +It may happen that this requirement contradicts the license restrictions of +other proprietary libraries that do not normally accompany the operating system. +Such a contradiction means you cannot use both them and the Library together +in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side +in a single library together with other library facilities not covered by +this License, and distribute such a combined library, provided that the separate +distribution of the work based on the Library and of the other library facilities +is otherwise permitted, and provided that you do these two things: + +a) Accompany the combined library with a copy of the same work based on the +Library, uncombined with any other library facilities. This must be distributed +under the terms of the Sections above. + +b) Give prominent notice with the combined library of the fact that part of +it is a work based on the Library, and explaining where to find the accompanying +uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library +except as expressly provided under this License. Any attempt otherwise to +copy, modify, sublicense, link with, or distribute the Library is void, and +will automatically terminate your rights under this License. However, parties +who have received copies, or rights, from you under this License will not +have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Library or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Library +(or any work based on the Library), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), +the recipient automatically receives a license from the original licensor +to copy, distribute, link with or modify the Library subject to these terms +and conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties to this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Library at all. For example, if a +patent license would not permit royalty-free redistribution of the Library +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Library under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of +the Library 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 Library specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Library does not specify a license version number, you may choose any version +ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs +whose distribution conditions are incompatible with these, write to the author +to ask for permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make exceptions +for this. Our decision will be guided by the two goals of preserving the free +status of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY +"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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +THE LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH +HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible +use to the public, we recommend making it free software that everyone can +redistribute and change. You can do so by permitting redistribution under +these terms (or, alternatively, under the terms of the ordinary General Public +License). + +To apply these terms, attach the following notices to the library. It is safest +to attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the "copyright" +line and a pointer to where the full notice is found. + +one line to give the library's name and an idea of what it does. + +Copyright (C) year name of author + +This library is free software; you can redistribute it and/or modify it under +the terms of the GNU Library General Public License as published by the Free +Software Foundation; either version 2 of the License, or (at your option) +any later version. + +This library 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 Library General Public License for more +details. + +You should have received a copy of the GNU Library General Public License +along with this library; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the library, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in + +the library `Frob' (a library for tweaking knobs) written + +by James Random Hacker. + +signature of Ty Coon, 1 April 1990 + +Ty Coon, President of Vice + +That's all there is to it! diff --git a/LICENSES/LGPL-2.1-only.txt b/LICENSES/LGPL-2.1-only.txt new file mode 100644 index 0000000..130dffb --- /dev/null +++ b/LICENSES/LGPL-2.1-only.txt @@ -0,0 +1,467 @@ +GNU LESSER GENERAL PUBLIC LICENSE + +Version 2.1, February 1999 + +Copyright (C) 1991, 1999 Free Software Foundation, Inc. + +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts as the +successor of the GNU Library Public License, version 2, hence the version +number 2.1.] + +Preamble + +The licenses for most software are designed to take away your freedom to share +and change it. By contrast, the GNU General Public Licenses are intended to +guarantee your freedom to share and change free software--to make sure the +software is free for all its users. + +This license, the Lesser General Public License, applies to some specially +designated software packages--typically libraries--of the Free Software Foundation +and other authors who decide to use it. You can use it too, but we suggest +you first think carefully about whether this license or the ordinary General +Public License is the better strategy to use in any particular case, based +on the explanations below. + +When we speak of free software, we are referring to freedom of use, 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 this service if you +wish); that you receive source code or can get it if you want it; that you +can change the software and use pieces of it in new free programs; and that +you are informed that you can do these things. + +To protect your rights, we need to make restrictions that forbid distributors +to deny you these rights or to ask you to surrender these rights. These restrictions +translate to certain responsibilities for you if you distribute copies of +the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for +a fee, you must give the recipients all the rights that we gave you. You must +make sure that they, too, receive or can get the source code. If you link +other code with the library, you must provide complete object files to the +recipients, so that they can relink them with the library after making changes +to the library and recompiling it. And you must show them these terms so they +know their rights. + +We protect your rights with a two-step method: (1) we copyright the library, +and (2) we offer you this license, which gives you legal permission to copy, +distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no +warranty for the free library. Also, if the library is modified by someone +else and passed on, the recipients should know that what they have is not +the original version, so that the original author's reputation will not be +affected by problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any free +program. We wish to make sure that a company cannot effectively restrict the +users of a free program by obtaining a restrictive license from a patent holder. +Therefore, we insist that any patent license obtained for a version of the +library must be consistent with the full freedom of use specified in this +license. + +Most GNU software, including some libraries, is covered by the ordinary GNU +General Public License. This license, the GNU Lesser General Public License, +applies to certain designated libraries, and is quite different from the ordinary +General Public License. We use this license for certain libraries in order +to permit linking those libraries into non-free programs. + +When a program is linked with a library, whether statically or using a shared +library, the combination of the two is legally speaking a combined work, a +derivative of the original library. The ordinary General Public License therefore +permits such linking only if the entire combination fits its criteria of freedom. +The Lesser General Public License permits more lax criteria for linking other +code with the library. + +We call this license the "Lesser" General Public License because it does Less +to protect the user's freedom than the ordinary General Public License. It +also provides other free software developers Less of an advantage over competing +non-free programs. These disadvantages are the reason we use the ordinary +General Public License for many libraries. However, the Lesser license provides +advantages in certain special circumstances. + +For example, on rare occasions, there may be a special need to encourage the +widest possible use of a certain library, so that it becomes a de-facto standard. +To achieve this, non-free programs must be allowed to use the library. A more +frequent case is that a free library does the same job as widely used non-free +libraries. In this case, there is little to gain by limiting the free library +to free software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free programs +enables a greater number of people to use a large body of free software. For +example, permission to use the GNU C Library in non-free programs enables +many more people to use the whole GNU operating system, as well as its variant, +the GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users' +freedom, it does ensure that the user of a program that is linked with the +Library has the freedom and the wherewithal to run that program using a modified +version of the Library. + +The precise terms and conditions for copying, distribution and modification +follow. Pay close attention to the difference between a "work based on the +library" and a "work that uses the library". The former contains code derived +from the library, whereas the latter must be combined with the library in +order to run. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library or other program +which contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Lesser General +Public License (also called "this License"). Each licensee is addressed as +"you". + +A "library" means a collection of software functions and/or data prepared +so as to be conveniently linked with application programs (which use some +of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has +been distributed under these terms. A "work based on the Library" means either +the Library or any derivative work under copyright law: that is to say, a +work containing the Library or a portion of it, either verbatim or with modifications +and/or translated straightforwardly into another language. (Hereinafter, translation +is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications +to it. For a library, complete source code means all the source code for all +modules it contains, plus any associated interface definition files, plus +the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running a program +using the Library is not restricted, and output from such a program is covered +only if its contents constitute a work based on the Library (independent of +the use of the Library in a tool for writing it). Whether that is true depends +on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and disclaimer +of warranty; keep intact all the notices that refer to this License and to +the absence of any warranty; and distribute a copy of this License along with +the Library. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, +thus forming a work based on the Library, and copy and distribute such modifications +or work under the terms of Section 1 above, provided that you also meet all +of these conditions: + + a) The modified work must itself be a software library. + +b) You must cause the files modified to carry prominent notices stating that +you changed the files and the date of any change. + +c) You must cause the whole of the work to be licensed at no charge to all +third parties under the terms of this License. + +d) If a facility in the modified Library refers to a function or a table of +data to be supplied by an application program that uses the facility, other +than as an argument passed when the facility is invoked, then you must make +a good faith effort to ensure that, in the event an application does not supply +such function or table, the facility still operates, and performs whatever +part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose +that is entirely well-defined independent of the application. Therefore, Subsection +2d requires that any application-supplied function or table used by this function +must be optional: if the application does not supply it, the square root function +must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Library, and can be reasonably +considered independent and separate works in themselves, then this License, +and its terms, do not apply to those sections when you distribute them as +separate works. But when you distribute the same sections as part of a whole +which is a work based on the Library, the distribution of the whole must be +on the terms of this License, whose permissions for other licensees extend +to the entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest your +rights to work written entirely by you; rather, the intent is to exercise +the right to control the distribution of derivative or collective works based +on the Library. + +In addition, mere aggregation of another work not based on the Library with +the Library (or with a work based on the Library) on a volume of a storage +or distribution medium does not bring the other work under the scope of this +License. + +3. You may opt to apply the terms of the ordinary GNU General Public License +instead of this License to a given copy of the Library. To do this, you must +alter all the notices that refer to this License, so that they refer to the +ordinary GNU General Public License, version 2, instead of to this License. +(If a newer version than version 2 of the ordinary GNU General Public License +has appeared, then you can specify that version instead if you wish.) Do not +make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, +so the ordinary GNU General Public License applies to all subsequent copies +and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library +into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of +it, under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you accompany it with the complete corresponding +machine-readable source code, which must be distributed under the terms of +Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated +place, then offering equivalent access to copy the source code from the same +place satisfies the requirement to distribute the source code, even though +third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but +is designed to work with the Library by being compiled or linked with it, +is called a "work that uses the Library". Such a work, in isolation, is not +a derivative work of the Library, and therefore falls outside the scope of +this License. + +However, linking a "work that uses the Library" with the Library creates an +executable that is a derivative of the Library (because it contains portions +of the Library), rather than a "work that uses the library". The executable +is therefore covered by this License. Section 6 states terms for distribution +of such executables. + +When a "work that uses the Library" uses material from a header file that +is part of the Library, the object code for the work may be a derivative work +of the Library even though the source code is not. Whether this is true is +especially significant if the work can be linked without the Library, or if +the work is itself a library. The threshold for this to be true is not precisely +defined by law. + +If such an object file uses only numerical parameters, data structure layouts +and accessors, and small macros and small inline functions (ten lines or less +in length), then the use of the object file is unrestricted, regardless of +whether it is legally a derivative work. (Executables containing this object +code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute +the object code for the work under the terms of Section 6. Any executables +containing that work also fall under Section 6, whether or not they are linked +directly with the Library itself. + +6. As an exception to the Sections above, you may also combine or link a "work +that uses the Library" with the Library to produce a work containing portions +of the Library, and distribute that work under terms of your choice, provided +that the terms permit modification of the work for the customer's own use +and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library +is used in it and that the Library and its use are covered by this License. +You must supply a copy of this License. If the work during execution displays +copyright notices, you must include the copyright notice for the Library among +them, as well as a reference directing the user to the copy of this License. +Also, you must do one of these things: + +a) Accompany the work with the complete corresponding machine-readable source +code for the Library including whatever changes were used in the work (which +must be distributed under Sections 1 and 2 above); and, if the work is an +executable linked with the Library, with the complete machine-readable "work +that uses the Library", as object code and/or source code, so that the user +can modify the Library and then relink to produce a modified executable containing +the modified Library. (It is understood that the user who changes the contents +of definitions files in the Library will not necessarily be able to recompile +the application to use the modified definitions.) + +b) Use a suitable shared library mechanism for linking with the Library. A +suitable mechanism is one that (1) uses at run time a copy of the library +already present on the user's computer system, rather than copying library +functions into the executable, and (2) will operate properly with a modified +version of the library, if the user installs one, as long as the modified +version is interface-compatible with the version that the work was made with. + +c) Accompany the work with a written offer, valid for at least three years, +to give the same user the materials specified in Subsection 6a, above, for +a charge no more than the cost of performing this distribution. + +d) If distribution of the work is made by offering access to copy from a designated +place, offer equivalent access to copy the above specified materials from +the same place. + +e) Verify that the user has already received a copy of these materials or +that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must +include any data and utility programs needed for reproducing the executable +from it. However, as a special exception, the materials to be distributed +need not include anything that is normally distributed (in either source or +binary form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component itself +accompanies the executable. + +It may happen that this requirement contradicts the license restrictions of +other proprietary libraries that do not normally accompany the operating system. +Such a contradiction means you cannot use both them and the Library together +in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side +in a single library together with other library facilities not covered by +this License, and distribute such a combined library, provided that the separate +distribution of the work based on the Library and of the other library facilities +is otherwise permitted, and provided that you do these two things: + +a) Accompany the combined library with a copy of the same work based on the +Library, uncombined with any other library facilities. This must be distributed +under the terms of the Sections above. + +b) Give prominent notice with the combined library of the fact that part of +it is a work based on the Library, and explaining where to find the accompanying +uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library +except as expressly provided under this License. Any attempt otherwise to +copy, modify, sublicense, link with, or distribute the Library is void, and +will automatically terminate your rights under this License. However, parties +who have received copies, or rights, from you under this License will not +have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed +it. However, nothing else grants you permission to modify or distribute the +Library or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the Library +(or any work based on the Library), you indicate your acceptance of this License +to do so, and all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), +the recipient automatically receives a license from the original licensor +to copy, distribute, link with or modify the Library subject to these terms +and conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties with this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement +or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your +obligations under this License and any other pertinent obligations, then as +a consequence you may not distribute the Library at all. For example, if a +patent license would not permit royalty-free redistribution of the Library +by all those who receive copies directly or indirectly through you, then the +only way you could satisfy both it and this License would be to refrain entirely +from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents +or other property right claims or to contest validity of any such claims; +this section has the sole purpose of protecting the integrity of the free +software distribution system which is implemented by public license practices. +Many people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Library under this License may add an explicit geographical +distribution limitation excluding those countries, so that distribution is +permitted only in or among countries not thus excluded. In such case, this +License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of +the Lesser 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 Library specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that version +or of any later version published by the Free Software Foundation. If the +Library does not specify a license version number, you may choose any version +ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs +whose distribution conditions are incompatible with these, write to the author +to ask for permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make exceptions +for this. Our decision will be guided by the two goals of preserving the free +status of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR +THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY +"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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE +THE LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH +HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible +use to the public, we recommend making it free software that everyone can +redistribute and change. You can do so by permitting redistribution under +these terms (or, alternatively, under the terms of the ordinary General Public +License). + +To apply these terms, attach the following notices to the library. It is safest +to attach them to the start of each source file to most effectively convey +the exclusion of warranty; and each file should have at least the "copyright" +line and a pointer to where the full notice is found. + +< one line to give the library's name and an idea of what it does. > + +Copyright (C) < year > < name of author > + +This library is free software; you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation; either version 2.1 of the License, or (at your option) +any later version. + +This library 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 Lesser General Public License for more +details. + +You should have received a copy of the GNU Lesser General Public License along +with this library; if not, write to the Free Software Foundation, Inc., 51 +Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information +on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, +if any, to sign a "copyright disclaimer" for the library, if necessary. Here +is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in + +the library `Frob' (a library for tweaking knobs) written + +by James Random Hacker. + +< signature of Ty Coon > , 1 April 1990 + +Ty Coon, President of Vice + +That's all there is to it! diff --git a/LICENSES/LGPL-2.1-or-later.txt b/LICENSES/LGPL-2.1-or-later.txt new file mode 100644 index 0000000..c9aa530 --- /dev/null +++ b/LICENSES/LGPL-2.1-or-later.txt @@ -0,0 +1,175 @@ +GNU LESSER GENERAL PUBLIC LICENSE + +Version 2.1, February 1999 + +Copyright (C) 1991, 1999 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] + +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. + +This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. + +When we speak of free software, we are referring to freedom of use, 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 this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. + +To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. + +We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. + +Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. + +When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. + +We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. + +For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. + +The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. + +However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. + +When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. + +If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. + +6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: + + a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. + + e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. + + b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), 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 distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of the Lesser 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 Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). + +To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + + one line to give the library's name and an idea of what it does. + Copyright (C) year name of author + + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. + + This library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in +the library `Frob' (a library for tweaking knobs) written +by James Random Hacker. + +signature of Ty Coon, 1 April 1990 +Ty Coon, President of Vice +That's all there is to it! diff --git a/LICENSES/LGPL-3.0-only.txt b/LICENSES/LGPL-3.0-only.txt new file mode 100644 index 0000000..bd405af --- /dev/null +++ b/LICENSES/LGPL-3.0-only.txt @@ -0,0 +1,163 @@ +GNU LESSER 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. + +This version of the GNU Lesser General Public License incorporates the terms +and conditions of version 3 of the GNU General Public License, supplemented +by the additional permissions listed below. + + 0. Additional Definitions. + + + +As used herein, "this License" refers to version 3 of the GNU Lesser General +Public License, and the "GNU GPL" refers to version 3 of the GNU General Public +License. + + + +"The Library" refers to a covered work governed by this License, other than +an Application or a Combined Work as defined below. + + + +An "Application" is any work that makes use of an interface provided by the +Library, but which is not otherwise based on the Library. Defining a subclass +of a class defined by the Library is deemed a mode of using an interface provided +by the Library. + + + +A "Combined Work" is a work produced by combining or linking an Application +with the Library. The particular version of the Library with which the Combined +Work was made is also called the "Linked Version". + + + +The "Minimal Corresponding Source" for a Combined Work means the Corresponding +Source for the Combined Work, excluding any source code for portions of the +Combined Work that, considered in isolation, are based on the Application, +and not on the Linked Version. + + + +The "Corresponding Application Code" for a Combined Work means the object +code and/or source code for the Application, including any data and utility +programs needed for reproducing the Combined Work from the Application, but +excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + +You may convey a covered work under sections 3 and 4 of this License without +being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + +If you modify a copy of the Library, and, in your modifications, a facility +refers to a function or data to be supplied by an Application that uses the +facility (other than as an argument passed when the facility is invoked), +then you may convey a copy of the modified version: + +a) under this License, provided that you make a good faith effort to ensure +that, in the event an Application does not supply the function or data, the +facility still operates, and performs whatever part of its purpose remains +meaningful, or + +b) under the GNU GPL, with none of the additional permissions of this License +applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + +The object code form of an Application may incorporate material from a header +file that is part of the Library. You may convey such object code under terms +of your choice, provided that, if the incorporated material is not limited +to numerical parameters, data structure layouts and accessors, or small macros, +inline functions and templates (ten or fewer lines in length), you do both +of the following: + +a) Give prominent notice with each copy of the object code that the Library +is used in it and that the Library and its use are covered by this License. + +b) Accompany the object code with a copy of the GNU GPL and this license document. + + 4. Combined Works. + +You may convey a Combined Work under terms of your choice that, taken together, +effectively do not restrict modification of the portions of the Library contained +in the Combined Work and reverse engineering for debugging such modifications, +if you also do each of the following: + +a) Give prominent notice with each copy of the Combined Work that the Library +is used in it and that the Library and its use are covered by this License. + +b) Accompany the Combined Work with a copy of the GNU GPL and this license +document. + +c) For a Combined Work that displays copyright notices during execution, include +the copyright notice for the Library among these notices, as well as a reference +directing the user to the copies of the GNU GPL and this license document. + + d) Do one of the following: + +0) Convey the Minimal Corresponding Source under the terms of this License, +and the Corresponding Application Code in a form suitable for, and under terms +that permit, the user to recombine or relink the Application with a modified +version of the Linked Version to produce a modified Combined Work, in the +manner specified by section 6 of the GNU GPL for conveying Corresponding Source. + +1) Use a suitable shared library mechanism for linking with the Library. A +suitable mechanism is one that (a) uses at run time a copy of the Library +already present on the user's computer system, and (b) will operate properly +with a modified version of the Library that is interface-compatible with the +Linked Version. + +e) Provide Installation Information, but only if you would otherwise be required +to provide such information under section 6 of the GNU GPL, and only to the +extent that such information is necessary to install and execute a modified +version of the Combined Work produced by recombining or relinking the Application +with a modified version of the Linked Version. (If you use option 4d0, the +Installation Information must accompany the Minimal Corresponding Source and +Corresponding Application Code. If you use option 4d1, you must provide the +Installation Information in the manner specified by section 6 of the GNU GPL +for conveying Corresponding Source.) + + 5. Combined Libraries. + +You may place library facilities that are a work based on the Library side +by side in a single library together with other library facilities that are +not Applications and are not covered by this License, and convey such a combined +library under terms of your choice, if you do both of the following: + +a) Accompany the combined library with a copy of the same work based on the +Library, uncombined with any other library facilities, conveyed under the +terms of this License. + +b) Give prominent notice with the combined library that part of it is a work +based on the Library, and explaining where to find the accompanying uncombined +form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + +The Free Software Foundation may publish revised and/or new versions of the +GNU Lesser 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 Library as you +received it specifies that a certain numbered version of the GNU Lesser General +Public License "or any later version" applies to it, you have the option of +following the terms and conditions either of that published version or of +any later version published by the Free Software Foundation. If the Library +as you received it does not specify a version number of the GNU Lesser General +Public License, you may choose any version of the GNU Lesser General Public +License ever published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide whether +future versions of the GNU Lesser General Public License shall apply, that +proxy's public statement of acceptance of any version is permanent authorization +for you to choose that version for the Library. diff --git a/LICENSES/LicenseRef-KDE-Accepted-GPL.txt b/LICENSES/LicenseRef-KDE-Accepted-GPL.txt new file mode 100644 index 0000000..60a2dff --- /dev/null +++ b/LICENSES/LicenseRef-KDE-Accepted-GPL.txt @@ -0,0 +1,12 @@ +This library 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) at any later version that is +accepted by the membership of KDE e.V. (or its successor +approved by the membership of KDE e.V.), which shall act as a +proxy as defined in Section 14 of version 3 of the license. + +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. diff --git a/LICENSES/LicenseRef-KDE-Accepted-LGPL.txt b/LICENSES/LicenseRef-KDE-Accepted-LGPL.txt new file mode 100644 index 0000000..232b3c5 --- /dev/null +++ b/LICENSES/LicenseRef-KDE-Accepted-LGPL.txt @@ -0,0 +1,12 @@ +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 3 of the license or (at your option) any later version +that is accepted by the membership of KDE e.V. (or its successor +approved by the membership of KDE e.V.), which shall act as a +proxy as defined in Section 6 of version 3 of the license. + +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. diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt new file mode 100644 index 0000000..204b93d --- /dev/null +++ b/LICENSES/MIT.txt @@ -0,0 +1,19 @@ +MIT License Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Mainpage.dox b/Mainpage.dox new file mode 100644 index 0000000..c0d4e69 --- /dev/null +++ b/Mainpage.dox @@ -0,0 +1,19 @@ +/** @mainpage KWin + +KWin is the KDE window manager. + +@authors +Matthias Ettrich \
+Lubos Lunak \ + +@maintainers +Lubos Lunak \ + +@licenses +@gpl + + +*/ + +// DOXYGEN_SET_PROJECT_NAME = KWin +// vim:ts=4:sw=4:expandtab:filetype=doxygen diff --git a/README.md b/README.md new file mode 100644 index 0000000..d11fc5f --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +# KWin + +KWin is an easy to use, but flexible, composited Window Manager for Xorg windowing systems (Wayland, X11) on Linux. Its primary usage is in conjunction with a Desktop Shell (e.g. KDE Plasma Desktop). KWin is designed to go out of the way; users should not notice that they use a window manager at all. Nevertheless KWin provides a steep learning curve for advanced features, which are available, if they do not conflict with the primary mission. KWin does not have a dedicated targeted user group, but follows the targeted user group of the Desktop Shell using KWin as it's window manager. + +## KWin is not... + + * a standalone window manager (c.f. openbox, i3) and does not provide any functionality belonging to a Desktop Shell. + * a replacement for window managers designed for use with a specific Desktop Shell (e.g. GNOME Shell) + * a minimalistic window manager + * designed for use without compositing or for X11 network transparency, though both are possible. + +# Contributing to KWin + +Please refer to the [contributing document](CONTRIBUTING.md) for everything you need to know to get started contributing to KWin. + +# Contacting KWin development team + + * mailing list: [kwin@kde.org](https://mail.kde.org/mailman/listinfo/kwin) + * IRC: #kde-kwin on irc.libera.chat + +# Support +## Application Developer +If you are an application developer having questions regarding windowing systems (either X11 or Wayland) please do not hesitate to contact us. Preferable through our mailing list. Ideally subscribe to the mailing list, so that your mail doesn't get stuck in the moderation queue. + +## End user +Please contact the support channels of your Linux distribution for user support. The KWin development team does not provide end user support. + +# Reporting bugs + +Please use [KDE's bugtracker](https://bugs.kde.org) and report for [product KWin](https://bugs.kde.org/enter_bug.cgi?product=kwin). + +## Guidelines for new features + +A new Feature can only be added to KWin if: + + * it does not violate the primary missions as stated at the start of this document + * it does not introduce instabilities + * it is maintained, that is bugs are fixed in a timely manner (second next minor release) if it is not a corner case. + * it works together with all existing features + * it supports both single and multi screen (xrandr) + * it adds a significant advantage + * it is feature complete, that is supports at least all useful features from competitive implementations + * it is not a special case for a small user group + * it does not increase code complexity significantly + * it does not affect KWin's license (GPLv2+) + +All new added features are under probation, that is if any of the non-functional requirements as listed above do not hold true in the next two feature releases, the added feature will be removed again. + +The same non functional requirements hold true for any kind of plugins (effects, scripts, etc.). It is suggested to use scripted plugins and distribute them separately. diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt new file mode 100644 index 0000000..b5d01dd --- /dev/null +++ b/autotests/CMakeLists.txt @@ -0,0 +1,251 @@ +add_definitions(-DKWIN_UNIT_TEST) +remove_definitions(-DQT_USE_QSTRINGBUILDER) +add_subdirectory(libkwineffects) +add_subdirectory(integration) +add_subdirectory(libinput) +add_subdirectory(tabbox) + +######################################################## +# Test WindowPaintData +######################################################## +set(testWindowPaintData_SRCS test_window_paint_data.cpp) +add_executable(testWindowPaintData ${testWindowPaintData_SRCS}) +target_link_libraries(testWindowPaintData kwineffects Qt::Widgets Qt::Test ) +add_test(NAME kwin-testWindowPaintData COMMAND testWindowPaintData) +ecm_mark_as_test(testWindowPaintData) + +######################################################## +# Test VirtualDesktopManager +######################################################## +set(testVirtualDesktops_SRCS + ../src/virtualdesktops.cpp + test_virtual_desktops.cpp +) +add_executable(testVirtualDesktops ${testVirtualDesktops_SRCS}) + +target_link_libraries(testVirtualDesktops + kwin + + Qt::Test + Qt::Widgets + + KF5::ConfigCore + KF5::GlobalAccel + KF5::I18n + KF5::WindowSystem +) +add_test(NAME kwin-testVirtualDesktops COMMAND testVirtualDesktops) +ecm_mark_as_test(testVirtualDesktops) + +######################################################## +# Test ClientMachine +######################################################## +set(testClientMachine_SRCS + ../src/client_machine.cpp + test_client_machine.cpp + xcb_scaling_mock.cpp +) +add_executable(testClientMachine ${testClientMachine_SRCS}) +set_target_properties(testClientMachine PROPERTIES COMPILE_DEFINITIONS "NO_NONE_WINDOW") + +target_link_libraries(testClientMachine + Qt::Concurrent + Qt::Test + Qt::Widgets + + KF5::ConfigCore + KF5::WindowSystem + + XCB::XCB + XCB::XFIXES + + ${X11_X11_LIB} # to make jenkins happy +) +if (QT_MAJOR_VERSION EQUAL "5") + target_link_libraries(testClientMachine Qt::X11Extras) +else() + target_link_libraries(testClientMachine Qt::GuiPrivate) +endif() +add_test(NAME kwin-testClientMachine COMMAND testClientMachine) +ecm_mark_as_test(testClientMachine) + +######################################################## +# Test XcbWrapper +######################################################## +add_executable(testXcbWrapper test_xcb_wrapper.cpp xcb_scaling_mock.cpp) + +target_link_libraries(testXcbWrapper + Qt::Test + Qt::Widgets + + KF5::ConfigCore + KF5::WindowSystem + + XCB::XCB +) +if (QT_MAJOR_VERSION EQUAL "5") + target_link_libraries(testXcbWrapper Qt::X11Extras) +else() + target_link_libraries(testXcbWrapper Qt::GuiPrivate) +endif() +add_test(NAME kwin-testXcbWrapper COMMAND testXcbWrapper) +ecm_mark_as_test(testXcbWrapper) + +if (XCB_ICCCM_FOUND) + add_executable(testXcbSizeHints test_xcb_size_hints.cpp xcb_scaling_mock.cpp) + set_target_properties(testXcbSizeHints PROPERTIES COMPILE_DEFINITIONS "NO_NONE_WINDOW") + target_link_libraries(testXcbSizeHints + Qt::Test + Qt::Widgets + + KF5::ConfigCore + KF5::WindowSystem + + XCB::ICCCM + XCB::XCB + ) + if (QT_MAJOR_VERSION EQUAL "5") + target_link_libraries(testXcbSizeHints Qt::X11Extras) + else() + target_link_libraries(testXcbSizeHints Qt::GuiPrivate) + endif() + add_test(NAME kwin-testXcbSizeHints COMMAND testXcbSizeHints) + ecm_mark_as_test(testXcbSizeHints) +endif() + +######################################################## +# Test XcbWindow +######################################################## +add_executable(testXcbWindow test_xcb_window.cpp xcb_scaling_mock.cpp) + +target_link_libraries(testXcbWindow + Qt::Test + Qt::Widgets + + KF5::ConfigCore + KF5::WindowSystem + + XCB::XCB +) +if (QT_MAJOR_VERSION EQUAL "5") + target_link_libraries(testXcbWindow Qt::X11Extras) +else() + target_link_libraries(testXcbWindow Qt::GuiPrivate) +endif() +add_test(NAME kwin-testXcbWindow COMMAND testXcbWindow) +ecm_mark_as_test(testXcbWindow) + +include_directories(${KWin_SOURCE_DIR}/src) + +######################################################## +# Test OnScreenNotification +######################################################## +set(testOnScreenNotification_SRCS + ../src/input_event_spy.cpp + ../src/onscreennotification.cpp + onscreennotificationtest.cpp +) +add_executable(testOnScreenNotification ${testOnScreenNotification_SRCS}) + +target_link_libraries(testOnScreenNotification + Qt::DBus + Qt::Quick + Qt::Test + Qt::Widgets # QAction include + + KF5::ConfigCore +) + +add_test(NAME kwin-testOnScreenNotification COMMAND testOnScreenNotification) +ecm_mark_as_test(testOnScreenNotification) + +######################################################## +# Test Gestures +######################################################## +set(testGestures_SRCS + ../src/gestures.cpp + test_gestures.cpp +) +add_executable(testGestures ${testGestures_SRCS}) + +target_link_libraries(testGestures + Qt::Test +) + +add_test(NAME kwin-testGestures COMMAND testGestures) +ecm_mark_as_test(testGestures) + +######################################################## +# Test X11 TimestampUpdate +######################################################## +add_executable(testX11TimestampUpdate test_x11_timestamp_update.cpp) +target_link_libraries(testX11TimestampUpdate + KF5::CoreAddons + Qt::Test + kwin +) +add_test(NAME kwin-testX11TimestampUpdate COMMAND testX11TimestampUpdate) +ecm_mark_as_test(testX11TimestampUpdate) + +set(testOpenGLContextAttributeBuilder_SRCS + ../src/utils/abstract_opengl_context_attribute_builder.cpp + ../src/utils/egl_context_attribute_builder.cpp + opengl_context_attribute_builder_test.cpp +) + +if (HAVE_EPOXY_GLX) + set(testOpenGLContextAttributeBuilder_SRCS ${testOpenGLContextAttributeBuilder_SRCS} ../src/backends/x11/standalone/x11_standalone_glx_context_attribute_builder.cpp) +endif() +add_executable(testOpenGLContextAttributeBuilder ${testOpenGLContextAttributeBuilder_SRCS}) +target_link_libraries(testOpenGLContextAttributeBuilder Qt::Test) +add_test(NAME kwin-testOpenGLContextAttributeBuilder COMMAND testOpenGLContextAttributeBuilder) +ecm_mark_as_test(testOpenGLContextAttributeBuilder) + +set(testXkb_SRCS + ../src/xkb.cpp + test_xkb.cpp +) +add_executable(testXkb ${testXkb_SRCS}) +target_link_libraries(testXkb + kwin + + Qt::Gui + Qt::Test + Qt::Widgets + + KF5::ConfigCore + KF5::WindowSystem + + kwineffects + + XKB::XKB +) +if (QT_MAJOR_VERSION EQUAL "5") + target_link_libraries(testXkb Qt5::XkbCommonSupportPrivate) +else() + target_link_libraries(testXkb Qt::GuiPrivate) +endif() +add_test(NAME kwin-testXkb COMMAND testXkb) +ecm_mark_as_test(testXkb) + +######################################################## +# Test FTrace +######################################################## +add_executable(testFtrace test_ftrace.cpp) +target_link_libraries(testFtrace + Qt::Test + kwin +) +add_test(NAME kwin-testFtrace COMMAND testFtrace) +ecm_mark_as_test(testFtrace) + +######################################################## +# Test KWin Utils +######################################################## +add_executable(testUtils test_utils.cpp) +target_link_libraries(testUtils + Qt::Test + kwin +) +add_test(NAME kwin-testUtils COMMAND testUtils) +ecm_mark_as_test(testUtils) diff --git a/autotests/integration/CMakeLists.txt b/autotests/integration/CMakeLists.txt new file mode 100644 index 0000000..28d701a --- /dev/null +++ b/autotests/integration/CMakeLists.txt @@ -0,0 +1,171 @@ +add_subdirectory(helper) + +add_library(KWinIntegrationTestFramework SHARED) +if (QT_MAJOR_VERSION EQUAL "5") + ecm_add_qtwayland_client_protocol(KWinIntegrationTestFramework + PROTOCOL ${WaylandProtocols_DATADIR}/unstable/input-method/input-method-unstable-v1.xml + BASENAME input-method-unstable-v1 + ) + ecm_add_qtwayland_client_protocol(KWinIntegrationTestFramework + PROTOCOL ${WaylandProtocols_DATADIR}/unstable/text-input/text-input-unstable-v3.xml + BASENAME text-input-unstable-v3 + ) + ecm_add_qtwayland_client_protocol(KWinIntegrationTestFramework + PROTOCOL protocols/wlr-layer-shell-unstable-v1.xml + BASENAME wlr-layer-shell-unstable-v1 + ) + ecm_add_qtwayland_client_protocol(KWinIntegrationTestFramework + PROTOCOL ${WaylandProtocols_DATADIR}/stable/xdg-shell/xdg-shell.xml + BASENAME xdg-shell + ) + ecm_add_qtwayland_client_protocol(KWinIntegrationTestFramework + PROTOCOL ${WaylandProtocols_DATADIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml + BASENAME xdg-decoration-unstable-v1 + ) + ecm_add_qtwayland_client_protocol(KWinIntegrationTestFramework + PROTOCOL ${WaylandProtocols_DATADIR}/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml + BASENAME idle-inhibit-unstable-v1 + ) + ecm_add_qtwayland_client_protocol(KWinIntegrationTestFramework + PROTOCOL ${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-output-device-v2.xml + BASENAME kde-output-device-v2 + ) + ecm_add_qtwayland_client_protocol(KWinIntegrationTestFramework + PROTOCOL ${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-output-management-v2.xml + BASENAME kde-output-management-v2 + ) +else() + qt6_generate_wayland_protocol_client_sources(KWinIntegrationTestFramework FILES + ${WaylandProtocols_DATADIR}/unstable/input-method/input-method-unstable-v1.xml + ${WaylandProtocols_DATADIR}/unstable/text-input/text-input-unstable-v3.xml + ${CMAKE_CURRENT_SOURCE_DIR}/protocols/wlr-layer-shell-unstable-v1.xml + ${WaylandProtocols_DATADIR}/stable/xdg-shell/xdg-shell.xml + ${WaylandProtocols_DATADIR}/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml + ${WaylandProtocols_DATADIR}/unstable/idle-inhibit/idle-inhibit-unstable-v1.xml + ${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-output-device-v2.xml + ${PLASMA_WAYLAND_PROTOCOLS_DIR}/kde-output-management-v2.xml + ) +endif() + +target_sources(KWinIntegrationTestFramework PRIVATE + ../../src/cursor.cpp + + generic_scene_opengl_test.cpp + kwin_wayland_test.cpp + test_helpers.cpp +) +target_link_libraries(KWinIntegrationTestFramework + PUBLIC + Qt::Test + KF5::WaylandClient + Wayland::Client + kwin + + PRIVATE + # Own libraries + KWinXwaylandServerModule + + # Static plugins + KWinQpaPlugin + KF5GlobalAccelKWinPlugin + KF5WindowSystemKWinPlugin + KF5IdleTimeKWinPlugin +) +kcoreaddons_target_static_plugins(KWinIntegrationTestFramework "kwin/effects/plugins" LINK_OPTION "PRIVATE") +set_target_properties(KWinIntegrationTestFramework PROPERTIES CXX_VISIBILITY_PRESET default) + +function(integrationTest) + set(optionArgs WAYLAND_ONLY) + set(oneValueArgs NAME) + set(multiValueArgs SRCS LIBS) + cmake_parse_arguments(ARGS "${optionArgs}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + add_executable(${ARGS_NAME} ${ARGS_SRCS}) + target_link_libraries(${ARGS_NAME} KWinIntegrationTestFramework Qt::Test ${ARGS_LIBS}) + add_test(NAME kwin-${ARGS_NAME} COMMAND dbus-run-session ${CMAKE_BINARY_DIR}/bin/${ARGS_NAME}) + if (${ARGS_WAYLAND_ONLY}) + add_executable(${ARGS_NAME}_waylandonly ${ARGS_SRCS} ) + set_target_properties(${ARGS_NAME}_waylandonly PROPERTIES COMPILE_DEFINITIONS "NO_XWAYLAND") + target_link_libraries(${ARGS_NAME}_waylandonly KWinIntegrationTestFramework Qt::Test ${ARGS_LIBS}) + add_test(NAME kwin-${ARGS_NAME}-waylandonly COMMAND dbus-run-session ${CMAKE_BINARY_DIR}/bin/${ARGS_NAME}_waylandonly) + endif() +endfunction() + +integrationTest(NAME testDontCrashGlxgears SRCS dont_crash_glxgears.cpp) +if (KWIN_BUILD_SCREENLOCKER) + integrationTest(NAME testLockScreen SRCS lockscreen.cpp) +endif() +integrationTest(WAYLAND_ONLY NAME testDecorationInput SRCS decoration_input_test.cpp) +integrationTest(WAYLAND_ONLY NAME testInternalWindow SRCS internal_window.cpp) +integrationTest(WAYLAND_ONLY NAME testTouchInput SRCS touch_input_test.cpp) +integrationTest(WAYLAND_ONLY NAME testInputStackingOrder SRCS input_stacking_order.cpp) +integrationTest(NAME testPointerInput SRCS pointer_input.cpp) +integrationTest(NAME testPlatformCursor SRCS platformcursor.cpp) +integrationTest(WAYLAND_ONLY NAME testDontCrashCancelAnimation SRCS dont_crash_cancel_animation.cpp) +integrationTest(WAYLAND_ONLY NAME testTransientPlacement SRCS transient_placement.cpp) +integrationTest(NAME testDebugConsole SRCS debug_console_test.cpp) +integrationTest(NAME testDontCrashEmptyDeco SRCS dont_crash_empty_deco.cpp) +integrationTest(WAYLAND_ONLY NAME testPlasmaSurface SRCS plasma_surface_test.cpp) +integrationTest(WAYLAND_ONLY NAME testMaximized SRCS maximize_test.cpp) +integrationTest(WAYLAND_ONLY NAME testXdgShellWindow SRCS xdgshellwindow_test.cpp) +integrationTest(WAYLAND_ONLY NAME testDontCrashNoBorder SRCS dont_crash_no_border.cpp) +integrationTest(NAME testXwaylandSelections SRCS xwayland_selections_test.cpp) +integrationTest(WAYLAND_ONLY NAME testSceneOpenGL SRCS scene_opengl_test.cpp ) +integrationTest(WAYLAND_ONLY NAME testSceneOpenGLES SRCS scene_opengl_es_test.cpp ) +integrationTest(WAYLAND_ONLY NAME testScreenChanges SRCS screen_changes_test.cpp) +integrationTest(NAME testModiferOnlyShortcut SRCS modifier_only_shortcut_test.cpp) +if (KWIN_BUILD_TABBOX) + integrationTest(WAYLAND_ONLY NAME testTabBox SRCS tabbox_test.cpp) +endif() +integrationTest(WAYLAND_ONLY NAME testWindowSelection SRCS window_selection_test.cpp) +integrationTest(WAYLAND_ONLY NAME testPointerConstraints SRCS pointer_constraints_test.cpp) +integrationTest(WAYLAND_ONLY NAME testKeyboardLayout SRCS keyboard_layout_test.cpp) +integrationTest(WAYLAND_ONLY NAME testKeymapCreationFailure SRCS keymap_creation_failure_test.cpp) +integrationTest(WAYLAND_ONLY NAME testShowingDesktop SRCS showing_desktop_test.cpp) +integrationTest(WAYLAND_ONLY NAME testDontCrashUseractionsMenu SRCS dont_crash_useractions_menu.cpp) +integrationTest(WAYLAND_ONLY NAME testKWinBindings SRCS kwinbindings_test.cpp) +integrationTest(WAYLAND_ONLY NAME testLayerShellV1Window SRCS layershellv1window_test.cpp) +integrationTest(WAYLAND_ONLY NAME testVirtualDesktop SRCS virtual_desktop_test.cpp) +integrationTest(WAYLAND_ONLY NAME testXdgShellWindowRules SRCS xdgshellwindow_rules_test.cpp) +integrationTest(WAYLAND_ONLY NAME testIdleInhibition SRCS idle_inhibition_test.cpp) +integrationTest(WAYLAND_ONLY NAME testDontCrashCursorPhysicalSizeEmpty SRCS dont_crash_cursor_physical_size_empty.cpp) +integrationTest(WAYLAND_ONLY NAME testDontCrashReinitializeCompositor SRCS dont_crash_reinitialize_compositor.cpp) +integrationTest(WAYLAND_ONLY NAME testNoGlobalShortcuts SRCS no_global_shortcuts_test.cpp) +integrationTest(WAYLAND_ONLY NAME testBufferSizeChange SRCS buffer_size_change_test.cpp ) +integrationTest(WAYLAND_ONLY NAME testPlacement SRCS placement_test.cpp) +integrationTest(WAYLAND_ONLY NAME testActivation SRCS activation_test.cpp) +integrationTest(WAYLAND_ONLY NAME testInputMethod SRCS inputmethod_test.cpp) +integrationTest(WAYLAND_ONLY NAME testScreens SRCS screens_test.cpp) +integrationTest(WAYLAND_ONLY NAME testScreenEdges SRCS screenedges_test.cpp) +integrationTest(WAYLAND_ONLY NAME testOutputChanges SRCS outputchanges_test.cpp) + +qt_add_dbus_interfaces(DBUS_SRCS ${CMAKE_BINARY_DIR}/src/org.kde.kwin.VirtualKeyboard.xml) +integrationTest(WAYLAND_ONLY NAME testVirtualKeyboardDBus SRCS test_virtualkeyboard_dbus.cpp ${DBUS_SRCS}) +integrationTest(WAYLAND_ONLY NAME testNightColor SRCS nightcolor_test.cpp LIBS KWinNightColorPlugin) + +if (XCB_ICCCM_FOUND) + integrationTest(NAME testMoveResize SRCS move_resize_window_test.cpp LIBS XCB::ICCCM) + integrationTest(NAME testStruts SRCS struts_test.cpp LIBS XCB::ICCCM) + integrationTest(NAME testShade SRCS shade_test.cpp LIBS XCB::ICCCM) + integrationTest(NAME testDontCrashAuroraeDestroyDeco SRCS dont_crash_aurorae_destroy_deco.cpp LIBS XCB::ICCCM) + integrationTest(NAME testPlasmaWindow SRCS plasmawindow_test.cpp LIBS XCB::ICCCM) + integrationTest(NAME testScreenEdgeClientShow SRCS screenedge_client_show_test.cpp LIBS XCB::ICCCM) + integrationTest(NAME testX11DesktopWindow SRCS desktop_window_x11_test.cpp LIBS XCB::ICCCM) + integrationTest(NAME testXwaylandInput SRCS xwayland_input_test.cpp LIBS XCB::ICCCM) + integrationTest(NAME testWindowRules SRCS window_rules_test.cpp LIBS XCB::ICCCM) + integrationTest(NAME testX11Window SRCS x11_window_test.cpp LIBS XCB::ICCCM) + integrationTest(NAME testQuickTiling SRCS quick_tiling_test.cpp LIBS XCB::ICCCM) + integrationTest(NAME testGlobalShortcuts SRCS globalshortcuts_test.cpp LIBS XCB::ICCCM) + integrationTest(NAME testSceneQPainter SRCS scene_qpainter_test.cpp LIBS XCB::ICCCM) + integrationTest(NAME testStackingOrder SRCS stacking_order_test.cpp LIBS XCB::ICCCM) + integrationTest(NAME testDbusInterface SRCS dbus_interface_test.cpp LIBS XCB::ICCCM) + integrationTest(NAME testXwaylandServerCrash SRCS xwaylandserver_crash_test.cpp LIBS XCB::ICCCM) + integrationTest(NAME testXwaylandServerRestart SRCS xwaylandserver_restart_test.cpp LIBS XCB::ICCCM) + + if (KWIN_BUILD_ACTIVITIES) + integrationTest(NAME testActivities SRCS activities_test.cpp LIBS XCB::ICCCM) + endif() +endif() + +add_subdirectory(scripting) +add_subdirectory(effects) +add_subdirectory(fakes) diff --git a/autotests/integration/activation_test.cpp b/autotests/integration/activation_test.cpp new file mode 100644 index 0000000..77c4d89 --- /dev/null +++ b/autotests/integration/activation_test.cpp @@ -0,0 +1,570 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2019 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_activation-0"); + +class ActivationTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testSwitchToWindowToLeft(); + void testSwitchToWindowToRight(); + void testSwitchToWindowAbove(); + void testSwitchToWindowBelow(); + void testSwitchToWindowMaximized(); + void testSwitchToWindowFullScreen(); + +private: + void stackScreensHorizontally(); + void stackScreensVertically(); +}; + +void ActivationTest::initTestCase() +{ + qRegisterMetaType(); + + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); +} + +void ActivationTest::init() +{ + QVERIFY(Test::setupWaylandConnection()); + + workspace()->setActiveOutput(QPoint(640, 512)); + Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void ActivationTest::cleanup() +{ + Test::destroyWaylandConnection(); + + stackScreensHorizontally(); +} + +void ActivationTest::testSwitchToWindowToLeft() +{ + // This test verifies that "Switch to Window to the Left" shortcut works. + + using namespace KWayland::Client; + + // Prepare the test environment. + stackScreensHorizontally(); + + // Create several windows on the left screen. + std::unique_ptr surface1(Test::createSurface()); + std::unique_ptr shellSurface1(Test::createXdgToplevelSurface(surface1.get())); + Window *window1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::blue); + QVERIFY(window1); + QVERIFY(window1->isActive()); + + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + Window *window2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::blue); + QVERIFY(window2); + QVERIFY(window2->isActive()); + + window1->move(QPoint(300, 200)); + window2->move(QPoint(500, 200)); + + // Create several windows on the right screen. + std::unique_ptr surface3(Test::createSurface()); + std::unique_ptr shellSurface3(Test::createXdgToplevelSurface(surface3.get())); + Window *window3 = Test::renderAndWaitForShown(surface3.get(), QSize(100, 50), Qt::blue); + QVERIFY(window3); + QVERIFY(window3->isActive()); + + std::unique_ptr surface4(Test::createSurface()); + std::unique_ptr shellSurface4(Test::createXdgToplevelSurface(surface4.get())); + Window *window4 = Test::renderAndWaitForShown(surface4.get(), QSize(100, 50), Qt::blue); + QVERIFY(window4); + QVERIFY(window4->isActive()); + + window3->move(QPoint(1380, 200)); + window4->move(QPoint(1580, 200)); + + // Switch to window to the left. + workspace()->switchWindow(Workspace::DirectionWest); + QVERIFY(window3->isActive()); + + // Switch to window to the left. + workspace()->switchWindow(Workspace::DirectionWest); + QVERIFY(window2->isActive()); + + // Switch to window to the left. + workspace()->switchWindow(Workspace::DirectionWest); + QVERIFY(window1->isActive()); + + // Switch to window to the left. + workspace()->switchWindow(Workspace::DirectionWest); + QVERIFY(window4->isActive()); + + // Destroy all windows. + shellSurface1.reset(); + QVERIFY(Test::waitForWindowDestroyed(window1)); + shellSurface2.reset(); + QVERIFY(Test::waitForWindowDestroyed(window2)); + shellSurface3.reset(); + QVERIFY(Test::waitForWindowDestroyed(window3)); + shellSurface4.reset(); + QVERIFY(Test::waitForWindowDestroyed(window4)); +} + +void ActivationTest::testSwitchToWindowToRight() +{ + // This test verifies that "Switch to Window to the Right" shortcut works. + + using namespace KWayland::Client; + + // Prepare the test environment. + stackScreensHorizontally(); + + // Create several windows on the left screen. + std::unique_ptr surface1(Test::createSurface()); + std::unique_ptr shellSurface1(Test::createXdgToplevelSurface(surface1.get())); + Window *window1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::blue); + QVERIFY(window1); + QVERIFY(window1->isActive()); + + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + Window *window2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::blue); + QVERIFY(window2); + QVERIFY(window2->isActive()); + + window1->move(QPoint(300, 200)); + window2->move(QPoint(500, 200)); + + // Create several windows on the right screen. + std::unique_ptr surface3(Test::createSurface()); + std::unique_ptr shellSurface3(Test::createXdgToplevelSurface(surface3.get())); + Window *window3 = Test::renderAndWaitForShown(surface3.get(), QSize(100, 50), Qt::blue); + QVERIFY(window3); + QVERIFY(window3->isActive()); + + std::unique_ptr surface4(Test::createSurface()); + std::unique_ptr shellSurface4(Test::createXdgToplevelSurface(surface4.get())); + Window *window4 = Test::renderAndWaitForShown(surface4.get(), QSize(100, 50), Qt::blue); + QVERIFY(window4); + QVERIFY(window4->isActive()); + + window3->move(QPoint(1380, 200)); + window4->move(QPoint(1580, 200)); + + // Switch to window to the right. + workspace()->switchWindow(Workspace::DirectionEast); + QVERIFY(window1->isActive()); + + // Switch to window to the right. + workspace()->switchWindow(Workspace::DirectionEast); + QVERIFY(window2->isActive()); + + // Switch to window to the right. + workspace()->switchWindow(Workspace::DirectionEast); + QVERIFY(window3->isActive()); + + // Switch to window to the right. + workspace()->switchWindow(Workspace::DirectionEast); + QVERIFY(window4->isActive()); + + // Destroy all windows. + shellSurface1.reset(); + QVERIFY(Test::waitForWindowDestroyed(window1)); + shellSurface2.reset(); + QVERIFY(Test::waitForWindowDestroyed(window2)); + shellSurface3.reset(); + QVERIFY(Test::waitForWindowDestroyed(window3)); + shellSurface4.reset(); + QVERIFY(Test::waitForWindowDestroyed(window4)); +} + +void ActivationTest::testSwitchToWindowAbove() +{ + // This test verifies that "Switch to Window Above" shortcut works. + + using namespace KWayland::Client; + + // Prepare the test environment. + stackScreensVertically(); + + // Create several windows on the top screen. + std::unique_ptr surface1(Test::createSurface()); + std::unique_ptr shellSurface1(Test::createXdgToplevelSurface(surface1.get())); + Window *window1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::blue); + QVERIFY(window1); + QVERIFY(window1->isActive()); + + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + Window *window2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::blue); + QVERIFY(window2); + QVERIFY(window2->isActive()); + + window1->move(QPoint(200, 300)); + window2->move(QPoint(200, 500)); + + // Create several windows on the bottom screen. + std::unique_ptr surface3(Test::createSurface()); + std::unique_ptr shellSurface3(Test::createXdgToplevelSurface(surface3.get())); + Window *window3 = Test::renderAndWaitForShown(surface3.get(), QSize(100, 50), Qt::blue); + QVERIFY(window3); + QVERIFY(window3->isActive()); + + std::unique_ptr surface4(Test::createSurface()); + std::unique_ptr shellSurface4(Test::createXdgToplevelSurface(surface4.get())); + Window *window4 = Test::renderAndWaitForShown(surface4.get(), QSize(100, 50), Qt::blue); + QVERIFY(window4); + QVERIFY(window4->isActive()); + + window3->move(QPoint(200, 1224)); + window4->move(QPoint(200, 1424)); + + // Switch to window above. + workspace()->switchWindow(Workspace::DirectionNorth); + QVERIFY(window3->isActive()); + + // Switch to window above. + workspace()->switchWindow(Workspace::DirectionNorth); + QVERIFY(window2->isActive()); + + // Switch to window above. + workspace()->switchWindow(Workspace::DirectionNorth); + QVERIFY(window1->isActive()); + + // Switch to window above. + workspace()->switchWindow(Workspace::DirectionNorth); + QVERIFY(window4->isActive()); + + // Destroy all windows. + shellSurface1.reset(); + QVERIFY(Test::waitForWindowDestroyed(window1)); + shellSurface2.reset(); + QVERIFY(Test::waitForWindowDestroyed(window2)); + shellSurface3.reset(); + QVERIFY(Test::waitForWindowDestroyed(window3)); + shellSurface4.reset(); + QVERIFY(Test::waitForWindowDestroyed(window4)); +} + +void ActivationTest::testSwitchToWindowBelow() +{ + // This test verifies that "Switch to Window Bottom" shortcut works. + + using namespace KWayland::Client; + + // Prepare the test environment. + stackScreensVertically(); + + // Create several windows on the top screen. + std::unique_ptr surface1(Test::createSurface()); + std::unique_ptr shellSurface1(Test::createXdgToplevelSurface(surface1.get())); + Window *window1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::blue); + QVERIFY(window1); + QVERIFY(window1->isActive()); + + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + Window *window2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::blue); + QVERIFY(window2); + QVERIFY(window2->isActive()); + + window1->move(QPoint(200, 300)); + window2->move(QPoint(200, 500)); + + // Create several windows on the bottom screen. + std::unique_ptr surface3(Test::createSurface()); + std::unique_ptr shellSurface3(Test::createXdgToplevelSurface(surface3.get())); + Window *window3 = Test::renderAndWaitForShown(surface3.get(), QSize(100, 50), Qt::blue); + QVERIFY(window3); + QVERIFY(window3->isActive()); + + std::unique_ptr surface4(Test::createSurface()); + std::unique_ptr shellSurface4(Test::createXdgToplevelSurface(surface4.get())); + Window *window4 = Test::renderAndWaitForShown(surface4.get(), QSize(100, 50), Qt::blue); + QVERIFY(window4); + QVERIFY(window4->isActive()); + + window3->move(QPoint(200, 1224)); + window4->move(QPoint(200, 1424)); + + // Switch to window below. + workspace()->switchWindow(Workspace::DirectionSouth); + QVERIFY(window1->isActive()); + + // Switch to window below. + workspace()->switchWindow(Workspace::DirectionSouth); + QVERIFY(window2->isActive()); + + // Switch to window below. + workspace()->switchWindow(Workspace::DirectionSouth); + QVERIFY(window3->isActive()); + + // Switch to window below. + workspace()->switchWindow(Workspace::DirectionSouth); + QVERIFY(window4->isActive()); + + // Destroy all windows. + shellSurface1.reset(); + QVERIFY(Test::waitForWindowDestroyed(window1)); + shellSurface2.reset(); + QVERIFY(Test::waitForWindowDestroyed(window2)); + shellSurface3.reset(); + QVERIFY(Test::waitForWindowDestroyed(window3)); + shellSurface4.reset(); + QVERIFY(Test::waitForWindowDestroyed(window4)); +} + +void ActivationTest::testSwitchToWindowMaximized() +{ + // This test verifies that we switch to the top-most maximized window, i.e. + // the one that user sees at the moment. See bug 411356. + + using namespace KWayland::Client; + + // Prepare the test environment. + stackScreensHorizontally(); + + // Create several maximized windows on the left screen. + std::unique_ptr surface1(Test::createSurface()); + std::unique_ptr shellSurface1(Test::createXdgToplevelSurface(surface1.get())); + QSignalSpy toplevelConfigureRequestedSpy1(shellSurface1.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy1(shellSurface1->xdgSurface(), &Test::XdgSurface::configureRequested); + Window *window1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::blue); + QVERIFY(window1); + QVERIFY(window1->isActive()); + QVERIFY(surfaceConfigureRequestedSpy1.wait()); // Wait for the configure event with the activated state. + workspace()->slotWindowMaximize(); + QVERIFY(surfaceConfigureRequestedSpy1.wait()); + QSignalSpy frameGeometryChangedSpy1(window1, &Window::frameGeometryChanged); + shellSurface1->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy1.last().at(0).value()); + Test::render(surface1.get(), toplevelConfigureRequestedSpy1.last().at(0).toSize(), Qt::red); + QVERIFY(frameGeometryChangedSpy1.wait()); + + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + QSignalSpy toplevelConfigureRequestedSpy2(shellSurface2.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy2(shellSurface2->xdgSurface(), &Test::XdgSurface::configureRequested); + Window *window2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::blue); + QVERIFY(window2); + QVERIFY(window2->isActive()); + QVERIFY(surfaceConfigureRequestedSpy2.wait()); // Wait for the configure event with the activated state. + workspace()->slotWindowMaximize(); + QVERIFY(surfaceConfigureRequestedSpy2.wait()); + QSignalSpy frameGeometryChangedSpy2(window2, &Window::frameGeometryChanged); + shellSurface2->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy2.last().at(0).value()); + Test::render(surface2.get(), toplevelConfigureRequestedSpy2.last().at(0).toSize(), Qt::red); + QVERIFY(frameGeometryChangedSpy2.wait()); + + const QList stackingOrder = workspace()->stackingOrder(); + QVERIFY(stackingOrder.indexOf(window1) < stackingOrder.indexOf(window2)); + QCOMPARE(window1->maximizeMode(), MaximizeFull); + QCOMPARE(window2->maximizeMode(), MaximizeFull); + + // Create several windows on the right screen. + std::unique_ptr surface3(Test::createSurface()); + std::unique_ptr shellSurface3(Test::createXdgToplevelSurface(surface3.get())); + Window *window3 = Test::renderAndWaitForShown(surface3.get(), QSize(100, 50), Qt::blue); + QVERIFY(window3); + QVERIFY(window3->isActive()); + + std::unique_ptr surface4(Test::createSurface()); + std::unique_ptr shellSurface4(Test::createXdgToplevelSurface(surface4.get())); + Window *window4 = Test::renderAndWaitForShown(surface4.get(), QSize(100, 50), Qt::blue); + QVERIFY(window4); + QVERIFY(window4->isActive()); + + window3->move(QPoint(1380, 200)); + window4->move(QPoint(1580, 200)); + + // Switch to window to the left. + workspace()->switchWindow(Workspace::DirectionWest); + QVERIFY(window3->isActive()); + + // Switch to window to the left. + workspace()->switchWindow(Workspace::DirectionWest); + QVERIFY(window2->isActive()); + + // Switch to window to the left. + workspace()->switchWindow(Workspace::DirectionWest); + QVERIFY(window4->isActive()); + + // Destroy all windows. + shellSurface1.reset(); + QVERIFY(Test::waitForWindowDestroyed(window1)); + shellSurface2.reset(); + QVERIFY(Test::waitForWindowDestroyed(window2)); + shellSurface3.reset(); + QVERIFY(Test::waitForWindowDestroyed(window3)); + shellSurface4.reset(); + QVERIFY(Test::waitForWindowDestroyed(window4)); +} + +void ActivationTest::testSwitchToWindowFullScreen() +{ + // This test verifies that we switch to the top-most fullscreen window, i.e. + // the one that user sees at the moment. See bug 411356. + + using namespace KWayland::Client; + + // Prepare the test environment. + stackScreensVertically(); + + // Create several maximized windows on the top screen. + std::unique_ptr surface1(Test::createSurface()); + std::unique_ptr shellSurface1(Test::createXdgToplevelSurface(surface1.get())); + QSignalSpy toplevelConfigureRequestedSpy1(shellSurface1.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy1(shellSurface1->xdgSurface(), &Test::XdgSurface::configureRequested); + Window *window1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::blue); + QVERIFY(window1); + QVERIFY(window1->isActive()); + QVERIFY(surfaceConfigureRequestedSpy1.wait()); // Wait for the configure event with the activated state. + workspace()->slotWindowFullScreen(); + QVERIFY(surfaceConfigureRequestedSpy1.wait()); + QSignalSpy frameGeometryChangedSpy1(window1, &Window::frameGeometryChanged); + shellSurface1->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy1.last().at(0).value()); + Test::render(surface1.get(), toplevelConfigureRequestedSpy1.last().at(0).toSize(), Qt::red); + QVERIFY(frameGeometryChangedSpy1.wait()); + + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + QSignalSpy toplevelConfigureRequestedSpy2(shellSurface2.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy2(shellSurface2->xdgSurface(), &Test::XdgSurface::configureRequested); + Window *window2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::blue); + QVERIFY(window2); + QVERIFY(window2->isActive()); + QVERIFY(surfaceConfigureRequestedSpy2.wait()); // Wait for the configure event with the activated state. + workspace()->slotWindowFullScreen(); + QVERIFY(surfaceConfigureRequestedSpy2.wait()); + QSignalSpy frameGeometryChangedSpy2(window2, &Window::frameGeometryChanged); + shellSurface2->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy2.last().at(0).value()); + Test::render(surface2.get(), toplevelConfigureRequestedSpy2.last().at(0).toSize(), Qt::red); + QVERIFY(frameGeometryChangedSpy2.wait()); + + const QList stackingOrder = workspace()->stackingOrder(); + QVERIFY(stackingOrder.indexOf(window1) < stackingOrder.indexOf(window2)); + QVERIFY(window1->isFullScreen()); + QVERIFY(window2->isFullScreen()); + + // Create several windows on the bottom screen. + std::unique_ptr surface3(Test::createSurface()); + std::unique_ptr shellSurface3(Test::createXdgToplevelSurface(surface3.get())); + Window *window3 = Test::renderAndWaitForShown(surface3.get(), QSize(100, 50), Qt::blue); + QVERIFY(window3); + QVERIFY(window3->isActive()); + + std::unique_ptr surface4(Test::createSurface()); + std::unique_ptr shellSurface4(Test::createXdgToplevelSurface(surface4.get())); + Window *window4 = Test::renderAndWaitForShown(surface4.get(), QSize(100, 50), Qt::blue); + QVERIFY(window4); + QVERIFY(window4->isActive()); + + window3->move(QPoint(200, 1224)); + window4->move(QPoint(200, 1424)); + + // Switch to window above. + workspace()->switchWindow(Workspace::DirectionNorth); + QVERIFY(window3->isActive()); + + // Switch to window above. + workspace()->switchWindow(Workspace::DirectionNorth); + QVERIFY(window2->isActive()); + + // Switch to window above. + workspace()->switchWindow(Workspace::DirectionNorth); + QVERIFY(window4->isActive()); + + // Destroy all windows. + shellSurface1.reset(); + QVERIFY(Test::waitForWindowDestroyed(window1)); + shellSurface2.reset(); + QVERIFY(Test::waitForWindowDestroyed(window2)); + shellSurface3.reset(); + QVERIFY(Test::waitForWindowDestroyed(window3)); + shellSurface4.reset(); + QVERIFY(Test::waitForWindowDestroyed(window4)); +} + +void ActivationTest::stackScreensHorizontally() +{ + // Process pending wl_output bind requests before destroying all outputs. + QTest::qWait(1); + + const QVector screenGeometries{ + QRect(0, 0, 1280, 1024), + QRect(1280, 0, 1280, 1024), + }; + + const QVector screenScales{ + 1, + 1, + }; + + QMetaObject::invokeMethod(kwinApp()->platform(), + "setVirtualOutputs", + Qt::DirectConnection, + Q_ARG(int, screenGeometries.count()), + Q_ARG(QVector, screenGeometries), + Q_ARG(QVector, screenScales)); +} + +void ActivationTest::stackScreensVertically() +{ + // Process pending wl_output bind requests before destroying all outputs. + QTest::qWait(1); + + const QVector screenGeometries{ + QRect(0, 0, 1280, 1024), + QRect(0, 1024, 1280, 1024), + }; + + const QVector screenScales{ + 1, + 1, + }; + + QMetaObject::invokeMethod(kwinApp()->platform(), + "setVirtualOutputs", + Qt::DirectConnection, + Q_ARG(int, screenGeometries.count()), + Q_ARG(QVector, screenGeometries), + Q_ARG(QVector, screenScales)); +} + +} + +WAYLANDTEST_MAIN(KWin::ActivationTest) +#include "activation_test.moc" diff --git a/autotests/integration/activities_test.cpp b/autotests/integration/activities_test.cpp new file mode 100644 index 0000000..427db4e --- /dev/null +++ b/autotests/integration/activities_test.cpp @@ -0,0 +1,148 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "activities.h" +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "deleted.h" +#include "utils/xcbutils.h" +#include "wayland_server.h" +#include "workspace.h" +#include "x11window.h" +#include + +#include +#include +#include + +#include +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_activities-0"); + +class ActivitiesTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + void testSetOnActivitiesValidates(); + +private: +}; + +void ActivitiesTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->setUseKActivities(true); + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); + setenv("QT_QPA_PLATFORM", "wayland", true); +} + +void ActivitiesTest::cleanupTestCase() +{ + // terminate any still running kactivitymanagerd + QDBusConnection::sessionBus().asyncCall(QDBusMessage::createMethodCall( + QStringLiteral("org.kde.ActivityManager"), + QStringLiteral("/ActivityManager"), + QStringLiteral("org.qtproject.Qt.QCoreApplication"), + QStringLiteral("quit"))); +} + +void ActivitiesTest::init() +{ + workspace()->setActiveOutput(QPoint(640, 512)); + Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void ActivitiesTest::cleanup() +{ +} + +struct XcbConnectionDeleter +{ + void operator()(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } +}; + +void ActivitiesTest::testSetOnActivitiesValidates() +{ + // this test verifies that windows can't be placed on activities that don't exist + // create an xcb window + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + + xcb_window_t windowId = xcb_generate_id(c.get()); + const QRect windowGeometry(0, 0, 100, 200); + + auto cookie = xcb_create_window_checked(c.get(), 0, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, 0, 0, nullptr); + QVERIFY(!xcb_request_check(c.get(), cookie)); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QVERIFY(window->isDecorated()); + + // verify the test machine doesn't have the following activities used + QVERIFY(!Workspace::self()->activities()->all().contains(QStringLiteral("foo"))); + QVERIFY(!Workspace::self()->activities()->all().contains(QStringLiteral("bar"))); + + window->setOnActivities(QStringList{QStringLiteral("foo"), QStringLiteral("bar")}); + QVERIFY(!window->activities().contains(QLatin1String("foo"))); + QVERIFY(!window->activities().contains(QLatin1String("bar"))); + + // and destroy the window again + xcb_unmap_window(c.get(), windowId); + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + c.reset(); + + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + QVERIFY(windowClosedSpy.wait()); +} + +} + +WAYLANDTEST_MAIN(KWin::ActivitiesTest) +#include "activities_test.moc" diff --git a/autotests/integration/buffer_size_change_test.cpp b/autotests/integration/buffer_size_change_test.cpp new file mode 100644 index 0000000..b3454f2 --- /dev/null +++ b/autotests/integration/buffer_size_change_test.cpp @@ -0,0 +1,114 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2019 Roman Gilg + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "generic_scene_opengl_test.h" + +#include "composite.h" +#include "scene.h" +#include "wayland_server.h" +#include "window.h" + +#include +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_buffer_size_change-0"); + +class BufferSizeChangeTest : public GenericSceneOpenGLTest +{ + Q_OBJECT +public: + BufferSizeChangeTest() + : GenericSceneOpenGLTest(QByteArrayLiteral("O2")) + { + } +private Q_SLOTS: + void init(); + void testShmBufferSizeChange(); + void testShmBufferSizeChangeOnSubSurface(); +}; + +void BufferSizeChangeTest::init() +{ + QVERIFY(Test::setupWaylandConnection()); +} + +void BufferSizeChangeTest::testShmBufferSizeChange() +{ + // This test verifies that an SHM buffer size change is handled correctly + + using namespace KWayland::Client; + + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + + // set buffer size + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // add a first repaint + QSignalSpy frameRenderedSpy(Compositor::self()->scene(), &Scene::frameRendered); + Compositor::self()->scene()->addRepaintFull(); + QVERIFY(frameRenderedSpy.wait()); + + // now change buffer size + Test::render(surface.get(), QSize(30, 10), Qt::red); + + QSignalSpy damagedSpy(window, &Window::damaged); + QVERIFY(damagedSpy.wait()); + KWin::Compositor::self()->scene()->addRepaintFull(); + QVERIFY(frameRenderedSpy.wait()); +} + +void BufferSizeChangeTest::testShmBufferSizeChangeOnSubSurface() +{ + using namespace KWayland::Client; + + // setup parent surface + std::unique_ptr parentSurface(Test::createSurface()); + QVERIFY(parentSurface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(parentSurface.get())); + QVERIFY(shellSurface != nullptr); + + // setup sub surface + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr subSurface(Test::createSubSurface(surface.get(), parentSurface.get())); + QVERIFY(subSurface != nullptr); + + // set buffer sizes + Test::render(surface.get(), QSize(30, 10), Qt::red); + Window *parent = Test::renderAndWaitForShown(parentSurface.get(), QSize(100, 50), Qt::blue); + QVERIFY(parent); + + // add a first repaint + QSignalSpy frameRenderedSpy(Compositor::self()->scene(), &Scene::frameRendered); + Compositor::self()->scene()->addRepaintFull(); + QVERIFY(frameRenderedSpy.wait()); + + // change buffer size of sub surface + QSignalSpy damagedParentSpy(parent, &Window::damaged); + Test::render(surface.get(), QSize(20, 10), Qt::red); + parentSurface->commit(KWayland::Client::Surface::CommitFlag::None); + + QVERIFY(damagedParentSpy.wait()); + + // add a second repaint + KWin::Compositor::self()->scene()->addRepaintFull(); + QVERIFY(frameRenderedSpy.wait()); +} + +} + +WAYLANDTEST_MAIN(KWin::BufferSizeChangeTest) +#include "buffer_size_change_test.moc" diff --git a/autotests/integration/data/anim-data-delete-effect/effect.js b/autotests/integration/data/anim-data-delete-effect/effect.js new file mode 100644 index 0000000..0a4170b --- /dev/null +++ b/autotests/integration/data/anim-data-delete-effect/effect.js @@ -0,0 +1,14 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +effects.windowAdded.connect(function(w) { + w.fadeAnimation = effect.animate(w, Effect.Opacity, 100, 1.0, 0.0); +}); +effect.animationEnded.connect(function(w) { + cancel(w.fadeAnimation); +}); diff --git a/autotests/integration/data/example.desktop b/autotests/integration/data/example.desktop new file mode 100644 index 0000000..739f5d3 --- /dev/null +++ b/autotests/integration/data/example.desktop @@ -0,0 +1,3 @@ +[Desktop Entry] +Name=An example application +Icon=kwin diff --git a/autotests/integration/data/rules/maximize-vert-apply-initial b/autotests/integration/data/rules/maximize-vert-apply-initial new file mode 100644 index 0000000..1f8d42e --- /dev/null +++ b/autotests/integration/data/rules/maximize-vert-apply-initial @@ -0,0 +1,13 @@ +Description=Window settings for kpat +clientmachine=localhost +clientmachinematch=0 +maximizevert=true +maximizevertrule=3 +title=KPatience +titlematch=0 +types=1 +windowrole=mainwindow +windowrolematch=1 +wmclass=kpat +wmclasscomplete=false +wmclassmatch=1 diff --git a/autotests/integration/dbus_interface_test.cpp b/autotests/integration/dbus_interface_test.cpp new file mode 100644 index 0000000..b536b3b --- /dev/null +++ b/autotests/integration/dbus_interface_test.cpp @@ -0,0 +1,385 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2018 Martin Flöser + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL +*/ +#include "config-kwin.h" + +#include "kwin_wayland_test.h" + +#include "atoms.h" +#include "core/platform.h" +#include "deleted.h" +#include "rules.h" +#include "virtualdesktops.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" +#include "x11window.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include + +using namespace KWin; +using namespace KWayland::Client; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_dbus_interface-0"); + +const QString s_destination{QStringLiteral("org.kde.KWin")}; +const QString s_path{QStringLiteral("/KWin")}; +const QString s_interface{QStringLiteral("org.kde.KWin")}; + +class TestDbusInterface : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testGetWindowInfoInvalidUuid(); + void testGetWindowInfoXdgShellClient(); + void testGetWindowInfoX11Client(); +}; + +void TestDbusInterface::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + VirtualDesktopManager::self()->setCount(4); +} + +void TestDbusInterface::init() +{ + QVERIFY(Test::setupWaylandConnection()); +} + +void TestDbusInterface::cleanup() +{ + Test::destroyWaylandConnection(); +} + +namespace +{ +QDBusPendingCall getWindowInfo(const QUuid &uuid) +{ + auto msg = QDBusMessage::createMethodCall(s_destination, s_path, s_interface, QStringLiteral("getWindowInfo")); + msg.setArguments({uuid.toString()}); + return QDBusConnection::sessionBus().asyncCall(msg); +} +} + +void TestDbusInterface::testGetWindowInfoInvalidUuid() +{ + QDBusPendingReply reply{getWindowInfo(QUuid::createUuid())}; + reply.waitForFinished(); + QVERIFY(reply.isValid()); + QVERIFY(!reply.isError()); + const auto windowData = reply.value(); + QVERIFY(windowData.empty()); +} + +void TestDbusInterface::testGetWindowInfoXdgShellClient() +{ + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + shellSurface->set_app_id(QStringLiteral("org.kde.foo")); + shellSurface->set_title(QStringLiteral("Test window")); + + // now let's render + Test::render(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(windowAddedSpy.isEmpty()); + QVERIFY(windowAddedSpy.wait()); + auto window = windowAddedSpy.first().first().value(); + QVERIFY(window); + + const QVariantMap expectedData = { + {QStringLiteral("type"), int(NET::Normal)}, + {QStringLiteral("x"), window->x()}, + {QStringLiteral("y"), window->y()}, + {QStringLiteral("width"), window->width()}, + {QStringLiteral("height"), window->height()}, + {QStringLiteral("desktops"), window->desktopIds()}, + {QStringLiteral("minimized"), false}, + {QStringLiteral("shaded"), false}, + {QStringLiteral("fullscreen"), false}, + {QStringLiteral("keepAbove"), false}, + {QStringLiteral("keepBelow"), false}, + {QStringLiteral("skipTaskbar"), false}, + {QStringLiteral("skipPager"), false}, + {QStringLiteral("skipSwitcher"), false}, + {QStringLiteral("maximizeHorizontal"), false}, + {QStringLiteral("maximizeVertical"), false}, + {QStringLiteral("noBorder"), false}, + {QStringLiteral("clientMachine"), QString()}, + {QStringLiteral("localhost"), true}, + {QStringLiteral("role"), QString()}, + {QStringLiteral("resourceName"), QStringLiteral("testDbusInterface")}, + {QStringLiteral("resourceClass"), QStringLiteral("org.kde.foo")}, + {QStringLiteral("desktopFile"), QStringLiteral("org.kde.foo")}, + {QStringLiteral("caption"), QStringLiteral("Test window")}, +#if KWIN_BUILD_ACTIVITIES + {QStringLiteral("activities"), QStringList()}, +#endif + }; + + // let's get the window info + QDBusPendingReply reply{getWindowInfo(window->internalId())}; + reply.waitForFinished(); + QVERIFY(reply.isValid()); + QVERIFY(!reply.isError()); + auto windowData = reply.value(); + QCOMPARE(windowData, expectedData); + + auto verifyProperty = [window](const QString &name) { + QDBusPendingReply reply{getWindowInfo(window->internalId())}; + reply.waitForFinished(); + return reply.value().value(name).toBool(); + }; + + QVERIFY(!window->isMinimized()); + window->setMinimized(true); + QVERIFY(window->isMinimized()); + QCOMPARE(verifyProperty(QStringLiteral("minimized")), true); + + QVERIFY(!window->keepAbove()); + window->setKeepAbove(true); + QVERIFY(window->keepAbove()); + QCOMPARE(verifyProperty(QStringLiteral("keepAbove")), true); + + QVERIFY(!window->keepBelow()); + window->setKeepBelow(true); + QVERIFY(window->keepBelow()); + QCOMPARE(verifyProperty(QStringLiteral("keepBelow")), true); + + QVERIFY(!window->skipTaskbar()); + window->setSkipTaskbar(true); + QVERIFY(window->skipTaskbar()); + QCOMPARE(verifyProperty(QStringLiteral("skipTaskbar")), true); + + QVERIFY(!window->skipPager()); + window->setSkipPager(true); + QVERIFY(window->skipPager()); + QCOMPARE(verifyProperty(QStringLiteral("skipPager")), true); + + QVERIFY(!window->skipSwitcher()); + window->setSkipSwitcher(true); + QVERIFY(window->skipSwitcher()); + QCOMPARE(verifyProperty(QStringLiteral("skipSwitcher")), true); + + // not testing shaded as that's X11 + // not testing fullscreen, maximizeHorizontal, maximizeVertical and noBorder as those require window geometry changes + + QCOMPARE(window->desktop(), 1); + workspace()->sendWindowToDesktop(window, 2, false); + QCOMPARE(window->desktop(), 2); + reply = getWindowInfo(window->internalId()); + reply.waitForFinished(); + QCOMPARE(reply.value().value(QStringLiteral("desktops")).toStringList(), window->desktopIds()); + + window->move(QPoint(10, 20)); + reply = getWindowInfo(window->internalId()); + reply.waitForFinished(); + QCOMPARE(reply.value().value(QStringLiteral("x")).toInt(), window->x()); + QCOMPARE(reply.value().value(QStringLiteral("y")).toInt(), window->y()); + // not testing width, height as that would require window geometry change + + // finally close window + const auto id = window->internalId(); + QSignalSpy windowClosedSpy(window, &Window::windowClosed); + shellSurface.reset(); + surface.reset(); + QVERIFY(windowClosedSpy.wait()); + QCOMPARE(windowClosedSpy.count(), 1); + + reply = getWindowInfo(id); + reply.waitForFinished(); + QVERIFY(reply.value().empty()); +} + +struct XcbConnectionDeleter +{ + void operator()(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } +}; + +void TestDbusInterface::testGetWindowInfoX11Client() +{ + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 600, 400); + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + xcb_icccm_set_wm_class(c.get(), windowId, 7, "foo\0bar"); + NETWinInfo winInfo(c.get(), windowId, rootWindow(), NET::Properties(), NET::Properties2()); + winInfo.setName("Some caption"); + winInfo.setDesktopFileName("org.kde.foo"); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QCOMPARE(window->clientSize(), windowGeometry.size()); + + const QVariantMap expectedData = { + {QStringLiteral("type"), NET::Normal}, + {QStringLiteral("x"), window->x()}, + {QStringLiteral("y"), window->y()}, + {QStringLiteral("width"), window->width()}, + {QStringLiteral("height"), window->height()}, + {QStringLiteral("desktops"), window->desktopIds()}, + {QStringLiteral("minimized"), false}, + {QStringLiteral("shaded"), false}, + {QStringLiteral("fullscreen"), false}, + {QStringLiteral("keepAbove"), false}, + {QStringLiteral("keepBelow"), false}, + {QStringLiteral("skipTaskbar"), false}, + {QStringLiteral("skipPager"), false}, + {QStringLiteral("skipSwitcher"), false}, + {QStringLiteral("maximizeHorizontal"), false}, + {QStringLiteral("maximizeVertical"), false}, + {QStringLiteral("noBorder"), false}, + {QStringLiteral("role"), QString()}, + {QStringLiteral("resourceName"), QStringLiteral("foo")}, + {QStringLiteral("resourceClass"), QStringLiteral("bar")}, + {QStringLiteral("desktopFile"), QStringLiteral("org.kde.foo")}, + {QStringLiteral("caption"), QStringLiteral("Some caption")}, +#if KWIN_BUILD_ACTIVITIES + {QStringLiteral("activities"), QStringList()}, +#endif + }; + + // let's get the window info + QDBusPendingReply reply{getWindowInfo(window->internalId())}; + reply.waitForFinished(); + QVERIFY(reply.isValid()); + QVERIFY(!reply.isError()); + auto windowData = reply.value(); + // not testing clientmachine as that is system dependent due to that also not testing localhost + windowData.remove(QStringLiteral("clientMachine")); + windowData.remove(QStringLiteral("localhost")); + QCOMPARE(windowData, expectedData); + + auto verifyProperty = [window](const QString &name) { + QDBusPendingReply reply{getWindowInfo(window->internalId())}; + reply.waitForFinished(); + return reply.value().value(name).toBool(); + }; + + QVERIFY(!window->isMinimized()); + window->setMinimized(true); + QVERIFY(window->isMinimized()); + QCOMPARE(verifyProperty(QStringLiteral("minimized")), true); + + QVERIFY(!window->keepAbove()); + window->setKeepAbove(true); + QVERIFY(window->keepAbove()); + QCOMPARE(verifyProperty(QStringLiteral("keepAbove")), true); + + QVERIFY(!window->keepBelow()); + window->setKeepBelow(true); + QVERIFY(window->keepBelow()); + QCOMPARE(verifyProperty(QStringLiteral("keepBelow")), true); + + QVERIFY(!window->skipTaskbar()); + window->setSkipTaskbar(true); + QVERIFY(window->skipTaskbar()); + QCOMPARE(verifyProperty(QStringLiteral("skipTaskbar")), true); + + QVERIFY(!window->skipPager()); + window->setSkipPager(true); + QVERIFY(window->skipPager()); + QCOMPARE(verifyProperty(QStringLiteral("skipPager")), true); + + QVERIFY(!window->skipSwitcher()); + window->setSkipSwitcher(true); + QVERIFY(window->skipSwitcher()); + QCOMPARE(verifyProperty(QStringLiteral("skipSwitcher")), true); + + QVERIFY(!window->isShade()); + window->setShade(ShadeNormal); + QVERIFY(window->isShade()); + QCOMPARE(verifyProperty(QStringLiteral("shaded")), true); + window->setShade(ShadeNone); + QVERIFY(!window->isShade()); + + QVERIFY(!window->noBorder()); + window->setNoBorder(true); + QVERIFY(window->noBorder()); + QCOMPARE(verifyProperty(QStringLiteral("noBorder")), true); + window->setNoBorder(false); + QVERIFY(!window->noBorder()); + + QVERIFY(!window->isFullScreen()); + window->setFullScreen(true); + QVERIFY(window->isFullScreen()); + QVERIFY(window->clientSize() != windowGeometry.size()); + QCOMPARE(verifyProperty(QStringLiteral("fullscreen")), true); + reply = getWindowInfo(window->internalId()); + reply.waitForFinished(); + QCOMPARE(reply.value().value(QStringLiteral("width")).toInt(), window->width()); + QCOMPARE(reply.value().value(QStringLiteral("height")).toInt(), window->height()); + window->setFullScreen(false); + QVERIFY(!window->isFullScreen()); + + // maximize + window->setMaximize(true, false); + QCOMPARE(verifyProperty(QStringLiteral("maximizeVertical")), true); + QCOMPARE(verifyProperty(QStringLiteral("maximizeHorizontal")), false); + window->setMaximize(false, true); + QCOMPARE(verifyProperty(QStringLiteral("maximizeVertical")), false); + QCOMPARE(verifyProperty(QStringLiteral("maximizeHorizontal")), true); + + const auto id = window->internalId(); + // destroy the window + xcb_unmap_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + QVERIFY(windowClosedSpy.wait()); + xcb_destroy_window(c.get(), windowId); + c.reset(); + + reply = getWindowInfo(id); + reply.waitForFinished(); + QVERIFY(reply.value().empty()); +} + +WAYLANDTEST_MAIN(TestDbusInterface) +#include "dbus_interface_test.moc" diff --git a/autotests/integration/debug_console_test.cpp b/autotests/integration/debug_console_test.cpp new file mode 100644 index 0000000..1ebc299 --- /dev/null +++ b/autotests/integration/debug_console_test.cpp @@ -0,0 +1,497 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "debug_console.h" +#include "internalwindow.h" +#include "utils/xcbutils.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include +#include +#include +#include + +#include +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_debug_console-0"); + +class DebugConsoleTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void cleanup(); + void topLevelTest_data(); + void topLevelTest(); + void testX11Window(); + void testX11Unmanaged(); + void testWaylandClient(); + void testInternalWindow(); + void testClosingDebugConsole(); +}; + +void DebugConsoleTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); + setenv("QT_QPA_PLATFORM", "wayland", true); +} + +void DebugConsoleTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void DebugConsoleTest::topLevelTest_data() +{ + QTest::addColumn("row"); + QTest::addColumn("column"); + QTest::addColumn("expectedValid"); + + // this tests various combinations of row/column on the top level whether they are valid + // valid are rows 0-4 with column 0, everything else is invalid + QTest::newRow("0/0") << 0 << 0 << true; + QTest::newRow("0/1") << 0 << 1 << false; + QTest::newRow("0/3") << 0 << 3 << false; + QTest::newRow("1/0") << 1 << 0 << true; + QTest::newRow("1/1") << 1 << 1 << false; + QTest::newRow("1/3") << 1 << 3 << false; + QTest::newRow("2/0") << 2 << 0 << true; + QTest::newRow("3/0") << 3 << 0 << true; + QTest::newRow("4/0") << 4 << 0 << false; + QTest::newRow("100/0") << 4 << 0 << false; +} + +void DebugConsoleTest::topLevelTest() +{ + DebugConsoleModel model; + QCOMPARE(model.rowCount(QModelIndex()), 4); + QCOMPARE(model.columnCount(QModelIndex()), 2); + QFETCH(int, row); + QFETCH(int, column); + const QModelIndex index = model.index(row, column, QModelIndex()); + QTEST(index.isValid(), "expectedValid"); + if (index.isValid()) { + QVERIFY(!model.parent(index).isValid()); + QVERIFY(model.data(index, Qt::DisplayRole).isValid()); + QCOMPARE(model.data(index, Qt::DisplayRole).userType(), int(QMetaType::QString)); + for (int i = Qt::DecorationRole; i <= Qt::UserRole; i++) { + QVERIFY(!model.data(index, i).isValid()); + } + } +} + +void DebugConsoleTest::testX11Window() +{ + DebugConsoleModel model; + QModelIndex x11TopLevelIndex = model.index(0, 0, QModelIndex()); + QVERIFY(x11TopLevelIndex.isValid()); + // we don't have any windows yet + QCOMPARE(model.rowCount(x11TopLevelIndex), 0); + QVERIFY(!model.hasChildren(x11TopLevelIndex)); + // child index must be invalid + QVERIFY(!model.index(0, 0, x11TopLevelIndex).isValid()); + QVERIFY(!model.index(0, 1, x11TopLevelIndex).isValid()); + QVERIFY(!model.index(0, 2, x11TopLevelIndex).isValid()); + QVERIFY(!model.index(1, 0, x11TopLevelIndex).isValid()); + + // start glxgears, to get a window, which should be added to the model + QSignalSpy rowsInsertedSpy(&model, &QAbstractItemModel::rowsInserted); + + QProcess glxgears; + glxgears.setProgram(QStringLiteral("glxgears")); + glxgears.start(); + QVERIFY(glxgears.waitForStarted()); + + QVERIFY(rowsInsertedSpy.wait()); + QCOMPARE(rowsInsertedSpy.count(), 1); + QVERIFY(model.hasChildren(x11TopLevelIndex)); + QCOMPARE(model.rowCount(x11TopLevelIndex), 1); + QCOMPARE(rowsInsertedSpy.first().at(0).value(), x11TopLevelIndex); + QCOMPARE(rowsInsertedSpy.first().at(1).value(), 0); + QCOMPARE(rowsInsertedSpy.first().at(2).value(), 0); + + QModelIndex windowIndex = model.index(0, 0, x11TopLevelIndex); + QVERIFY(windowIndex.isValid()); + QCOMPARE(model.parent(windowIndex), x11TopLevelIndex); + QVERIFY(model.hasChildren(windowIndex)); + QVERIFY(model.rowCount(windowIndex) != 0); + QCOMPARE(model.columnCount(windowIndex), 2); + // other indexes are still invalid + QVERIFY(!model.index(0, 1, x11TopLevelIndex).isValid()); + QVERIFY(!model.index(0, 2, x11TopLevelIndex).isValid()); + QVERIFY(!model.index(1, 0, x11TopLevelIndex).isValid()); + + // the windowIndex has children and those are properties + for (int i = 0; i < model.rowCount(windowIndex); i++) { + const QModelIndex propNameIndex = model.index(i, 0, windowIndex); + QVERIFY(propNameIndex.isValid()); + QCOMPARE(model.parent(propNameIndex), windowIndex); + QVERIFY(!model.hasChildren(propNameIndex)); + QVERIFY(!model.index(0, 0, propNameIndex).isValid()); + QVERIFY(model.data(propNameIndex, Qt::DisplayRole).isValid()); + QCOMPARE(model.data(propNameIndex, Qt::DisplayRole).userType(), int(QMetaType::QString)); + + // and the value + const QModelIndex propValueIndex = model.index(i, 1, windowIndex); + QVERIFY(propValueIndex.isValid()); + QCOMPARE(model.parent(propValueIndex), windowIndex); + QVERIFY(!model.index(0, 0, propValueIndex).isValid()); + QVERIFY(!model.hasChildren(propValueIndex)); + // TODO: how to test whether the values actually work? + + // and on third column we should not get an index any more + QVERIFY(!model.index(i, 2, windowIndex).isValid()); + } + // row after count should be invalid + QVERIFY(!model.index(model.rowCount(windowIndex), 0, windowIndex).isValid()); + + // creating a second model should be initialized directly with the X11 child + DebugConsoleModel model2; + QVERIFY(model2.hasChildren(model2.index(0, 0, QModelIndex()))); + + // now close the window again, it should be removed from the model + QSignalSpy rowsRemovedSpy(&model, &QAbstractItemModel::rowsRemoved); + + glxgears.terminate(); + QVERIFY(glxgears.waitForFinished()); + + QVERIFY(rowsRemovedSpy.wait()); + QCOMPARE(rowsRemovedSpy.count(), 1); + QCOMPARE(rowsRemovedSpy.first().first().value(), x11TopLevelIndex); + QCOMPARE(rowsRemovedSpy.first().at(1).value(), 0); + QCOMPARE(rowsRemovedSpy.first().at(2).value(), 0); + + // the child should be gone again + QVERIFY(!model.hasChildren(x11TopLevelIndex)); + QVERIFY(!model2.hasChildren(model2.index(0, 0, QModelIndex()))); +} + +void DebugConsoleTest::testX11Unmanaged() +{ + DebugConsoleModel model; + QModelIndex unmanagedTopLevelIndex = model.index(1, 0, QModelIndex()); + QVERIFY(unmanagedTopLevelIndex.isValid()); + // we don't have any windows yet + QCOMPARE(model.rowCount(unmanagedTopLevelIndex), 0); + QVERIFY(!model.hasChildren(unmanagedTopLevelIndex)); + // child index must be invalid + QVERIFY(!model.index(0, 0, unmanagedTopLevelIndex).isValid()); + QVERIFY(!model.index(0, 1, unmanagedTopLevelIndex).isValid()); + QVERIFY(!model.index(0, 2, unmanagedTopLevelIndex).isValid()); + QVERIFY(!model.index(1, 0, unmanagedTopLevelIndex).isValid()); + + // we need to create an unmanaged window + QSignalSpy rowsInsertedSpy(&model, &QAbstractItemModel::rowsInserted); + + // let's create an override redirect window + const uint32_t values[] = {true}; + Xcb::Window window(QRect(0, 0, 10, 10), XCB_CW_OVERRIDE_REDIRECT, values); + window.map(); + + QVERIFY(rowsInsertedSpy.wait()); + QCOMPARE(rowsInsertedSpy.count(), 1); + QVERIFY(model.hasChildren(unmanagedTopLevelIndex)); + QCOMPARE(model.rowCount(unmanagedTopLevelIndex), 1); + QCOMPARE(rowsInsertedSpy.first().at(0).value(), unmanagedTopLevelIndex); + QCOMPARE(rowsInsertedSpy.first().at(1).value(), 0); + QCOMPARE(rowsInsertedSpy.first().at(2).value(), 0); + + QModelIndex windowIndex = model.index(0, 0, unmanagedTopLevelIndex); + QVERIFY(windowIndex.isValid()); + QCOMPARE(model.parent(windowIndex), unmanagedTopLevelIndex); + QVERIFY(model.hasChildren(windowIndex)); + QVERIFY(model.rowCount(windowIndex) != 0); + QCOMPARE(model.columnCount(windowIndex), 2); + // other indexes are still invalid + QVERIFY(!model.index(0, 1, unmanagedTopLevelIndex).isValid()); + QVERIFY(!model.index(0, 2, unmanagedTopLevelIndex).isValid()); + QVERIFY(!model.index(1, 0, unmanagedTopLevelIndex).isValid()); + + QCOMPARE(model.data(windowIndex, Qt::DisplayRole).toString(), QStringLiteral("0x%1").arg(window, 0, 16)); + + // the windowIndex has children and those are properties + for (int i = 0; i < model.rowCount(windowIndex); i++) { + const QModelIndex propNameIndex = model.index(i, 0, windowIndex); + QVERIFY(propNameIndex.isValid()); + QCOMPARE(model.parent(propNameIndex), windowIndex); + QVERIFY(!model.hasChildren(propNameIndex)); + QVERIFY(!model.index(0, 0, propNameIndex).isValid()); + QVERIFY(model.data(propNameIndex, Qt::DisplayRole).isValid()); + QCOMPARE(model.data(propNameIndex, Qt::DisplayRole).userType(), int(QMetaType::QString)); + + // and the value + const QModelIndex propValueIndex = model.index(i, 1, windowIndex); + QVERIFY(propValueIndex.isValid()); + QCOMPARE(model.parent(propValueIndex), windowIndex); + QVERIFY(!model.index(0, 0, propValueIndex).isValid()); + QVERIFY(!model.hasChildren(propValueIndex)); + // TODO: how to test whether the values actually work? + + // and on third column we should not get an index any more + QVERIFY(!model.index(i, 2, windowIndex).isValid()); + } + // row after count should be invalid + QVERIFY(!model.index(model.rowCount(windowIndex), 0, windowIndex).isValid()); + + // creating a second model should be initialized directly with the X11 child + DebugConsoleModel model2; + QVERIFY(model2.hasChildren(model2.index(1, 0, QModelIndex()))); + + // now close the window again, it should be removed from the model + QSignalSpy rowsRemovedSpy(&model, &QAbstractItemModel::rowsRemoved); + + window.unmap(); + + QVERIFY(rowsRemovedSpy.wait()); + QCOMPARE(rowsRemovedSpy.count(), 1); + QCOMPARE(rowsRemovedSpy.first().first().value(), unmanagedTopLevelIndex); + QCOMPARE(rowsRemovedSpy.first().at(1).value(), 0); + QCOMPARE(rowsRemovedSpy.first().at(2).value(), 0); + + // the child should be gone again + QVERIFY(!model.hasChildren(unmanagedTopLevelIndex)); + QVERIFY(!model2.hasChildren(model2.index(1, 0, QModelIndex()))); +} + +void DebugConsoleTest::testWaylandClient() +{ + DebugConsoleModel model; + QModelIndex waylandTopLevelIndex = model.index(2, 0, QModelIndex()); + QVERIFY(waylandTopLevelIndex.isValid()); + + // we don't have any windows yet + QCOMPARE(model.rowCount(waylandTopLevelIndex), 0); + QVERIFY(!model.hasChildren(waylandTopLevelIndex)); + // child index must be invalid + QVERIFY(!model.index(0, 0, waylandTopLevelIndex).isValid()); + QVERIFY(!model.index(0, 1, waylandTopLevelIndex).isValid()); + QVERIFY(!model.index(0, 2, waylandTopLevelIndex).isValid()); + QVERIFY(!model.index(1, 0, waylandTopLevelIndex).isValid()); + + // we need to create a wayland window + QSignalSpy rowsInsertedSpy(&model, &QAbstractItemModel::rowsInserted); + + // create our connection + QVERIFY(Test::setupWaylandConnection()); + + // create the Surface and ShellSurface + using namespace KWayland::Client; + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface->isValid()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + Test::render(surface.get(), QSize(10, 10), Qt::red); + + // now we have the window, it should be added to our model + QVERIFY(rowsInsertedSpy.wait()); + QCOMPARE(rowsInsertedSpy.count(), 1); + + QVERIFY(model.hasChildren(waylandTopLevelIndex)); + QCOMPARE(model.rowCount(waylandTopLevelIndex), 1); + QCOMPARE(rowsInsertedSpy.first().at(0).value(), waylandTopLevelIndex); + QCOMPARE(rowsInsertedSpy.first().at(1).value(), 0); + QCOMPARE(rowsInsertedSpy.first().at(2).value(), 0); + + QModelIndex windowIndex = model.index(0, 0, waylandTopLevelIndex); + QVERIFY(windowIndex.isValid()); + QCOMPARE(model.parent(windowIndex), waylandTopLevelIndex); + QVERIFY(model.hasChildren(windowIndex)); + QVERIFY(model.rowCount(windowIndex) != 0); + QCOMPARE(model.columnCount(windowIndex), 2); + // other indexes are still invalid + QVERIFY(!model.index(0, 1, waylandTopLevelIndex).isValid()); + QVERIFY(!model.index(0, 2, waylandTopLevelIndex).isValid()); + QVERIFY(!model.index(1, 0, waylandTopLevelIndex).isValid()); + + // the windowIndex has children and those are properties + for (int i = 0; i < model.rowCount(windowIndex); i++) { + const QModelIndex propNameIndex = model.index(i, 0, windowIndex); + QVERIFY(propNameIndex.isValid()); + QCOMPARE(model.parent(propNameIndex), windowIndex); + QVERIFY(!model.hasChildren(propNameIndex)); + QVERIFY(!model.index(0, 0, propNameIndex).isValid()); + QVERIFY(model.data(propNameIndex, Qt::DisplayRole).isValid()); + QCOMPARE(model.data(propNameIndex, Qt::DisplayRole).userType(), int(QMetaType::QString)); + + // and the value + const QModelIndex propValueIndex = model.index(i, 1, windowIndex); + QVERIFY(propValueIndex.isValid()); + QCOMPARE(model.parent(propValueIndex), windowIndex); + QVERIFY(!model.index(0, 0, propValueIndex).isValid()); + QVERIFY(!model.hasChildren(propValueIndex)); + // TODO: how to test whether the values actually work? + + // and on third column we should not get an index any more + QVERIFY(!model.index(i, 2, windowIndex).isValid()); + } + // row after count should be invalid + QVERIFY(!model.index(model.rowCount(windowIndex), 0, windowIndex).isValid()); + + // creating a second model should be initialized directly with the X11 child + DebugConsoleModel model2; + QVERIFY(model2.hasChildren(model2.index(2, 0, QModelIndex()))); + + // now close the window again, it should be removed from the model + QSignalSpy rowsRemovedSpy(&model, &QAbstractItemModel::rowsRemoved); + + surface->attachBuffer(Buffer::Ptr()); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(rowsRemovedSpy.wait()); + + QCOMPARE(rowsRemovedSpy.count(), 1); + QCOMPARE(rowsRemovedSpy.first().first().value(), waylandTopLevelIndex); + QCOMPARE(rowsRemovedSpy.first().at(1).value(), 0); + QCOMPARE(rowsRemovedSpy.first().at(2).value(), 0); + + // the child should be gone again + QVERIFY(!model.hasChildren(waylandTopLevelIndex)); + QVERIFY(!model2.hasChildren(model2.index(2, 0, QModelIndex()))); +} + +class HelperWindow : public QRasterWindow +{ + Q_OBJECT +public: + HelperWindow() + : QRasterWindow(nullptr) + { + } + ~HelperWindow() override = default; + +Q_SIGNALS: + void entered(); + void left(); + void mouseMoved(const QPoint &global); + void mousePressed(); + void mouseReleased(); + void wheel(); + void keyPressed(); + void keyReleased(); + +protected: + void paintEvent(QPaintEvent *event) override + { + Q_UNUSED(event) + QPainter p(this); + p.fillRect(0, 0, width(), height(), Qt::red); + } +}; + +void DebugConsoleTest::testInternalWindow() +{ + DebugConsoleModel model; + QModelIndex internalTopLevelIndex = model.index(3, 0, QModelIndex()); + QVERIFY(internalTopLevelIndex.isValid()); + + // there might already be some internal windows, so we cannot reliable test whether there are children + // given that we just test whether adding a window works. + + QSignalSpy rowsInsertedSpy(&model, &QAbstractItemModel::rowsInserted); + + std::unique_ptr w(new HelperWindow); + w->setGeometry(0, 0, 100, 100); + w->show(); + + QTRY_COMPARE(rowsInsertedSpy.count(), 1); + QCOMPARE(rowsInsertedSpy.first().first().value(), internalTopLevelIndex); + + QModelIndex windowIndex = model.index(rowsInsertedSpy.first().last().toInt(), 0, internalTopLevelIndex); + QVERIFY(windowIndex.isValid()); + QCOMPARE(model.parent(windowIndex), internalTopLevelIndex); + QVERIFY(model.hasChildren(windowIndex)); + QVERIFY(model.rowCount(windowIndex) != 0); + QCOMPARE(model.columnCount(windowIndex), 2); + // other indexes are still invalid + QVERIFY(!model.index(rowsInsertedSpy.first().last().toInt(), 1, internalTopLevelIndex).isValid()); + QVERIFY(!model.index(rowsInsertedSpy.first().last().toInt(), 2, internalTopLevelIndex).isValid()); + QVERIFY(!model.index(rowsInsertedSpy.first().last().toInt() + 1, 0, internalTopLevelIndex).isValid()); + + // the wayland shell client top level should not have gained this window + QVERIFY(!model.hasChildren(model.index(2, 0, QModelIndex()))); + + // the windowIndex has children and those are properties + for (int i = 0; i < model.rowCount(windowIndex); i++) { + const QModelIndex propNameIndex = model.index(i, 0, windowIndex); + QVERIFY(propNameIndex.isValid()); + QCOMPARE(model.parent(propNameIndex), windowIndex); + QVERIFY(!model.hasChildren(propNameIndex)); + QVERIFY(!model.index(0, 0, propNameIndex).isValid()); + QVERIFY(model.data(propNameIndex, Qt::DisplayRole).isValid()); + QCOMPARE(model.data(propNameIndex, Qt::DisplayRole).userType(), int(QMetaType::QString)); + + // and the value + const QModelIndex propValueIndex = model.index(i, 1, windowIndex); + QVERIFY(propValueIndex.isValid()); + QCOMPARE(model.parent(propValueIndex), windowIndex); + QVERIFY(!model.index(0, 0, propValueIndex).isValid()); + QVERIFY(!model.hasChildren(propValueIndex)); + // TODO: how to test whether the values actually work? + + // and on third column we should not get an index any more + QVERIFY(!model.index(i, 2, windowIndex).isValid()); + } + // row after count should be invalid + QVERIFY(!model.index(model.rowCount(windowIndex), 0, windowIndex).isValid()); + + // now close the window again, it should be removed from the model + QSignalSpy rowsRemovedSpy(&model, &QAbstractItemModel::rowsRemoved); + + w->hide(); + w.reset(); + + QTRY_COMPARE(rowsRemovedSpy.count(), 1); + QCOMPARE(rowsRemovedSpy.first().first().value(), internalTopLevelIndex); +} + +void DebugConsoleTest::testClosingDebugConsole() +{ + // this test verifies that the DebugConsole gets destroyed when closing the window + // BUG: 369858 + + DebugConsole *console = new DebugConsole; + QSignalSpy destroyedSpy(console, &QObject::destroyed); + + QSignalSpy windowAddedSpy(workspace(), &Workspace::internalWindowAdded); + console->show(); + QCOMPARE(console->windowHandle()->isVisible(), true); + QTRY_COMPARE(windowAddedSpy.count(), 1); + InternalWindow *window = windowAddedSpy.first().first().value(); + QVERIFY(window->isInternal()); + QCOMPARE(window->handle(), console->windowHandle()); + QVERIFY(window->isDecorated()); + QCOMPARE(window->isMinimizable(), false); + window->closeWindow(); + QVERIFY(destroyedSpy.wait()); +} + +} + +WAYLANDTEST_MAIN(KWin::DebugConsoleTest) +#include "debug_console_test.moc" diff --git a/autotests/integration/decoration_input_test.cpp b/autotests/integration/decoration_input_test.cpp new file mode 100644 index 0000000..be24008 --- /dev/null +++ b/autotests/integration/decoration_input_test.cpp @@ -0,0 +1,789 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "internalwindow.h" +#include "pointer_input.h" +#include "touch_input.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" +#include + +#include "decorations/decoratedclient.h" +#include "decorations/decorationbridge.h" +#include "decorations/settings.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +Q_DECLARE_METATYPE(Qt::WindowFrameSection) + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_decoration_input-0"); + +class DecorationInputTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testAxis_data(); + void testAxis(); + void testDoubleClick_data(); + void testDoubleClick(); + void testDoubleTap_data(); + void testDoubleTap(); + void testHover(); + void testPressToMove_data(); + void testPressToMove(); + void testTapToMove_data(); + void testTapToMove(); + void testResizeOutsideWindow_data(); + void testResizeOutsideWindow(); + void testModifierClickUnrestrictedMove_data(); + void testModifierClickUnrestrictedMove(); + void testModifierScrollOpacity_data(); + void testModifierScrollOpacity(); + void testTouchEvents(); + void testTooltipDoesntEatKeyEvents(); + +private: + std::pair> showWindow(); +}; + +#define MOTION(target) Test::pointerMotion(target, timestamp++) + +#define PRESS Test::pointerButtonPressed(BTN_LEFT, timestamp++) + +#define RELEASE Test::pointerButtonReleased(BTN_LEFT, timestamp++) + +std::pair> DecorationInputTest::showWindow() +{ + using namespace KWayland::Client; +#define VERIFY(statement) \ + if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__)) \ + return {nullptr, nullptr}; +#define COMPARE(actual, expected) \ + if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)) \ + return {nullptr, nullptr}; + + std::unique_ptr surface{Test::createSurface()}; + VERIFY(surface.get()); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly, surface.get()); + VERIFY(shellSurface); + Test::XdgToplevelDecorationV1 *decoration = Test::createXdgToplevelDecorationV1(shellSurface, shellSurface); + VERIFY(decoration); + + QSignalSpy decorationConfigureRequestedSpy(decoration, &Test::XdgToplevelDecorationV1::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + + decoration->set_mode(Test::XdgToplevelDecorationV1::mode_server_side); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + VERIFY(surfaceConfigureRequestedSpy.wait()); + COMPARE(decorationConfigureRequestedSpy.last().at(0).value(), Test::XdgToplevelDecorationV1::mode_server_side); + + // let's render + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(500, 50), Qt::blue); + VERIFY(window); + COMPARE(workspace()->activeWindow(), window); + +#undef VERIFY +#undef COMPARE + + return {window, std::move(surface)}; +} + +void DecorationInputTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + // change some options + KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + config->group(QStringLiteral("MouseBindings")).writeEntry("CommandTitlebarWheel", QStringLiteral("above/below")); + config->group(QStringLiteral("Windows")).writeEntry("TitlebarDoubleClickCommand", QStringLiteral("OnAllDesktops")); + config->group(QStringLiteral("Desktops")).writeEntry("Number", 2); + config->sync(); + + kwinApp()->setConfig(config); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); + setenv("QT_QPA_PLATFORM", "wayland", true); +} + +void DecorationInputTest::init() +{ + using namespace KWayland::Client; + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::XdgDecorationV1)); + QVERIFY(Test::waitForWaylandPointer()); + + workspace()->setActiveOutput(QPoint(640, 512)); + Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void DecorationInputTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void DecorationInputTest::testAxis_data() +{ + QTest::addColumn("decoPoint"); + QTest::addColumn("expectedSection"); + + QTest::newRow("topLeft") << QPoint(0, 0) << Qt::TopLeftSection; + QTest::newRow("top") << QPoint(250, 0) << Qt::TopSection; + QTest::newRow("topRight") << QPoint(499, 0) << Qt::TopRightSection; +} + +void DecorationInputTest::testAxis() +{ + const auto [window, surface] = showWindow(); + QVERIFY(window); + QVERIFY(window->isDecorated()); + QVERIFY(!window->noBorder()); + QCOMPARE(window->titlebarPosition(), Qt::TopEdge); + QVERIFY(!window->keepAbove()); + QVERIFY(!window->keepBelow()); + + quint32 timestamp = 1; + MOTION(QPoint(window->frameGeometry().center().x(), window->clientPos().y() / 2)); + QVERIFY(input()->pointer()->decoration()); + QCOMPARE(input()->pointer()->decoration()->decoration()->sectionUnderMouse(), Qt::TitleBarArea); + + // TODO: mouse wheel direction looks wrong to me + // simulate wheel + Test::pointerAxisVertical(5.0, timestamp++); + QVERIFY(window->keepBelow()); + QVERIFY(!window->keepAbove()); + Test::pointerAxisVertical(-5.0, timestamp++); + QVERIFY(!window->keepBelow()); + QVERIFY(!window->keepAbove()); + Test::pointerAxisVertical(-5.0, timestamp++); + QVERIFY(!window->keepBelow()); + QVERIFY(window->keepAbove()); + + // test top most deco pixel, BUG: 362860 + window->move(QPoint(0, 0)); + QFETCH(QPoint, decoPoint); + MOTION(decoPoint); + QVERIFY(input()->pointer()->decoration()); + QCOMPARE(input()->pointer()->decoration()->window(), window); + QTEST(input()->pointer()->decoration()->decoration()->sectionUnderMouse(), "expectedSection"); + Test::pointerAxisVertical(5.0, timestamp++); + QVERIFY(!window->keepBelow()); + QVERIFY(!window->keepAbove()); +} + +void DecorationInputTest::testDoubleClick_data() +{ + QTest::addColumn("decoPoint"); + QTest::addColumn("expectedSection"); + + QTest::newRow("topLeft") << QPoint(0, 0) << Qt::TopLeftSection; + QTest::newRow("top") << QPoint(250, 0) << Qt::TopSection; + QTest::newRow("topRight") << QPoint(499, 0) << Qt::TopRightSection; +} + +void KWin::DecorationInputTest::testDoubleClick() +{ + const auto [window, surface] = showWindow(); + QVERIFY(window); + QVERIFY(window->isDecorated()); + QVERIFY(!window->noBorder()); + QVERIFY(!window->isOnAllDesktops()); + quint32 timestamp = 1; + MOTION(QPoint(window->frameGeometry().center().x(), window->clientPos().y() / 2)); + + // double click + PRESS; + RELEASE; + PRESS; + RELEASE; + QVERIFY(window->isOnAllDesktops()); + // double click again + PRESS; + RELEASE; + QVERIFY(window->isOnAllDesktops()); + PRESS; + RELEASE; + QVERIFY(!window->isOnAllDesktops()); + + // test top most deco pixel, BUG: 362860 + window->move(QPoint(0, 0)); + QFETCH(QPoint, decoPoint); + MOTION(decoPoint); + QVERIFY(input()->pointer()->decoration()); + QCOMPARE(input()->pointer()->decoration()->window(), window); + QTEST(input()->pointer()->decoration()->decoration()->sectionUnderMouse(), "expectedSection"); + // double click + PRESS; + RELEASE; + QVERIFY(!window->isOnAllDesktops()); + PRESS; + RELEASE; + QVERIFY(window->isOnAllDesktops()); +} + +void DecorationInputTest::testDoubleTap_data() +{ + QTest::addColumn("decoPoint"); + QTest::addColumn("expectedSection"); + + QTest::newRow("topLeft") << QPoint(10, 10) << Qt::TopLeftSection; + QTest::newRow("top") << QPoint(260, 10) << Qt::TopSection; + QTest::newRow("topRight") << QPoint(509, 10) << Qt::TopRightSection; +} + +void KWin::DecorationInputTest::testDoubleTap() +{ + const auto [window, surface] = showWindow(); + QVERIFY(window); + QVERIFY(window->isDecorated()); + QVERIFY(!window->noBorder()); + QVERIFY(!window->isOnAllDesktops()); + quint32 timestamp = 1; + const QPoint tapPoint(window->frameGeometry().center().x(), window->clientPos().y() / 2); + + // double tap + Test::touchDown(0, tapPoint, timestamp++); + Test::touchUp(0, timestamp++); + Test::touchDown(0, tapPoint, timestamp++); + Test::touchUp(0, timestamp++); + QVERIFY(window->isOnAllDesktops()); + // double tap again + Test::touchDown(0, tapPoint, timestamp++); + Test::touchUp(0, timestamp++); + QVERIFY(window->isOnAllDesktops()); + Test::touchDown(0, tapPoint, timestamp++); + Test::touchUp(0, timestamp++); + QVERIFY(!window->isOnAllDesktops()); + + // test top most deco pixel, BUG: 362860 + // + // Not directly at (0, 0), otherwise ScreenEdgeInputFilter catches + // event before DecorationEventFilter. + window->move(QPoint(10, 10)); + QFETCH(QPoint, decoPoint); + // double click + Test::touchDown(0, decoPoint, timestamp++); + QVERIFY(input()->touch()->decoration()); + QCOMPARE(input()->touch()->decoration()->window(), window); + QTEST(input()->touch()->decoration()->decoration()->sectionUnderMouse(), "expectedSection"); + Test::touchUp(0, timestamp++); + QVERIFY(!window->isOnAllDesktops()); + Test::touchDown(0, decoPoint, timestamp++); + Test::touchUp(0, timestamp++); + QVERIFY(window->isOnAllDesktops()); +} + +void DecorationInputTest::testHover() +{ + const auto [window, surface] = showWindow(); + QVERIFY(window); + QVERIFY(window->isDecorated()); + QVERIFY(!window->noBorder()); + + // our left border is moved out of the visible area, so move the window to a better place + window->move(QPoint(20, 0)); + + quint32 timestamp = 1; + MOTION(QPoint(window->frameGeometry().center().x(), window->clientPos().y() / 2)); + QCOMPARE(window->cursor(), CursorShape(Qt::ArrowCursor)); + + // There is a mismatch of the cursor key positions between windows + // with and without borders (with borders one can move inside a bit and still + // be on an edge, without not). We should make this consistent in KWin's core. + // + // TODO: Test input position with different border sizes. + // TODO: We should test with the fake decoration to have a fixed test environment. + const bool hasBorders = Workspace::self()->decorationBridge()->settings()->borderSize() != KDecoration2::BorderSize::None; + auto deviation = [hasBorders] { + return hasBorders ? -1 : 0; + }; + + MOTION(QPoint(window->frameGeometry().x(), 0)); + QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeNorthWest)); + MOTION(QPoint(window->frameGeometry().x() + window->frameGeometry().width() / 2, 0)); + QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeNorth)); + MOTION(QPoint(window->frameGeometry().x() + window->frameGeometry().width() - 1, 0)); + QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeNorthEast)); + MOTION(QPoint(window->frameGeometry().x() + window->frameGeometry().width() + deviation(), window->height() / 2)); + QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeEast)); + MOTION(QPoint(window->frameGeometry().x() + window->frameGeometry().width() + deviation(), window->height() - 1)); + QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeSouthEast)); + MOTION(QPoint(window->frameGeometry().x() + window->frameGeometry().width() / 2, window->height() + deviation())); + QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeSouth)); + MOTION(QPoint(window->frameGeometry().x(), window->height() + deviation())); + QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeSouthWest)); + MOTION(QPoint(window->frameGeometry().x() - 1, window->height() / 2)); + QCOMPARE(window->cursor(), CursorShape(KWin::ExtendedCursor::SizeWest)); + + MOTION(window->frameGeometry().center()); + QEXPECT_FAIL("", "Cursor not set back on leave", Continue); + QCOMPARE(window->cursor(), CursorShape(Qt::ArrowCursor)); +} + +void DecorationInputTest::testPressToMove_data() +{ + QTest::addColumn("offset"); + QTest::addColumn("offset2"); + QTest::addColumn("offset3"); + + QTest::newRow("To right") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0); + QTest::newRow("To left") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0); + QTest::newRow("To bottom") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30); + QTest::newRow("To top") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30); +} + +void DecorationInputTest::testPressToMove() +{ + const auto [window, surface] = showWindow(); + QVERIFY(window); + QVERIFY(window->isDecorated()); + QVERIFY(!window->noBorder()); + window->move(workspace()->activeOutput()->geometry().center() - QPoint(window->width() / 2, window->height() / 2)); + QSignalSpy startMoveResizedSpy(window, &Window::clientStartUserMovedResized); + QSignalSpy clientFinishUserMovedResizedSpy(window, &Window::clientFinishUserMovedResized); + + quint32 timestamp = 1; + MOTION(QPoint(window->frameGeometry().center().x(), window->y() + window->clientPos().y() / 2)); + QCOMPARE(window->cursor(), CursorShape(Qt::ArrowCursor)); + + PRESS; + QVERIFY(!window->isInteractiveMove()); + QFETCH(QPoint, offset); + MOTION(QPoint(window->frameGeometry().center().x(), window->y() + window->clientPos().y() / 2) + offset); + const QPointF oldPos = window->pos(); + QVERIFY(window->isInteractiveMove()); + QCOMPARE(startMoveResizedSpy.count(), 1); + + RELEASE; + QTRY_VERIFY(!window->isInteractiveMove()); + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); + QEXPECT_FAIL("", "Just trigger move doesn't move the window", Continue); + QCOMPARE(window->pos(), oldPos + offset); + + // again + PRESS; + QVERIFY(!window->isInteractiveMove()); + QFETCH(QPoint, offset2); + MOTION(QPoint(window->frameGeometry().center().x(), window->y() + window->clientPos().y() / 2) + offset2); + QVERIFY(window->isInteractiveMove()); + QCOMPARE(startMoveResizedSpy.count(), 2); + QFETCH(QPoint, offset3); + MOTION(QPoint(window->frameGeometry().center().x(), window->y() + window->clientPos().y() / 2) + offset3); + + RELEASE; + QTRY_VERIFY(!window->isInteractiveMove()); + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 2); + // TODO: the offset should also be included + QCOMPARE(window->pos(), oldPos + offset2 + offset3); +} + +void DecorationInputTest::testTapToMove_data() +{ + QTest::addColumn("offset"); + QTest::addColumn("offset2"); + QTest::addColumn("offset3"); + + QTest::newRow("To right") << QPoint(10, 0) << QPoint(20, 0) << QPoint(30, 0); + QTest::newRow("To left") << QPoint(-10, 0) << QPoint(-20, 0) << QPoint(-30, 0); + QTest::newRow("To bottom") << QPoint(0, 10) << QPoint(0, 20) << QPoint(0, 30); + QTest::newRow("To top") << QPoint(0, -10) << QPoint(0, -20) << QPoint(0, -30); +} + +void DecorationInputTest::testTapToMove() +{ + const auto [window, surface] = showWindow(); + QVERIFY(window); + QVERIFY(window->isDecorated()); + QVERIFY(!window->noBorder()); + window->move(workspace()->activeOutput()->geometry().center() - QPoint(window->width() / 2, window->height() / 2)); + QSignalSpy startMoveResizedSpy(window, &Window::clientStartUserMovedResized); + QSignalSpy clientFinishUserMovedResizedSpy(window, &Window::clientFinishUserMovedResized); + + quint32 timestamp = 1; + QPoint p = QPoint(window->frameGeometry().center().x(), window->y() + window->clientPos().y() / 2); + + Test::touchDown(0, p, timestamp++); + QVERIFY(!window->isInteractiveMove()); + QFETCH(QPoint, offset); + QCOMPARE(input()->touch()->decorationPressId(), 0); + Test::touchMotion(0, p + offset, timestamp++); + const QPointF oldPos = window->pos(); + QVERIFY(window->isInteractiveMove()); + QCOMPARE(startMoveResizedSpy.count(), 1); + + Test::touchUp(0, timestamp++); + QTRY_VERIFY(!window->isInteractiveMove()); + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); + QEXPECT_FAIL("", "Just trigger move doesn't move the window", Continue); + QCOMPARE(window->pos(), oldPos + offset); + + // again + Test::touchDown(1, p + offset, timestamp++); + QCOMPARE(input()->touch()->decorationPressId(), 1); + QVERIFY(!window->isInteractiveMove()); + QFETCH(QPoint, offset2); + Test::touchMotion(1, QPoint(window->frameGeometry().center().x(), window->y() + window->clientPos().y() / 2) + offset2, timestamp++); + QVERIFY(window->isInteractiveMove()); + QCOMPARE(startMoveResizedSpy.count(), 2); + QFETCH(QPoint, offset3); + Test::touchMotion(1, QPoint(window->frameGeometry().center().x(), window->y() + window->clientPos().y() / 2) + offset3, timestamp++); + + Test::touchUp(1, timestamp++); + QTRY_VERIFY(!window->isInteractiveMove()); + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 2); + // TODO: the offset should also be included + QCOMPARE(window->pos(), oldPos + offset2 + offset3); +} + +void DecorationInputTest::testResizeOutsideWindow_data() +{ + QTest::addColumn("edge"); + QTest::addColumn("expectedCursor"); + + QTest::newRow("left") << Qt::LeftEdge << Qt::SizeHorCursor; + QTest::newRow("right") << Qt::RightEdge << Qt::SizeHorCursor; + QTest::newRow("bottom") << Qt::BottomEdge << Qt::SizeVerCursor; +} + +void DecorationInputTest::testResizeOutsideWindow() +{ + // this test verifies that one can resize the window outside the decoration with NoSideBorder + + // first adjust config + kwinApp()->config()->group("org.kde.kdecoration2").writeEntry("BorderSize", QStringLiteral("None")); + kwinApp()->config()->sync(); + workspace()->slotReconfigure(); + + // now create window + const auto [window, surface] = showWindow(); + QVERIFY(window); + QVERIFY(window->isDecorated()); + QVERIFY(!window->noBorder()); + window->move(workspace()->activeOutput()->geometry().center() - QPoint(window->width() / 2, window->height() / 2)); + QVERIFY(window->frameGeometry() != window->inputGeometry()); + QVERIFY(window->inputGeometry().contains(window->frameGeometry())); + QSignalSpy startMoveResizedSpy(window, &Window::clientStartUserMovedResized); + + // go to border + quint32 timestamp = 1; + QFETCH(Qt::Edge, edge); + switch (edge) { + case Qt::LeftEdge: + MOTION(QPoint(window->frameGeometry().x() - 1, window->frameGeometry().center().y())); + break; + case Qt::RightEdge: + MOTION(QPoint(window->frameGeometry().x() + window->frameGeometry().width() + 1, window->frameGeometry().center().y())); + break; + case Qt::BottomEdge: + MOTION(QPoint(window->frameGeometry().center().x(), window->frameGeometry().y() + window->frameGeometry().height() + 1)); + break; + default: + break; + } + QVERIFY(!window->frameGeometry().contains(KWin::Cursors::self()->mouse()->pos())); + + // pressing should trigger resize + PRESS; + QVERIFY(!window->isInteractiveResize()); + QVERIFY(startMoveResizedSpy.wait()); + QVERIFY(window->isInteractiveResize()); + + RELEASE; + QVERIFY(!window->isInteractiveResize()); +} + +void DecorationInputTest::testModifierClickUnrestrictedMove_data() +{ + QTest::addColumn("modifierKey"); + QTest::addColumn("mouseButton"); + QTest::addColumn("modKey"); + QTest::addColumn("capsLock"); + + const QString alt = QStringLiteral("Alt"); + const QString meta = QStringLiteral("Meta"); + + QTest::newRow("Left Alt + Left Click") << KEY_LEFTALT << BTN_LEFT << alt << false; + QTest::newRow("Left Alt + Right Click") << KEY_LEFTALT << BTN_RIGHT << alt << false; + QTest::newRow("Left Alt + Middle Click") << KEY_LEFTALT << BTN_MIDDLE << alt << false; + QTest::newRow("Right Alt + Left Click") << KEY_RIGHTALT << BTN_LEFT << alt << false; + QTest::newRow("Right Alt + Right Click") << KEY_RIGHTALT << BTN_RIGHT << alt << false; + QTest::newRow("Right Alt + Middle Click") << KEY_RIGHTALT << BTN_MIDDLE << alt << false; + // now everything with meta + QTest::newRow("Left Meta + Left Click") << KEY_LEFTMETA << BTN_LEFT << meta << false; + QTest::newRow("Left Meta + Right Click") << KEY_LEFTMETA << BTN_RIGHT << meta << false; + QTest::newRow("Left Meta + Middle Click") << KEY_LEFTMETA << BTN_MIDDLE << meta << false; + QTest::newRow("Right Meta + Left Click") << KEY_RIGHTMETA << BTN_LEFT << meta << false; + QTest::newRow("Right Meta + Right Click") << KEY_RIGHTMETA << BTN_RIGHT << meta << false; + QTest::newRow("Right Meta + Middle Click") << KEY_RIGHTMETA << BTN_MIDDLE << meta << false; + + // and with capslock + QTest::newRow("Left Alt + Left Click/CapsLock") << KEY_LEFTALT << BTN_LEFT << alt << true; + QTest::newRow("Left Alt + Right Click/CapsLock") << KEY_LEFTALT << BTN_RIGHT << alt << true; + QTest::newRow("Left Alt + Middle Click/CapsLock") << KEY_LEFTALT << BTN_MIDDLE << alt << true; + QTest::newRow("Right Alt + Left Click/CapsLock") << KEY_RIGHTALT << BTN_LEFT << alt << true; + QTest::newRow("Right Alt + Right Click/CapsLock") << KEY_RIGHTALT << BTN_RIGHT << alt << true; + QTest::newRow("Right Alt + Middle Click/CapsLock") << KEY_RIGHTALT << BTN_MIDDLE << alt << true; + // now everything with meta + QTest::newRow("Left Meta + Left Click/CapsLock") << KEY_LEFTMETA << BTN_LEFT << meta << true; + QTest::newRow("Left Meta + Right Click/CapsLock") << KEY_LEFTMETA << BTN_RIGHT << meta << true; + QTest::newRow("Left Meta + Middle Click/CapsLock") << KEY_LEFTMETA << BTN_MIDDLE << meta << true; + QTest::newRow("Right Meta + Left Click/CapsLock") << KEY_RIGHTMETA << BTN_LEFT << meta << true; + QTest::newRow("Right Meta + Right Click/CapsLock") << KEY_RIGHTMETA << BTN_RIGHT << meta << true; + QTest::newRow("Right Meta + Middle Click/CapsLock") << KEY_RIGHTMETA << BTN_MIDDLE << meta << true; +} + +void DecorationInputTest::testModifierClickUnrestrictedMove() +{ + // this test ensures that Alt+mouse button press triggers unrestricted move + + // first modify the config for this run + QFETCH(QString, modKey); + KConfigGroup group = kwinApp()->config()->group("MouseBindings"); + group.writeEntry("CommandAllKey", modKey); + group.writeEntry("CommandAll1", "Move"); + group.writeEntry("CommandAll2", "Move"); + group.writeEntry("CommandAll3", "Move"); + group.sync(); + workspace()->slotReconfigure(); + QCOMPARE(options->commandAllModifier(), modKey == QStringLiteral("Alt") ? Qt::AltModifier : Qt::MetaModifier); + QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove); + QCOMPARE(options->commandAll2(), Options::MouseUnrestrictedMove); + QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedMove); + + // create a window + const auto [window, surface] = showWindow(); + QVERIFY(window); + QVERIFY(window->isDecorated()); + QVERIFY(!window->noBorder()); + window->move(workspace()->activeOutput()->geometry().center() - QPoint(window->width() / 2, window->height() / 2)); + // move cursor on window + Cursors::self()->mouse()->setPos(QPoint(window->frameGeometry().center().x(), window->y() + window->clientPos().y() / 2)); + + // simulate modifier+click + quint32 timestamp = 1; + QFETCH(bool, capsLock); + if (capsLock) { + Test::keyboardKeyPressed(KEY_CAPSLOCK, timestamp++); + } + QFETCH(int, modifierKey); + QFETCH(int, mouseButton); + Test::keyboardKeyPressed(modifierKey, timestamp++); + QVERIFY(!window->isInteractiveMove()); + Test::pointerButtonPressed(mouseButton, timestamp++); + QVERIFY(window->isInteractiveMove()); + // release modifier should not change it + Test::keyboardKeyReleased(modifierKey, timestamp++); + QVERIFY(window->isInteractiveMove()); + // but releasing the key should end move/resize + Test::pointerButtonReleased(mouseButton, timestamp++); + QVERIFY(!window->isInteractiveMove()); + if (capsLock) { + Test::keyboardKeyReleased(KEY_CAPSLOCK, timestamp++); + } +} + +void DecorationInputTest::testModifierScrollOpacity_data() +{ + QTest::addColumn("modifierKey"); + QTest::addColumn("modKey"); + QTest::addColumn("capsLock"); + + const QString alt = QStringLiteral("Alt"); + const QString meta = QStringLiteral("Meta"); + + QTest::newRow("Left Alt") << KEY_LEFTALT << alt << false; + QTest::newRow("Right Alt") << KEY_RIGHTALT << alt << false; + QTest::newRow("Left Meta") << KEY_LEFTMETA << meta << false; + QTest::newRow("Right Meta") << KEY_RIGHTMETA << meta << false; + QTest::newRow("Left Alt/CapsLock") << KEY_LEFTALT << alt << true; + QTest::newRow("Right Alt/CapsLock") << KEY_RIGHTALT << alt << true; + QTest::newRow("Left Meta/CapsLock") << KEY_LEFTMETA << meta << true; + QTest::newRow("Right Meta/CapsLock") << KEY_RIGHTMETA << meta << true; +} + +void DecorationInputTest::testModifierScrollOpacity() +{ + // this test verifies that mod+wheel performs a window operation + + // first modify the config for this run + QFETCH(QString, modKey); + KConfigGroup group = kwinApp()->config()->group("MouseBindings"); + group.writeEntry("CommandAllKey", modKey); + group.writeEntry("CommandAllWheel", "change opacity"); + group.sync(); + workspace()->slotReconfigure(); + + const auto [window, surface] = showWindow(); + QVERIFY(window); + QVERIFY(window->isDecorated()); + QVERIFY(!window->noBorder()); + window->move(workspace()->activeOutput()->geometry().center() - QPoint(window->width() / 2, window->height() / 2)); + // move cursor on window + Cursors::self()->mouse()->setPos(QPoint(window->frameGeometry().center().x(), window->y() + window->clientPos().y() / 2)); + // set the opacity to 0.5 + window->setOpacity(0.5); + QCOMPARE(window->opacity(), 0.5); + + // simulate modifier+wheel + quint32 timestamp = 1; + QFETCH(bool, capsLock); + if (capsLock) { + Test::keyboardKeyPressed(KEY_CAPSLOCK, timestamp++); + } + QFETCH(int, modifierKey); + Test::keyboardKeyPressed(modifierKey, timestamp++); + Test::pointerAxisVertical(-5, timestamp++); + QCOMPARE(window->opacity(), 0.6); + Test::pointerAxisVertical(5, timestamp++); + QCOMPARE(window->opacity(), 0.5); + Test::keyboardKeyReleased(modifierKey, timestamp++); + if (capsLock) { + Test::keyboardKeyReleased(KEY_CAPSLOCK, timestamp++); + } +} + +class EventHelper : public QObject +{ + Q_OBJECT +public: + EventHelper() + : QObject() + { + } + ~EventHelper() override = default; + + bool eventFilter(QObject *watched, QEvent *event) override + { + Q_UNUSED(watched) + if (event->type() == QEvent::HoverMove) { + Q_EMIT hoverMove(); + } else if (event->type() == QEvent::HoverLeave) { + Q_EMIT hoverLeave(); + } + return false; + } + +Q_SIGNALS: + void hoverMove(); + void hoverLeave(); +}; + +void DecorationInputTest::testTouchEvents() +{ + // this test verifies that the decoration gets a hover leave event on touch release + // see BUG 386231 + const auto [window, surface] = showWindow(); + QVERIFY(window); + QVERIFY(window->isDecorated()); + QVERIFY(!window->noBorder()); + + EventHelper helper; + window->decoration()->installEventFilter(&helper); + QSignalSpy hoverMoveSpy(&helper, &EventHelper::hoverMove); + QSignalSpy hoverLeaveSpy(&helper, &EventHelper::hoverLeave); + + quint32 timestamp = 1; + const QPoint tapPoint(window->frameGeometry().center().x(), window->clientPos().y() / 2); + + QVERIFY(!input()->touch()->decoration()); + Test::touchDown(0, tapPoint, timestamp++); + QVERIFY(input()->touch()->decoration()); + QCOMPARE(input()->touch()->decoration()->decoration(), window->decoration()); + QCOMPARE(hoverMoveSpy.count(), 1); + QCOMPARE(hoverLeaveSpy.count(), 0); + Test::touchUp(0, timestamp++); + QCOMPARE(hoverMoveSpy.count(), 1); + QCOMPARE(hoverLeaveSpy.count(), 1); + + QCOMPARE(window->isInteractiveMove(), false); + + // let's check that a hover motion is sent if the pointer is on deco, when touch release + Cursors::self()->mouse()->setPos(tapPoint); + QCOMPARE(hoverMoveSpy.count(), 2); + Test::touchDown(0, tapPoint, timestamp++); + QCOMPARE(hoverMoveSpy.count(), 3); + QCOMPARE(hoverLeaveSpy.count(), 1); + Test::touchUp(0, timestamp++); + QCOMPARE(hoverMoveSpy.count(), 3); + QCOMPARE(hoverLeaveSpy.count(), 2); +} + +void DecorationInputTest::testTooltipDoesntEatKeyEvents() +{ + // this test verifies that a tooltip on the decoration does not steal key events + // BUG: 393253 + + // first create a keyboard + auto keyboard = Test::waylandSeat()->createKeyboard(Test::waylandSeat()); + QVERIFY(keyboard); + QSignalSpy enteredSpy(keyboard, &KWayland::Client::Keyboard::entered); + + const auto [window, surface] = showWindow(); + QVERIFY(window); + QVERIFY(window->isDecorated()); + QVERIFY(!window->noBorder()); + QVERIFY(enteredSpy.wait()); + + QSignalSpy keyEvent(keyboard, &KWayland::Client::Keyboard::keyChanged); + QVERIFY(keyEvent.isValid()); + + QSignalSpy windowAddedSpy(workspace(), &Workspace::internalWindowAdded); + window->decoratedClient()->requestShowToolTip(QStringLiteral("test")); + // now we should get an internal window + QVERIFY(windowAddedSpy.wait()); + InternalWindow *internal = windowAddedSpy.first().first().value(); + QVERIFY(internal->isInternal()); + QVERIFY(internal->handle()->flags().testFlag(Qt::ToolTip)); + + // now send a key + quint32 timestamp = 0; + Test::keyboardKeyPressed(KEY_A, timestamp++); + QVERIFY(keyEvent.wait()); + Test::keyboardKeyReleased(KEY_A, timestamp++); + QVERIFY(keyEvent.wait()); + + window->decoratedClient()->requestHideToolTip(); + Test::waitForWindowDestroyed(internal); +} + +} + +WAYLANDTEST_MAIN(KWin::DecorationInputTest) +#include "decoration_input_test.moc" diff --git a/autotests/integration/desktop_window_x11_test.cpp b/autotests/integration/desktop_window_x11_test.cpp new file mode 100644 index 0000000..356acb2 --- /dev/null +++ b/autotests/integration/desktop_window_x11_test.cpp @@ -0,0 +1,163 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "deleted.h" +#include "utils/xcbutils.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" +#include "x11window.h" +#include + +#include +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_x11_desktop_window-0"); + +class X11DesktopWindowTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testDesktopWindow(); + +private: +}; + +void X11DesktopWindowTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); + setenv("QT_QPA_PLATFORM", "wayland", true); +} + +void X11DesktopWindowTest::init() +{ + workspace()->setActiveOutput(QPoint(640, 512)); + Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void X11DesktopWindowTest::cleanup() +{ +} + +struct XcbConnectionDeleter +{ + void operator()(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } +}; + +void X11DesktopWindowTest::testDesktopWindow() +{ + // this test creates a desktop window with an RGBA visual and verifies that it's only considered + // as an RGB (opaque) window in KWin + + // create an xcb window + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + + xcb_window_t windowId = xcb_generate_id(c.get()); + const QRect windowGeometry(0, 0, 1280, 1024); + + // helper to find the visual + auto findDepth = [&c]() -> xcb_visualid_t { + // find a visual with 32 depth + const xcb_setup_t *setup = xcb_get_setup(c.get()); + + for (auto screen = xcb_setup_roots_iterator(setup); screen.rem; xcb_screen_next(&screen)) { + for (auto depth = xcb_screen_allowed_depths_iterator(screen.data); depth.rem; xcb_depth_next(&depth)) { + if (depth.data->depth != 32) { + continue; + } + const int len = xcb_depth_visuals_length(depth.data); + const xcb_visualtype_t *visuals = xcb_depth_visuals(depth.data); + + for (int i = 0; i < len; i++) { + return visuals[0].visual_id; + } + } + } + return 0; + }; + auto visualId = findDepth(); + auto colormapId = xcb_generate_id(c.get()); + auto cmCookie = xcb_create_colormap_checked(c.get(), XCB_COLORMAP_ALLOC_NONE, colormapId, rootWindow(), visualId); + QVERIFY(!xcb_request_check(c.get(), cmCookie)); + + const uint32_t values[] = {XCB_PIXMAP_NONE, Xcb::defaultScreen()->black_pixel, colormapId}; + auto cookie = xcb_create_window_checked(c.get(), 32, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, visualId, XCB_CW_BACK_PIXMAP | XCB_CW_BORDER_PIXEL | XCB_CW_COLORMAP, values); + QVERIFY(!xcb_request_check(c.get(), cookie)); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + NETWinInfo info(c.get(), windowId, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); + info.setWindowType(NET::Desktop); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + // verify through a geometry request that it's depth 32 + Xcb::WindowGeometry geo(windowId); + QCOMPARE(geo->depth, uint8_t(32)); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QVERIFY(!window->isDecorated()); + QCOMPARE(window->windowType(), NET::Desktop); + QCOMPARE(window->frameGeometry(), windowGeometry); + QVERIFY(window->isDesktop()); + QCOMPARE(window->depth(), 24); + QVERIFY(!window->hasAlpha()); + + // and destroy the window again + xcb_unmap_window(c.get(), windowId); + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + c.reset(); + + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + QVERIFY(windowClosedSpy.wait()); +} + +} + +WAYLANDTEST_MAIN(KWin::X11DesktopWindowTest) +#include "desktop_window_x11_test.moc" diff --git a/autotests/integration/dont_crash_aurorae_destroy_deco.cpp b/autotests/integration/dont_crash_aurorae_destroy_deco.cpp new file mode 100644 index 0000000..b390eae --- /dev/null +++ b/autotests/integration/dont_crash_aurorae_destroy_deco.cpp @@ -0,0 +1,139 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "composite.h" +#include "core/output.h" +#include "core/platform.h" +#include "core/renderbackend.h" +#include "cursor.h" +#include "wayland_server.h" +#include "workspace.h" +#include "x11window.h" +#include + +#include + +#include + +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_dont_crash_aurorae_destroy_deco-0"); + +class DontCrashAuroraeDestroyDecoTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void testBorderlessMaximizedWindows(); +}; + +void DontCrashAuroraeDestroyDecoTest::initTestCase() +{ + qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + config->group("org.kde.kdecoration2").writeEntry("library", "org.kde.kwin.aurorae"); + config->sync(); + kwinApp()->setConfig(config); + + // this test needs to enforce OpenGL compositing to get into the crashy condition + qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2")); + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); + setenv("QT_QPA_PLATFORM", "wayland", true); + + QCOMPARE(Compositor::self()->backend()->compositingType(), KWin::OpenGLCompositing); +} + +void DontCrashAuroraeDestroyDecoTest::init() +{ + workspace()->setActiveOutput(QPoint(640, 512)); + Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void DontCrashAuroraeDestroyDecoTest::testBorderlessMaximizedWindows() +{ + // this test verifies that Aurorae doesn't crash when clicking the maximize button + // with kwin config option BorderlessMaximizedWindows + // see BUG 362772 + + // first adjust the config + KConfigGroup group = kwinApp()->config()->group("Windows"); + group.writeEntry("BorderlessMaximizedWindows", true); + group.sync(); + workspace()->slotReconfigure(); + QCOMPARE(options->borderlessMaximizedWindows(), true); + + // create an xcb window + xcb_connection_t *c = xcb_connect(nullptr, nullptr); + QVERIFY(!xcb_connection_has_error(c)); + + xcb_window_t windowId = xcb_generate_id(c); + xcb_create_window(c, XCB_COPY_FROM_PARENT, windowId, rootWindow(), 0, 0, 100, 200, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_map_window(c, windowId); + xcb_flush(c); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QVERIFY(window->isDecorated()); + QCOMPARE(window->maximizeMode(), MaximizeRestore); + QCOMPARE(window->noBorder(), false); + // verify that the deco is Aurorae + QCOMPARE(qstrcmp(window->decoration()->metaObject()->className(), "Aurorae::Decoration"), 0); + // find the maximize button + QQuickItem *item = window->decoration()->findChild("maximizeButton"); + QVERIFY(item); + const QPointF scenePoint = item->mapToScene(QPoint(0, 0)); + + // mark the window as ready for painting, otherwise it doesn't get input events + QMetaObject::invokeMethod(window, "setReadyForPainting"); + QVERIFY(window->readyForPainting()); + + // simulate click on maximize button + QSignalSpy maximizedStateChangedSpy(window, static_cast(&Window::clientMaximizedStateChanged)); + quint32 timestamp = 1; + Test::pointerMotion(window->frameGeometry().topLeft() + scenePoint.toPoint(), timestamp++); + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + QVERIFY(maximizedStateChangedSpy.wait()); + QCOMPARE(window->maximizeMode(), MaximizeFull); + QCOMPARE(window->noBorder(), true); + + // and destroy the window again + xcb_unmap_window(c, windowId); + xcb_destroy_window(c, windowId); + xcb_flush(c); + xcb_disconnect(c); + + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + QVERIFY(windowClosedSpy.wait()); +} + +} + +WAYLANDTEST_MAIN(KWin::DontCrashAuroraeDestroyDecoTest) +#include "dont_crash_aurorae_destroy_deco.moc" diff --git a/autotests/integration/dont_crash_cancel_animation.cpp b/autotests/integration/dont_crash_cancel_animation.cpp new file mode 100644 index 0000000..f7f4003 --- /dev/null +++ b/autotests/integration/dont_crash_cancel_animation.cpp @@ -0,0 +1,110 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "composite.h" +#include "core/platform.h" +#include "deleted.h" +#include "effectloader.h" +#include "effects.h" +#include "scripting/scriptedeffect.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" +#include "x11window.h" + +#include + +#include +#include +#include +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_dont_crash_cancel_animation-0"); + +class DontCrashCancelAnimationFromAnimationEndedTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testScript(); +}; + +void DontCrashCancelAnimationFromAnimationEndedTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + kwinApp()->start(); + QVERIFY(Compositor::self()); + QSignalSpy compositorToggledSpy(Compositor::self(), &Compositor::compositingToggled); + QVERIFY(compositorToggledSpy.wait()); + QVERIFY(effects); +} + +void DontCrashCancelAnimationFromAnimationEndedTest::init() +{ + QVERIFY(Test::setupWaylandConnection()); +} + +void DontCrashCancelAnimationFromAnimationEndedTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void DontCrashCancelAnimationFromAnimationEndedTest::testScript() +{ + // load a scripted effect which deletes animation data + ScriptedEffect *effect = ScriptedEffect::create(QStringLiteral("crashy"), QFINDTESTDATA("data/anim-data-delete-effect/effect.js"), 10, QString()); + QVERIFY(effect); + + const auto children = effects->children(); + for (auto it = children.begin(); it != children.end(); ++it) { + if (qstrcmp((*it)->metaObject()->className(), "KWin::EffectLoader") != 0) { + continue; + } + QVERIFY(QMetaObject::invokeMethod(*it, "effectLoaded", Q_ARG(KWin::Effect *, effect), Q_ARG(QString, QStringLiteral("crashy")))); + break; + } + QVERIFY(static_cast(effects)->isEffectLoaded(QStringLiteral("crashy"))); + + using namespace KWayland::Client; + // create a window + std::unique_ptr surface{Test::createSurface()}; + QVERIFY(surface); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + // let's render + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QCOMPARE(workspace()->activeWindow(), window); + + // make sure we animate + QTest::qWait(200); + + // wait for the window to be passed to Deleted + QSignalSpy windowDeletedSpy(window, &Window::windowClosed); + + surface.reset(); + + QVERIFY(windowDeletedSpy.wait()); + // make sure we animate + QTest::qWait(200); +} + +} + +WAYLANDTEST_MAIN(KWin::DontCrashCancelAnimationFromAnimationEndedTest) +#include "dont_crash_cancel_animation.moc" diff --git a/autotests/integration/dont_crash_cursor_physical_size_empty.cpp b/autotests/integration/dont_crash_cursor_physical_size_empty.cpp new file mode 100644 index 0000000..9c0aae5 --- /dev/null +++ b/autotests/integration/dont_crash_cursor_physical_size_empty.cpp @@ -0,0 +1,99 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2018 Martin Flöser + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "composite.h" +#include "core/platform.h" +#include "cursor.h" +#include "effectloader.h" +#include "effects.h" +#include "wayland/display.h" +#include "wayland/output_interface.h" +#include "wayland_server.h" +#include "workspace.h" +#include "x11window.h" + +#include + +#include +#include +#include + +using namespace KWin; +using namespace KWayland::Client; +static const QString s_socketName = QStringLiteral("wayland_test_kwin_crash_cursor_physical_size_empty-0"); + +class DontCrashCursorPhysicalSizeEmpty : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void init(); + void initTestCase(); + void cleanup(); + void testMoveCursorOverDeco(); +}; + +void DontCrashCursorPhysicalSizeEmpty::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration)); + + workspace()->setActiveOutput(QPoint(640, 512)); + KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void DontCrashCursorPhysicalSizeEmpty::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void DontCrashCursorPhysicalSizeEmpty::initTestCase() +{ + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + if (!QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("icons/DMZ-White/index.theme")).isEmpty()) { + qputenv("XCURSOR_THEME", QByteArrayLiteral("DMZ-White")); + } else { + // might be vanilla-dmz (e.g. Arch, FreeBSD) + qputenv("XCURSOR_THEME", QByteArrayLiteral("Vanilla-DMZ")); + } + qputenv("XCURSOR_SIZE", QByteArrayLiteral("0")); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); +} + +void DontCrashCursorPhysicalSizeEmpty::testMoveCursorOverDeco() +{ + // This test ensures that there is no endless recursion if the cursor theme cannot be created + // a reason for creation failure could be physical size not existing + // see BUG: 390314 + std::unique_ptr surface(Test::createSurface()); + Test::waylandServerSideDecoration()->create(surface.get(), surface.get()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(window->isDecorated()); + + // destroy physical size + KWaylandServer::Display *display = waylandServer()->display(); + auto output = display->outputs().first(); + output->setPhysicalSize(QSize(0, 0)); + // and fake a cursor theme change, so that the theme gets recreated + Q_EMIT KWin::Cursors::self()->mouse()->themeChanged(); + + KWin::Cursors::self()->mouse()->setPos(QPoint(window->frameGeometry().center().x(), window->clientPos().y() / 2)); +} + +WAYLANDTEST_MAIN(DontCrashCursorPhysicalSizeEmpty) +#include "dont_crash_cursor_physical_size_empty.moc" diff --git a/autotests/integration/dont_crash_empty_deco.cpp b/autotests/integration/dont_crash_empty_deco.cpp new file mode 100644 index 0000000..683b9fa --- /dev/null +++ b/autotests/integration/dont_crash_empty_deco.cpp @@ -0,0 +1,106 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "composite.h" +#include "core/output.h" +#include "core/platform.h" +#include "core/renderbackend.h" +#include "cursor.h" +#include "wayland_server.h" +#include "workspace.h" +#include "x11window.h" +#include + +#include + +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_dont_crash_empty_decoration-0"); + +class DontCrashEmptyDecorationTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void testBug361551(); +}; + +void DontCrashEmptyDecorationTest::initTestCase() +{ + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + // this test needs to enforce OpenGL compositing to get into the crashy condition + qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2")); + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); + setenv("QT_QPA_PLATFORM", "wayland", true); + + QCOMPARE(Compositor::self()->backend()->compositingType(), KWin::OpenGLCompositing); +} + +void DontCrashEmptyDecorationTest::init() +{ + workspace()->setActiveOutput(QPoint(640, 512)); + Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void DontCrashEmptyDecorationTest::testBug361551() +{ + // this test verifies that resizing an X11 window to an invalid size does not result in crash on unmap + // when the DecorationRenderer gets copied to the Deleted + // there a repaint is scheduled and the resulting texture is invalid if the window size is invalid + + // create an xcb window + xcb_connection_t *c = xcb_connect(nullptr, nullptr); + QVERIFY(!xcb_connection_has_error(c)); + + xcb_window_t windowId = xcb_generate_id(c); + xcb_create_window(c, XCB_COPY_FROM_PARENT, windowId, rootWindow(), 0, 0, 10, 10, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_map_window(c, windowId); + xcb_flush(c); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QVERIFY(window->isDecorated()); + + // let's set a stupid geometry + window->moveResize({0, 0, 0, 0}); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 0, 0)); + + // and destroy the window again + xcb_unmap_window(c, windowId); + xcb_destroy_window(c, windowId); + xcb_flush(c); + xcb_disconnect(c); + + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + QVERIFY(windowClosedSpy.wait()); +} + +} + +WAYLANDTEST_MAIN(KWin::DontCrashEmptyDecorationTest) +#include "dont_crash_empty_deco.moc" diff --git a/autotests/integration/dont_crash_glxgears.cpp b/autotests/integration/dont_crash_glxgears.cpp new file mode 100644 index 0000000..285cc57 --- /dev/null +++ b/autotests/integration/dont_crash_glxgears.cpp @@ -0,0 +1,92 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2015 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/platform.h" +#include "deleted.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" +#include "x11window.h" + +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_dont_crash_glxgears-0"); + +class DontCrashGlxgearsTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void testGlxgears(); +}; + +void DontCrashGlxgearsTest::initTestCase() +{ + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); +} + +void DontCrashGlxgearsTest::testGlxgears() +{ + // closing a glxgears window through Aurorae themes used to crash KWin + // Let's make sure that doesn't happen anymore + + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + + QProcess glxgears; + glxgears.setProgram(QStringLiteral("glxgears")); + glxgears.start(); + QVERIFY(glxgears.waitForStarted()); + + QVERIFY(windowAddedSpy.wait()); + QCOMPARE(windowAddedSpy.count(), 1); + QCOMPARE(workspace()->clientList().count(), 1); + X11Window *glxgearsWindow = workspace()->clientList().first(); + QVERIFY(glxgearsWindow->isDecorated()); + QSignalSpy closedSpy(glxgearsWindow, &X11Window::windowClosed); + KDecoration2::Decoration *decoration = glxgearsWindow->decoration(); + QVERIFY(decoration); + + // send a mouse event to the position of the close button + // TODO: position is dependent on the decoration in use. We should use a static target instead, a fake deco for autotests. + QPointF pos = decoration->rect().topRight() + QPointF(-decoration->borderTop() / 2, decoration->borderTop() / 2); + QHoverEvent event(QEvent::HoverMove, pos, pos); + QCoreApplication::instance()->sendEvent(decoration, &event); + // mouse press + QMouseEvent mousePressevent(QEvent::MouseButtonPress, pos, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + mousePressevent.setAccepted(false); + QCoreApplication::sendEvent(decoration, &mousePressevent); + QVERIFY(mousePressevent.isAccepted()); + // mouse Release + QMouseEvent mouseReleaseEvent(QEvent::MouseButtonRelease, pos, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + mouseReleaseEvent.setAccepted(false); + QCoreApplication::sendEvent(decoration, &mouseReleaseEvent); + QVERIFY(mouseReleaseEvent.isAccepted()); + + QVERIFY(closedSpy.wait()); + QCOMPARE(closedSpy.count(), 1); + xcb_flush(connection()); + + if (glxgears.state() == QProcess::Running) { + QVERIFY(glxgears.waitForFinished()); + } +} + +} + +WAYLANDTEST_MAIN(KWin::DontCrashGlxgearsTest) +#include "dont_crash_glxgears.moc" diff --git a/autotests/integration/dont_crash_no_border.cpp b/autotests/integration/dont_crash_no_border.cpp new file mode 100644 index 0000000..c6c2cbd --- /dev/null +++ b/autotests/integration/dont_crash_no_border.cpp @@ -0,0 +1,108 @@ + +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "composite.h" +#include "core/output.h" +#include "core/platform.h" +#include "core/renderbackend.h" +#include "cursor.h" +#include "wayland_server.h" +#include "workspace.h" +#include "x11window.h" +#include + +#include + +#include + +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_dont_crash_no_border-0"); + +class DontCrashNoBorder : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testCreateWindow(); +}; + +void DontCrashNoBorder::initTestCase() +{ + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + config->group("org.kde.kdecoration2").writeEntry("NoPlugin", true); + config->sync(); + kwinApp()->setConfig(config); + + // this test needs to enforce OpenGL compositing to get into the crashy condition + qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2")); + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); + setenv("QT_QPA_PLATFORM", "wayland", true); + + QCOMPARE(Compositor::self()->backend()->compositingType(), KWin::OpenGLCompositing); +} + +void DontCrashNoBorder::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::XdgDecorationV1)); + + workspace()->setActiveOutput(QPoint(640, 512)); + Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void DontCrashNoBorder::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void DontCrashNoBorder::testCreateWindow() +{ + // create a window and ensure that this doesn't crash + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + std::unique_ptr decoration(Test::createXdgToplevelDecorationV1(shellSurface.get())); + QSignalSpy decorationConfigureRequestedSpy(decoration.get(), &Test::XdgToplevelDecorationV1::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + + // Initialize the xdg-toplevel surface. + decoration->set_mode(Test::XdgToplevelDecorationV1::mode_server_side); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(decorationConfigureRequestedSpy.last().at(0).value(), Test::XdgToplevelDecorationV1::mode_client_side); + + // let's render + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(500, 50), Qt::blue); + QVERIFY(window); + QCOMPARE(workspace()->activeWindow(), window); + QVERIFY(!window->isDecorated()); +} + +} + +WAYLANDTEST_MAIN(KWin::DontCrashNoBorder) +#include "dont_crash_no_border.moc" diff --git a/autotests/integration/dont_crash_reinitialize_compositor.cpp b/autotests/integration/dont_crash_reinitialize_compositor.cpp new file mode 100644 index 0000000..c5e0cdc --- /dev/null +++ b/autotests/integration/dont_crash_reinitialize_compositor.cpp @@ -0,0 +1,149 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2018 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "kwin_wayland_test.h" + +#include "composite.h" +#include "core/output.h" +#include "core/platform.h" +#include "core/renderbackend.h" +#include "deleted.h" +#include "effectloader.h" +#include "effects.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_dont_crash_reinitialize_compositor-0"); + +class DontCrashReinitializeCompositorTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testReinitializeCompositor_data(); + void testReinitializeCompositor(); +}; + +void DontCrashReinitializeCompositorTest::initTestCase() +{ + qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); + + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup plugins(config, QStringLiteral("Plugins")); + const auto builtinNames = EffectLoader().listOfKnownEffects(); + for (const QString &name : builtinNames) { + plugins.writeEntry(name + QStringLiteral("Enabled"), false); + } + config->sync(); + kwinApp()->setConfig(config); + + qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2")); + qputenv("KWIN_EFFECTS_FORCE_ANIMATIONS", QByteArrayLiteral("1")); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); + + QCOMPARE(Compositor::self()->backend()->compositingType(), KWin::OpenGLCompositing); +} + +void DontCrashReinitializeCompositorTest::init() +{ + QVERIFY(Test::setupWaylandConnection()); +} + +void DontCrashReinitializeCompositorTest::cleanup() +{ + // Unload all effects. + auto effectsImpl = qobject_cast(effects); + QVERIFY(effectsImpl); + effectsImpl->unloadAllEffects(); + QVERIFY(effectsImpl->loadedEffects().isEmpty()); + + Test::destroyWaylandConnection(); +} + +void DontCrashReinitializeCompositorTest::testReinitializeCompositor_data() +{ + QTest::addColumn("effectName"); + + QTest::newRow("Fade") << QStringLiteral("kwin4_effect_fade"); + QTest::newRow("Glide") << QStringLiteral("glide"); + QTest::newRow("Scale") << QStringLiteral("kwin4_effect_scale"); +} + +void DontCrashReinitializeCompositorTest::testReinitializeCompositor() +{ + // This test verifies that KWin doesn't crash when the compositor settings + // have been changed while a scripted effect animates the disappearing of + // a window. + + // Make sure that we have the right effects ptr. + auto effectsImpl = qobject_cast(effects); + QVERIFY(effectsImpl); + + // Create the test window. + using namespace KWayland::Client; + + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // Make sure that only the test effect is loaded. + QFETCH(QString, effectName); + QVERIFY(effectsImpl->loadEffect(effectName)); + QCOMPARE(effectsImpl->loadedEffects().count(), 1); + QCOMPARE(effectsImpl->loadedEffects().first(), effectName); + Effect *effect = effectsImpl->findEffect(effectName); + QVERIFY(effect); + QVERIFY(!effect->isActive()); + + // Close the test window. + QSignalSpy windowClosedSpy(window, &Window::windowClosed); + shellSurface.reset(); + surface.reset(); + QVERIFY(windowClosedSpy.wait()); + + // The test effect should start animating the test window. Is there a better + // way to verify that the test effect actually animates the test window? + QVERIFY(effect->isActive()); + + // Re-initialize the compositor, effects will be destroyed and created again. + Compositor::self()->reinitialize(); + + // By this time, KWin should still be alive. +} + +} // namespace KWin + +WAYLANDTEST_MAIN(KWin::DontCrashReinitializeCompositorTest) +#include "dont_crash_reinitialize_compositor.moc" diff --git a/autotests/integration/dont_crash_useractions_menu.cpp b/autotests/integration/dont_crash_useractions_menu.cpp new file mode 100644 index 0000000..eb26315 --- /dev/null +++ b/autotests/integration/dont_crash_useractions_menu.cpp @@ -0,0 +1,104 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2017 Martin Flöser + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "keyboard_input.h" +#include "pointer_input.h" +#include "useractions.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace KWin; +using namespace KWayland::Client; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_dont_crash_useractions_menu-0"); + +class TestDontCrashUseractionsMenu : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testShowHideShowUseractionsMenu(); +}; + +void TestDontCrashUseractionsMenu::initTestCase() +{ + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + // force style to breeze as that's the one which triggered the crash + QVERIFY(kwinApp()->setStyle(QStringLiteral("breeze"))); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); +} + +void TestDontCrashUseractionsMenu::init() +{ + QVERIFY(Test::setupWaylandConnection()); + + workspace()->setActiveOutput(QPoint(640, 512)); + KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void TestDontCrashUseractionsMenu::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void TestDontCrashUseractionsMenu::testShowHideShowUseractionsMenu() +{ + // this test creates the condition of BUG 382063 + std::unique_ptr surface1(Test::createSurface()); + std::unique_ptr shellSurface1(Test::createXdgToplevelSurface(surface1.get())); + auto window = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + workspace()->showWindowMenu(QRect(), window); + auto userActionsMenu = workspace()->userActionsMenu(); + QTRY_VERIFY(userActionsMenu->isShown()); + QVERIFY(userActionsMenu->hasWindow()); + + Test::keyboardKeyPressed(KEY_ESC, 0); + Test::keyboardKeyReleased(KEY_ESC, 1); + QTRY_VERIFY(!userActionsMenu->isShown()); + QVERIFY(!userActionsMenu->hasWindow()); + + // and show again, this triggers BUG 382063 + workspace()->showWindowMenu(QRect(), window); + QTRY_VERIFY(userActionsMenu->isShown()); + QVERIFY(userActionsMenu->hasWindow()); +} + +WAYLANDTEST_MAIN(TestDontCrashUseractionsMenu) +#include "dont_crash_useractions_menu.moc" diff --git a/autotests/integration/effects/CMakeLists.txt b/autotests/integration/effects/CMakeLists.txt new file mode 100644 index 0000000..33557d5 --- /dev/null +++ b/autotests/integration/effects/CMakeLists.txt @@ -0,0 +1,11 @@ +if (XCB_ICCCM_FOUND) + integrationTest(NAME testTranslucency SRCS translucency_test.cpp LIBS XCB::ICCCM) + integrationTest(NAME testSlidingPopups SRCS slidingpopups_test.cpp LIBS XCB::ICCCM) + integrationTest(NAME testShadeWobblyWindows SRCS wobbly_shade_test.cpp LIBS XCB::ICCCM) +endif() +integrationTest(NAME testScriptedEffects SRCS scripted_effects_test.cpp) +integrationTest(WAYLAND_ONLY NAME testToplevelOpenCloseAnimation SRCS toplevel_open_close_animation_test.cpp) +integrationTest(WAYLAND_ONLY NAME testPopupOpenCloseAnimation SRCS popup_open_close_animation_test.cpp) +integrationTest(WAYLAND_ONLY NAME testDesktopSwitchingAnimation SRCS desktop_switching_animation_test.cpp) +integrationTest(WAYLAND_ONLY NAME testMinimizeAnimation SRCS minimize_animation_test.cpp) +integrationTest(WAYLAND_ONLY NAME testMaximizeAnimation SRCS maximize_animation_test.cpp) diff --git a/autotests/integration/effects/desktop_switching_animation_test.cpp b/autotests/integration/effects/desktop_switching_animation_test.cpp new file mode 100644 index 0000000..fadae89 --- /dev/null +++ b/autotests/integration/effects/desktop_switching_animation_test.cpp @@ -0,0 +1,142 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2019 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "kwin_wayland_test.h" + +#include "composite.h" +#include "core/platform.h" +#include "core/renderbackend.h" +#include "effectloader.h" +#include "effects.h" +#include "virtualdesktops.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include + +using namespace KWin; + +static const QString s_socketName = QStringLiteral("wayland_test_effects_desktop_switching_animation-0"); + +class DesktopSwitchingAnimationTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testSwitchDesktops_data(); + void testSwitchDesktops(); +}; + +void DesktopSwitchingAnimationTest::initTestCase() +{ + qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); + + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup plugins(config, QStringLiteral("Plugins")); + const auto builtinNames = EffectLoader().listOfKnownEffects(); + for (const QString &name : builtinNames) { + plugins.writeEntry(name + QStringLiteral("Enabled"), false); + } + config->sync(); + kwinApp()->setConfig(config); + + qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2")); + qputenv("KWIN_EFFECTS_FORCE_ANIMATIONS", QByteArrayLiteral("1")); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + + QCOMPARE(Compositor::self()->backend()->compositingType(), KWin::OpenGLCompositing); +} + +void DesktopSwitchingAnimationTest::init() +{ + QVERIFY(Test::setupWaylandConnection()); +} + +void DesktopSwitchingAnimationTest::cleanup() +{ + auto effectsImpl = qobject_cast(effects); + QVERIFY(effectsImpl); + effectsImpl->unloadAllEffects(); + QVERIFY(effectsImpl->loadedEffects().isEmpty()); + + VirtualDesktopManager::self()->setCount(1); + + Test::destroyWaylandConnection(); +} + +void DesktopSwitchingAnimationTest::testSwitchDesktops_data() +{ + QTest::addColumn("effectName"); + + QTest::newRow("Fade Desktop") << QStringLiteral("kwin4_effect_fadedesktop"); + QTest::newRow("Slide") << QStringLiteral("slide"); +} + +void DesktopSwitchingAnimationTest::testSwitchDesktops() +{ + // This test verifies that virtual desktop switching animation effects actually + // try to animate switching between desktops. + + // We need at least 2 virtual desktops for the test. + VirtualDesktopManager::self()->setCount(2); + QCOMPARE(VirtualDesktopManager::self()->current(), 1u); + QCOMPARE(VirtualDesktopManager::self()->count(), 2u); + + // The Fade Desktop effect will do nothing if there are no windows to fade, + // so we have to create a dummy test window. + using namespace KWayland::Client; + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QCOMPARE(window->desktops().count(), 1); + QCOMPARE(window->desktops().first(), VirtualDesktopManager::self()->desktops().first()); + + // Load effect that will be tested. + QFETCH(QString, effectName); + auto effectsImpl = qobject_cast(effects); + QVERIFY(effectsImpl); + QVERIFY(effectsImpl->loadEffect(effectName)); + QCOMPARE(effectsImpl->loadedEffects().count(), 1); + QCOMPARE(effectsImpl->loadedEffects().first(), effectName); + Effect *effect = effectsImpl->findEffect(effectName); + QVERIFY(effect); + QVERIFY(!effect->isActive()); + + // Switch to the second virtual desktop. + VirtualDesktopManager::self()->setCurrent(2u); + QCOMPARE(VirtualDesktopManager::self()->current(), 2u); + QVERIFY(effect->isActive()); + QCOMPARE(effects->activeFullScreenEffect(), effect); + + // Eventually, the animation will be complete. + QTRY_VERIFY(!effect->isActive()); + QTRY_COMPARE(effects->activeFullScreenEffect(), nullptr); + + // Destroy the test window. + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +WAYLANDTEST_MAIN(DesktopSwitchingAnimationTest) +#include "desktop_switching_animation_test.moc" diff --git a/autotests/integration/effects/maximize_animation_test.cpp b/autotests/integration/effects/maximize_animation_test.cpp new file mode 100644 index 0000000..95237ad --- /dev/null +++ b/autotests/integration/effects/maximize_animation_test.cpp @@ -0,0 +1,182 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2019 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "kwin_wayland_test.h" + +#include "composite.h" +#include "core/platform.h" +#include "effectloader.h" +#include "effects.h" +#include "scene.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include + +using namespace KWin; + +static const QString s_socketName = QStringLiteral("wayland_test_effects_maximize_animation-0"); + +class MaximizeAnimationTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testMaximizeRestore(); +}; + +void MaximizeAnimationTest::initTestCase() +{ + qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); + + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup plugins(config, QStringLiteral("Plugins")); + const auto builtinNames = EffectLoader().listOfKnownEffects(); + for (const QString &name : builtinNames) { + plugins.writeEntry(name + QStringLiteral("Enabled"), false); + } + config->sync(); + kwinApp()->setConfig(config); + + qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2")); + qputenv("KWIN_EFFECTS_FORCE_ANIMATIONS", QByteArrayLiteral("1")); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); +} + +void MaximizeAnimationTest::init() +{ + QVERIFY(Test::setupWaylandConnection()); +} + +void MaximizeAnimationTest::cleanup() +{ + auto effectsImpl = qobject_cast(effects); + QVERIFY(effectsImpl); + effectsImpl->unloadAllEffects(); + QVERIFY(effectsImpl->loadedEffects().isEmpty()); + + Test::destroyWaylandConnection(); +} + +void MaximizeAnimationTest::testMaximizeRestore() +{ + // This test verifies that the maximize effect animates a window + // when it's maximized or restored. + + using namespace KWayland::Client; + + // Create the test window. + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + + // Wait for the initial configure event. + Test::XdgToplevel::States states; + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value(), QSize(0, 0)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Draw contents of the surface. + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(window->isActive()); + QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore); + + // We should receive a configure event when the window becomes active. + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Load effect that will be tested. + const QString effectName = QStringLiteral("kwin4_effect_maximize"); + auto effectsImpl = qobject_cast(effects); + QVERIFY(effectsImpl); + QVERIFY(effectsImpl->loadEffect(effectName)); + QCOMPARE(effectsImpl->loadedEffects().count(), 1); + QCOMPARE(effectsImpl->loadedEffects().first(), effectName); + Effect *effect = effectsImpl->findEffect(effectName); + QVERIFY(effect); + QVERIFY(!effect->isActive()); + + // Maximize the window. + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + QSignalSpy maximizeChangedSpy(window, qOverload(&Window::clientMaximizedStateChanged)); + + workspace()->slotWindowMaximize(); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value(), QSize(1280, 1024)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Draw contents of the maximized window. + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(1280, 1024), Qt::red); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(frameGeometryChangedSpy.count(), 1); + QCOMPARE(maximizeChangedSpy.count(), 1); + QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeFull); + QVERIFY(effect->isActive()); + + // Eventually, the animation will be complete. + QTRY_VERIFY(!effect->isActive()); + + // Restore the window. + workspace()->slotWindowMaximize(); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 4); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value(), QSize(100, 50)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Draw contents of the restored window. + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(frameGeometryChangedSpy.count(), 2); + QCOMPARE(maximizeChangedSpy.count(), 2); + QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore); + QVERIFY(effect->isActive()); + + // Eventually, the animation will be complete. + QTRY_VERIFY(!effect->isActive()); + + // Destroy the test window. + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +WAYLANDTEST_MAIN(MaximizeAnimationTest) +#include "maximize_animation_test.moc" diff --git a/autotests/integration/effects/minimize_animation_test.cpp b/autotests/integration/effects/minimize_animation_test.cpp new file mode 100644 index 0000000..3db47b0 --- /dev/null +++ b/autotests/integration/effects/minimize_animation_test.cpp @@ -0,0 +1,174 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2019 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "kwin_wayland_test.h" + +#include "composite.h" +#include "core/platform.h" +#include "core/renderbackend.h" +#include "effectloader.h" +#include "effects.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include +#include +#include + +using namespace KWin; + +static const QString s_socketName = QStringLiteral("wayland_test_effects_minimize_animation-0"); + +class MinimizeAnimationTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testMinimizeUnminimize_data(); + void testMinimizeUnminimize(); +}; + +void MinimizeAnimationTest::initTestCase() +{ + qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); + + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup plugins(config, QStringLiteral("Plugins")); + const auto builtinNames = EffectLoader().listOfKnownEffects(); + for (const QString &name : builtinNames) { + plugins.writeEntry(name + QStringLiteral("Enabled"), false); + } + config->sync(); + kwinApp()->setConfig(config); + + qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2")); + qputenv("KWIN_EFFECTS_FORCE_ANIMATIONS", QByteArrayLiteral("1")); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + + QCOMPARE(Compositor::self()->backend()->compositingType(), KWin::OpenGLCompositing); +} + +void MinimizeAnimationTest::init() +{ + QVERIFY(Test::setupWaylandConnection( + Test::AdditionalWaylandInterface::PlasmaShell | Test::AdditionalWaylandInterface::WindowManagement)); +} + +void MinimizeAnimationTest::cleanup() +{ + auto effectsImpl = qobject_cast(effects); + QVERIFY(effectsImpl); + effectsImpl->unloadAllEffects(); + QVERIFY(effectsImpl->loadedEffects().isEmpty()); + + Test::destroyWaylandConnection(); +} + +void MinimizeAnimationTest::testMinimizeUnminimize_data() +{ + QTest::addColumn("effectName"); + + QTest::newRow("Magic Lamp") << QStringLiteral("magiclamp"); + QTest::newRow("Squash") << QStringLiteral("kwin4_effect_squash"); +} + +void MinimizeAnimationTest::testMinimizeUnminimize() +{ + // This test verifies that a minimize effect tries to animate a window + // when it's minimized or unminimized. + + using namespace KWayland::Client; + + QSignalSpy plasmaWindowCreatedSpy(Test::waylandWindowManagement(), &PlasmaWindowManagement::windowCreated); + + // Create a panel at the top of the screen. + const QRect panelRect = QRect(0, 0, 1280, 36); + std::unique_ptr panelSurface(Test::createSurface()); + QVERIFY(panelSurface != nullptr); + std::unique_ptr panelShellSurface(Test::createXdgToplevelSurface(panelSurface.get())); + QVERIFY(panelShellSurface != nullptr); + std::unique_ptr plasmaPanelShellSurface(Test::waylandPlasmaShell()->createSurface(panelSurface.get())); + QVERIFY(plasmaPanelShellSurface != nullptr); + plasmaPanelShellSurface->setRole(PlasmaShellSurface::Role::Panel); + plasmaPanelShellSurface->setPosition(panelRect.topLeft()); + plasmaPanelShellSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::AlwaysVisible); + Window *panel = Test::renderAndWaitForShown(panelSurface.get(), panelRect.size(), Qt::blue); + QVERIFY(panel); + QVERIFY(panel->isDock()); + QCOMPARE(panel->frameGeometry(), panelRect); + QVERIFY(plasmaWindowCreatedSpy.wait()); + QCOMPARE(plasmaWindowCreatedSpy.count(), 1); + + // Create the test window. + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::red); + QVERIFY(window); + QVERIFY(plasmaWindowCreatedSpy.wait()); + QCOMPARE(plasmaWindowCreatedSpy.count(), 2); + + // We have to set the minimized geometry because the squash effect needs it, + // otherwise it won't start animation. + auto plasmaWindow = plasmaWindowCreatedSpy.last().first().value(); + QVERIFY(plasmaWindow); + const QRect iconRect = QRect(0, 0, 42, 36); + plasmaWindow->setMinimizedGeometry(panelSurface.get(), iconRect); + Test::flushWaylandConnection(); + QTRY_COMPARE(window->iconGeometry(), iconRect.translated(panel->frameGeometry().topLeft().toPoint())); + + // Load effect that will be tested. + QFETCH(QString, effectName); + auto effectsImpl = qobject_cast(effects); + QVERIFY(effectsImpl); + QVERIFY(effectsImpl->loadEffect(effectName)); + QCOMPARE(effectsImpl->loadedEffects().count(), 1); + QCOMPARE(effectsImpl->loadedEffects().first(), effectName); + Effect *effect = effectsImpl->findEffect(effectName); + QVERIFY(effect); + QVERIFY(!effect->isActive()); + + // Start the minimize animation. + window->minimize(); + QVERIFY(effect->isActive()); + + // Eventually, the animation will be complete. + QTRY_VERIFY(!effect->isActive()); + + // Start the unminimize animation. + window->unminimize(); + QVERIFY(effect->isActive()); + + // Eventually, the animation will be complete. + QTRY_VERIFY(!effect->isActive()); + + // Destroy the panel. + panelSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(panel)); + + // Destroy the test window. + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +WAYLANDTEST_MAIN(MinimizeAnimationTest) +#include "minimize_animation_test.moc" diff --git a/autotests/integration/effects/popup_open_close_animation_test.cpp b/autotests/integration/effects/popup_open_close_animation_test.cpp new file mode 100644 index 0000000..a798a7a --- /dev/null +++ b/autotests/integration/effects/popup_open_close_animation_test.cpp @@ -0,0 +1,265 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2019 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "kwin_wayland_test.h" + +#include "core/platform.h" +#include "deleted.h" +#include "effectloader.h" +#include "effects.h" +#include "internalwindow.h" +#include "useractions.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include "decorations/decoratedclient.h" + +#include + +#include + +using namespace KWin; + +static const QString s_socketName = QStringLiteral("wayland_test_effects_popup_open_close_animation-0"); + +class PopupOpenCloseAnimationTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testAnimatePopups(); + void testAnimateUserActionsPopup(); + void testAnimateDecorationTooltips(); +}; + +void PopupOpenCloseAnimationTest::initTestCase() +{ + qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); + + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup plugins(config, QStringLiteral("Plugins")); + const auto builtinNames = EffectLoader().listOfKnownEffects(); + for (const QString &name : builtinNames) { + plugins.writeEntry(name + QStringLiteral("Enabled"), false); + } + config->sync(); + kwinApp()->setConfig(config); + + qputenv("KWIN_EFFECTS_FORCE_ANIMATIONS", QByteArrayLiteral("1")); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); +} + +void PopupOpenCloseAnimationTest::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::XdgDecorationV1)); +} + +void PopupOpenCloseAnimationTest::cleanup() +{ + auto effectsImpl = qobject_cast(effects); + QVERIFY(effectsImpl); + effectsImpl->unloadAllEffects(); + QVERIFY(effectsImpl->loadedEffects().isEmpty()); + + Test::destroyWaylandConnection(); +} + +void PopupOpenCloseAnimationTest::testAnimatePopups() +{ + // This test verifies that popup open/close animation effects try + // to animate popups(e.g. popup menus, tooltips, etc). + + // Make sure that we have the right effects ptr. + auto effectsImpl = qobject_cast(effects); + QVERIFY(effectsImpl); + + // Create the main window. + using namespace KWayland::Client; + std::unique_ptr mainWindowSurface(Test::createSurface()); + QVERIFY(mainWindowSurface != nullptr); + std::unique_ptr mainWindowShellSurface(Test::createXdgToplevelSurface(mainWindowSurface.get())); + QVERIFY(mainWindowShellSurface != nullptr); + Window *mainWindow = Test::renderAndWaitForShown(mainWindowSurface.get(), QSize(100, 50), Qt::blue); + QVERIFY(mainWindow); + + // Load effect that will be tested. + const QString effectName = QStringLiteral("kwin4_effect_fadingpopups"); + QVERIFY(effectsImpl->loadEffect(effectName)); + QCOMPARE(effectsImpl->loadedEffects().count(), 1); + QCOMPARE(effectsImpl->loadedEffects().first(), effectName); + Effect *effect = effectsImpl->findEffect(effectName); + QVERIFY(effect); + QVERIFY(!effect->isActive()); + + // Create a popup, it should be animated. + std::unique_ptr popupSurface(Test::createSurface()); + QVERIFY(popupSurface != nullptr); + std::unique_ptr positioner(Test::createXdgPositioner()); + positioner->set_size(20, 20); + positioner->set_anchor_rect(0, 0, 10, 10); + positioner->set_gravity(Test::XdgPositioner::gravity_bottom_right); + positioner->set_anchor(Test::XdgPositioner::anchor_bottom_left); + std::unique_ptr popupShellSurface(Test::createXdgPopupSurface(popupSurface.get(), mainWindowShellSurface->xdgSurface(), positioner.get())); + QVERIFY(popupShellSurface != nullptr); + Window *popup = Test::renderAndWaitForShown(popupSurface.get(), QSize(20, 20), Qt::red); + QVERIFY(popup); + QVERIFY(popup->isPopupWindow()); + QCOMPARE(popup->transientFor(), mainWindow); + QVERIFY(effect->isActive()); + + // Eventually, the animation will be complete. + QTRY_VERIFY(!effect->isActive()); + + // Destroy the popup, it should not be animated. + QSignalSpy popupClosedSpy(popup, &Window::windowClosed); + popupShellSurface.reset(); + popupSurface.reset(); + QVERIFY(popupClosedSpy.wait()); + QVERIFY(effect->isActive()); + + // Eventually, the animation will be complete. + QTRY_VERIFY(!effect->isActive()); + + // Destroy the main window. + mainWindowSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(mainWindow)); +} + +void PopupOpenCloseAnimationTest::testAnimateUserActionsPopup() +{ + // This test verifies that popup open/close animation effects try + // to animate the user actions popup. + + // Make sure that we have the right effects ptr. + auto effectsImpl = qobject_cast(effects); + QVERIFY(effectsImpl); + + // Create the test window. + using namespace KWayland::Client; + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // Load effect that will be tested. + const QString effectName = QStringLiteral("kwin4_effect_fadingpopups"); + QVERIFY(effectsImpl->loadEffect(effectName)); + QCOMPARE(effectsImpl->loadedEffects().count(), 1); + QCOMPARE(effectsImpl->loadedEffects().first(), effectName); + Effect *effect = effectsImpl->findEffect(effectName); + QVERIFY(effect); + QVERIFY(!effect->isActive()); + + // Show the user actions popup. + workspace()->showWindowMenu(QRect(), window); + auto userActionsMenu = workspace()->userActionsMenu(); + QTRY_VERIFY(userActionsMenu->isShown()); + QVERIFY(userActionsMenu->hasWindow()); + QVERIFY(effect->isActive()); + + // Eventually, the animation will be complete. + QTRY_VERIFY(!effect->isActive()); + + // Close the user actions popup. + Test::keyboardKeyPressed(KEY_ESC, 0); + Test::keyboardKeyReleased(KEY_ESC, 1); + QTRY_VERIFY(!userActionsMenu->isShown()); + QVERIFY(!userActionsMenu->hasWindow()); + QVERIFY(effect->isActive()); + + // Eventually, the animation will be complete. + QTRY_VERIFY(!effect->isActive()); + + // Destroy the test window. + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void PopupOpenCloseAnimationTest::testAnimateDecorationTooltips() +{ + // This test verifies that popup open/close animation effects try + // to animate decoration tooltips. + + // Make sure that we have the right effects ptr. + auto effectsImpl = qobject_cast(effects); + QVERIFY(effectsImpl); + + // Create the test window. + using namespace KWayland::Client; + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + QVERIFY(shellSurface != nullptr); + std::unique_ptr deco(Test::createXdgToplevelDecorationV1(shellSurface.get())); + QVERIFY(deco != nullptr); + + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + deco->set_mode(Test::XdgToplevelDecorationV1::mode_server_side); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(window->isDecorated()); + + // Load effect that will be tested. + const QString effectName = QStringLiteral("kwin4_effect_fadingpopups"); + QVERIFY(effectsImpl->loadEffect(effectName)); + QCOMPARE(effectsImpl->loadedEffects().count(), 1); + QCOMPARE(effectsImpl->loadedEffects().first(), effectName); + Effect *effect = effectsImpl->findEffect(effectName); + QVERIFY(effect); + QVERIFY(!effect->isActive()); + + // Show a decoration tooltip. + QSignalSpy tooltipAddedSpy(workspace(), &Workspace::internalWindowAdded); + window->decoratedClient()->requestShowToolTip(QStringLiteral("KWin rocks!")); + QVERIFY(tooltipAddedSpy.wait()); + InternalWindow *tooltip = tooltipAddedSpy.first().first().value(); + QVERIFY(tooltip->isInternal()); + QVERIFY(tooltip->isPopupWindow()); + QVERIFY(tooltip->handle()->flags().testFlag(Qt::ToolTip)); + QVERIFY(effect->isActive()); + + // Eventually, the animation will be complete. + QTRY_VERIFY(!effect->isActive()); + + // Hide the decoration tooltip. + QSignalSpy tooltipClosedSpy(tooltip, &InternalWindow::windowClosed); + window->decoratedClient()->requestHideToolTip(); + QVERIFY(tooltipClosedSpy.wait()); + QVERIFY(effect->isActive()); + + // Eventually, the animation will be complete. + QTRY_VERIFY(!effect->isActive()); + + // Destroy the test window. + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +WAYLANDTEST_MAIN(PopupOpenCloseAnimationTest) +#include "popup_open_close_animation_test.moc" diff --git a/autotests/integration/effects/scripted_effects_test.cpp b/autotests/integration/effects/scripted_effects_test.cpp new file mode 100644 index 0000000..b1c5b0a --- /dev/null +++ b/autotests/integration/effects/scripted_effects_test.cpp @@ -0,0 +1,758 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2018 David Edmundson + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "kwin_wayland_test.h" + +#include "composite.h" +#include "core/platform.h" +#include "core/renderbackend.h" +#include "cursor.h" +#include "deleted.h" +#include "effectloader.h" +#include "effects.h" +#include "libkwineffects/anidata_p.h" +#include "scripting/scriptedeffect.h" +#include "virtualdesktops.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace KWin; +using namespace std::chrono_literals; + +static const QString s_socketName = QStringLiteral("wayland_test_effects_scripts-0"); + +class ScriptedEffectsTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testEffectsHandler(); + void testEffectsContext(); + void testShortcuts(); + void testAnimations_data(); + void testAnimations(); + void testScreenEdge(); + void testScreenEdgeTouch(); + void testFullScreenEffect_data(); + void testFullScreenEffect(); + void testKeepAlive_data(); + void testKeepAlive(); + void testGrab(); + void testGrabAlreadyGrabbedWindow(); + void testGrabAlreadyGrabbedWindowForced(); + void testUngrab(); + void testRedirect_data(); + void testRedirect(); + void testComplete(); + +private: + ScriptedEffect *loadEffect(const QString &name); +}; + +class ScriptedEffectWithDebugSpy : public KWin::ScriptedEffect +{ + Q_OBJECT +public: + ScriptedEffectWithDebugSpy(); + bool load(const QString &name); + using AnimationEffect::AniMap; + using AnimationEffect::state; + Q_INVOKABLE void sendTestResponse(const QString &out); // proxies triggers out from the tests + QList actions(); // returns any QActions owned by the ScriptEngine +Q_SIGNALS: + void testOutput(const QString &data); +}; + +void ScriptedEffectWithDebugSpy::sendTestResponse(const QString &out) +{ + Q_EMIT testOutput(out); +} + +QList ScriptedEffectWithDebugSpy::actions() +{ + return findChildren(QString(), Qt::FindDirectChildrenOnly); +} + +ScriptedEffectWithDebugSpy::ScriptedEffectWithDebugSpy() + : ScriptedEffect() +{ +} + +bool ScriptedEffectWithDebugSpy::load(const QString &name) +{ + auto selfContext = engine()->newQObject(this); + QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); + const QString path = QFINDTESTDATA("./scripts/" + name + ".js"); + engine()->globalObject().setProperty("sendTestResponse", selfContext.property("sendTestResponse")); + if (!init(name, path)) { + return false; + } + + // inject our newly created effect to be registered with the EffectsHandlerImpl::loaded_effects + // this is private API so some horrible code is used to find the internal effectloader + // and register ourselves + auto children = effects->children(); + for (auto it = children.begin(); it != children.end(); ++it) { + if (qstrcmp((*it)->metaObject()->className(), "KWin::EffectLoader") != 0) { + continue; + } + QMetaObject::invokeMethod(*it, "effectLoaded", Q_ARG(KWin::Effect *, this), Q_ARG(QString, name)); + break; + } + + return (static_cast(effects)->isEffectLoaded(name)); +} + +void ScriptedEffectsTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + // disable all effects - we don't want to have it interact with the rendering + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup plugins(config, QStringLiteral("Plugins")); + const auto builtinNames = EffectLoader().listOfKnownEffects(); + for (QString name : builtinNames) { + plugins.writeEntry(name + QStringLiteral("Enabled"), false); + } + + config->sync(); + kwinApp()->setConfig(config); + + qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2")); + qputenv("KWIN_EFFECTS_FORCE_ANIMATIONS", "1"); + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + QVERIFY(Compositor::self()); + + QCOMPARE(Compositor::self()->backend()->compositingType(), KWin::OpenGLCompositing); + + KWin::VirtualDesktopManager::self()->setCount(2); +} + +void ScriptedEffectsTest::init() +{ + QVERIFY(Test::setupWaylandConnection()); +} + +void ScriptedEffectsTest::cleanup() +{ + Test::destroyWaylandConnection(); + + auto effectsImpl = static_cast(effects); + effectsImpl->unloadAllEffects(); + QVERIFY(effectsImpl->loadedEffects().isEmpty()); + + KWin::VirtualDesktopManager::self()->setCurrent(1); +} + +void ScriptedEffectsTest::testEffectsHandler() +{ + // this triggers and tests some of the signals in EffectHandler, which is exposed to JS as context property "effects" + auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean + QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); + auto waitFor = [&effectOutputSpy](const QString &expected) { + QVERIFY(effectOutputSpy.count() > 0 || effectOutputSpy.wait()); + QCOMPARE(effectOutputSpy.first().first(), expected); + effectOutputSpy.removeFirst(); + }; + QVERIFY(effect->load("effectsHandler")); + + // trigger windowAdded signal + + // create a window + using namespace KWayland::Client; + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + auto *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + shellSurface->set_title("WindowA"); + auto *c = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(c); + QCOMPARE(workspace()->activeWindow(), c); + + waitFor("windowAdded - WindowA"); + waitFor("stackingOrder - 1 WindowA"); + + // windowMinimsed + c->minimize(); + waitFor("windowMinimized - WindowA"); + + c->unminimize(); + waitFor("windowUnminimized - WindowA"); + + surface.reset(); + waitFor("windowClosed - WindowA"); + + // desktop management + KWin::VirtualDesktopManager::self()->setCurrent(2); + waitFor("desktopChanged - 1 2"); +} + +void ScriptedEffectsTest::testEffectsContext() +{ + // this tests misc non-objects exposed to the script engine: animationTime, displaySize, use of external enums + + auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean + QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); + QVERIFY(effect->load("effectContext")); + QCOMPARE(effectOutputSpy[0].first(), "1280x1024"); + QCOMPARE(effectOutputSpy[1].first(), "100"); + QCOMPARE(effectOutputSpy[2].first(), "2"); + QCOMPARE(effectOutputSpy[3].first(), "0"); +} + +void ScriptedEffectsTest::testShortcuts() +{ + // this tests method registerShortcut + auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean + QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); + QVERIFY(effect->load("shortcutsTest")); + QCOMPARE(effect->actions().count(), 1); + auto action = effect->actions()[0]; + QCOMPARE(action->objectName(), "testShortcut"); + QCOMPARE(action->text(), "Test Shortcut"); + QCOMPARE(KGlobalAccel::self()->shortcut(action).first(), QKeySequence("Meta+Shift+Y")); + action->trigger(); + QCOMPARE(effectOutputSpy[0].first(), "shortcutTriggered"); +} + +void ScriptedEffectsTest::testAnimations_data() +{ + QTest::addColumn("file"); + QTest::addColumn("animationCount"); + + QTest::newRow("single") << "animationTest" << 1; + QTest::newRow("multi") << "animationTestMulti" << 2; +} + +void ScriptedEffectsTest::testAnimations() +{ + // this tests animate/set/cancel + // methods take either an int or an array, as forced in the data above + // also splits animate vs effects.animate(..) + + QFETCH(QString, file); + QFETCH(int, animationCount); + + auto *effect = new ScriptedEffectWithDebugSpy; + QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); + QVERIFY(effect->load(file)); + + // animated after window added connect + using namespace KWayland::Client; + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + auto *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + shellSurface->set_title("Window 1"); + auto *c = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(c); + QCOMPARE(workspace()->activeWindow(), c); + + { + const auto state = effect->state(); + QCOMPARE(state.count(), 1); + QCOMPARE(state.firstKey(), c->effectWindow()); + const auto &animationsForWindow = state.first().first; + QCOMPARE(animationsForWindow.count(), animationCount); + QCOMPARE(animationsForWindow[0].timeLine.duration(), 100ms); + QCOMPARE(animationsForWindow[0].to, FPx2(1.4)); + QCOMPARE(animationsForWindow[0].attribute, AnimationEffect::Scale); + QCOMPARE(animationsForWindow[0].timeLine.easingCurve().type(), QEasingCurve::OutCubic); + QCOMPARE(animationsForWindow[0].terminationFlags, + AnimationEffect::TerminateAtSource | AnimationEffect::TerminateAtTarget); + + if (animationCount == 2) { + QCOMPARE(animationsForWindow[1].timeLine.duration(), 100ms); + QCOMPARE(animationsForWindow[1].to, FPx2(0.0)); + QCOMPARE(animationsForWindow[1].attribute, AnimationEffect::Opacity); + QCOMPARE(animationsForWindow[1].terminationFlags, + AnimationEffect::TerminateAtSource | AnimationEffect::TerminateAtTarget); + } + } + QCOMPARE(effectOutputSpy[0].first(), "true"); + + // window state changes, scale should be retargetted + + c->setMinimized(true); + { + const auto state = effect->state(); + QCOMPARE(state.count(), 1); + const auto &animationsForWindow = state.first().first; + QCOMPARE(animationsForWindow.count(), animationCount); + QCOMPARE(animationsForWindow[0].timeLine.duration(), 200ms); + QCOMPARE(animationsForWindow[0].to, FPx2(1.5)); + QCOMPARE(animationsForWindow[0].attribute, AnimationEffect::Scale); + QCOMPARE(animationsForWindow[0].terminationFlags, + AnimationEffect::TerminateAtSource | AnimationEffect::TerminateAtTarget); + if (animationCount == 2) { + QCOMPARE(animationsForWindow[1].timeLine.duration(), 200ms); + QCOMPARE(animationsForWindow[1].to, FPx2(1.5)); + QCOMPARE(animationsForWindow[1].attribute, AnimationEffect::Opacity); + QCOMPARE(animationsForWindow[1].terminationFlags, + AnimationEffect::TerminateAtSource | AnimationEffect::TerminateAtTarget); + } + } + c->setMinimized(false); + { + const auto state = effect->state(); + QCOMPARE(state.count(), 0); + } +} + +void ScriptedEffectsTest::testScreenEdge() +{ + // this test checks registerScreenEdge functions + auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean + QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); + QVERIFY(effect->load("screenEdgeTest")); + effect->borderActivated(KWin::ElectricTopRight); + QCOMPARE(effectOutputSpy.count(), 1); +} + +void ScriptedEffectsTest::testScreenEdgeTouch() +{ + // this test checks registerTouchScreenEdge functions + auto *effect = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean + QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); + QVERIFY(effect->load("screenEdgeTouchTest")); + effect->actions()[0]->trigger(); + QCOMPARE(effectOutputSpy.count(), 1); +} + +void ScriptedEffectsTest::testFullScreenEffect_data() +{ + QTest::addColumn("file"); + + QTest::newRow("single") << "fullScreenEffectTest"; + QTest::newRow("multi") << "fullScreenEffectTestMulti"; + QTest::newRow("global") << "fullScreenEffectTestGlobal"; +} + +void ScriptedEffectsTest::testFullScreenEffect() +{ + QFETCH(QString, file); + + auto *effectMain = new ScriptedEffectWithDebugSpy; // cleaned up in ::clean + QSignalSpy effectOutputSpy(effectMain, &ScriptedEffectWithDebugSpy::testOutput); + QSignalSpy fullScreenEffectActiveSpy(effects, &EffectsHandler::hasActiveFullScreenEffectChanged); + QSignalSpy isActiveFullScreenEffectSpy(effectMain, &ScriptedEffect::isActiveFullScreenEffectChanged); + + QVERIFY(effectMain->load(file)); + + // load any random effect from another test to confirm fullscreen effect state is correctly + // shown as being someone else + auto effectOther = new ScriptedEffectWithDebugSpy(); + QVERIFY(effectOther->load("screenEdgeTouchTest")); + QSignalSpy isActiveFullScreenEffectSpyOther(effectOther, &ScriptedEffect::isActiveFullScreenEffectChanged); + + using namespace KWayland::Client; + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + auto *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + shellSurface->set_title("Window 1"); + auto *c = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(c); + QCOMPARE(workspace()->activeWindow(), c); + + QCOMPARE(effects->hasActiveFullScreenEffect(), false); + QCOMPARE(effectMain->isActiveFullScreenEffect(), false); + + // trigger animation + KWin::VirtualDesktopManager::self()->setCurrent(2); + + QCOMPARE(effects->activeFullScreenEffect(), effectMain); + QCOMPARE(effects->hasActiveFullScreenEffect(), true); + QCOMPARE(fullScreenEffectActiveSpy.count(), 1); + + QCOMPARE(effectMain->isActiveFullScreenEffect(), true); + QCOMPARE(isActiveFullScreenEffectSpy.count(), 1); + + QCOMPARE(effectOther->isActiveFullScreenEffect(), false); + QCOMPARE(isActiveFullScreenEffectSpyOther.count(), 0); + + // after 500ms trigger another full screen animation + QTest::qWait(500); + KWin::VirtualDesktopManager::self()->setCurrent(1); + QCOMPARE(effects->activeFullScreenEffect(), effectMain); + + // after 1000ms (+a safety margin for time based tests) we should still be the active full screen effect + // despite first animation expiring + QTest::qWait(500 + 100); + QCOMPARE(effects->activeFullScreenEffect(), effectMain); + + // after 1500ms (+a safetey margin) we should have no full screen effect + QTest::qWait(500 + 100); + QCOMPARE(effects->activeFullScreenEffect(), nullptr); +} + +void ScriptedEffectsTest::testKeepAlive_data() +{ + QTest::addColumn("file"); + QTest::addColumn("keepAlive"); + + QTest::newRow("keep") << "keepAliveTest" << true; + QTest::newRow("don't keep") << "keepAliveTestDontKeep" << false; +} + +void ScriptedEffectsTest::testKeepAlive() +{ + // this test checks whether closed windows are kept alive + // when keepAlive property is set to true(false) + + QFETCH(QString, file); + QFETCH(bool, keepAlive); + + auto *effect = new ScriptedEffectWithDebugSpy; + QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); + QVERIFY(effect->load(file)); + + // create a window + using namespace KWayland::Client; + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + auto *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + auto *c = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(c); + QCOMPARE(workspace()->activeWindow(), c); + + // no active animations at the beginning + QCOMPARE(effect->state().count(), 0); + + // trigger windowClosed signal + surface.reset(); + QVERIFY(effectOutputSpy.count() == 1 || effectOutputSpy.wait()); + + if (keepAlive) { + QCOMPARE(effect->state().count(), 1); + + QTest::qWait(500); + QCOMPARE(effect->state().count(), 1); + + QTest::qWait(500 + 100); // 100ms is extra safety margin + QCOMPARE(effect->state().count(), 0); + } else { + // the test effect doesn't keep the window alive, so it should be + // removed immediately + QSignalSpy deletedRemovedSpy(workspace(), &Workspace::deletedRemoved); + QVERIFY(deletedRemovedSpy.count() == 1 || deletedRemovedSpy.wait(100)); // 100ms is less than duration of the animation + QCOMPARE(effect->state().count(), 0); + } +} + +void ScriptedEffectsTest::testGrab() +{ + // this test verifies that scripted effects can grab windows that are + // not already grabbed + + // load the test effect + auto effect = new ScriptedEffectWithDebugSpy; + QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); + QVERIFY(effect->load(QStringLiteral("grabTest"))); + + // create test window + using namespace KWayland::Client; + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QCOMPARE(workspace()->activeWindow(), window); + + // the test effect should grab the test window successfully + QCOMPARE(effectOutputSpy.count(), 1); + QCOMPARE(effectOutputSpy.first().first(), QStringLiteral("ok")); + QCOMPARE(window->effectWindow()->data(WindowAddedGrabRole).value(), effect); +} + +void ScriptedEffectsTest::testGrabAlreadyGrabbedWindow() +{ + // this test verifies that scripted effects cannot grab already grabbed + // windows (unless force is set to true of course) + + // load effect that will hold the window grab + auto owner = new ScriptedEffectWithDebugSpy; + QSignalSpy ownerOutputSpy(owner, &ScriptedEffectWithDebugSpy::testOutput); + QVERIFY(owner->load(QStringLiteral("grabAlreadyGrabbedWindowTest_owner"))); + + // load effect that will try to grab already grabbed window + auto grabber = new ScriptedEffectWithDebugSpy; + QSignalSpy grabberOutputSpy(grabber, &ScriptedEffectWithDebugSpy::testOutput); + QVERIFY(grabber->load(QStringLiteral("grabAlreadyGrabbedWindowTest_grabber"))); + + // create test window + using namespace KWayland::Client; + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QCOMPARE(workspace()->activeWindow(), window); + + // effect that initially held the grab should still hold the grab + QCOMPARE(ownerOutputSpy.count(), 1); + QCOMPARE(ownerOutputSpy.first().first(), QStringLiteral("ok")); + QCOMPARE(window->effectWindow()->data(WindowAddedGrabRole).value(), owner); + + // effect that tried to grab already grabbed window should fail miserably + QCOMPARE(grabberOutputSpy.count(), 1); + QCOMPARE(grabberOutputSpy.first().first(), QStringLiteral("fail")); +} + +void ScriptedEffectsTest::testGrabAlreadyGrabbedWindowForced() +{ + // this test verifies that scripted effects can steal window grabs when + // they forcefully try to grab windows + + // load effect that initially will be holding the window grab + auto owner = new ScriptedEffectWithDebugSpy; + QSignalSpy ownerOutputSpy(owner, &ScriptedEffectWithDebugSpy::testOutput); + QVERIFY(owner->load(QStringLiteral("grabAlreadyGrabbedWindowForcedTest_owner"))); + + // load effect that will try to steal the window grab + auto thief = new ScriptedEffectWithDebugSpy; + QSignalSpy thiefOutputSpy(thief, &ScriptedEffectWithDebugSpy::testOutput); + QVERIFY(thief->load(QStringLiteral("grabAlreadyGrabbedWindowForcedTest_thief"))); + + // create test window + using namespace KWayland::Client; + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QCOMPARE(workspace()->activeWindow(), window); + + // verify that the owner in fact held the grab + QCOMPARE(ownerOutputSpy.count(), 1); + QCOMPARE(ownerOutputSpy.first().first(), QStringLiteral("ok")); + + // effect that grabbed the test window forcefully should now hold the grab + QCOMPARE(thiefOutputSpy.count(), 1); + QCOMPARE(thiefOutputSpy.first().first(), QStringLiteral("ok")); + QCOMPARE(window->effectWindow()->data(WindowAddedGrabRole).value(), thief); +} + +void ScriptedEffectsTest::testUngrab() +{ + // this test verifies that scripted effects can ungrab windows that they + // are previously grabbed + + // load the test effect + auto effect = new ScriptedEffectWithDebugSpy; + QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); + QVERIFY(effect->load(QStringLiteral("ungrabTest"))); + + // create test window + using namespace KWayland::Client; + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QCOMPARE(workspace()->activeWindow(), window); + + // the test effect should grab the test window successfully + QCOMPARE(effectOutputSpy.count(), 1); + QCOMPARE(effectOutputSpy.first().first(), QStringLiteral("ok")); + QCOMPARE(window->effectWindow()->data(WindowAddedGrabRole).value(), effect); + + // when the test effect sees that a window was minimized, it will try to ungrab it + effectOutputSpy.clear(); + window->setMinimized(true); + + QCOMPARE(effectOutputSpy.count(), 1); + QCOMPARE(effectOutputSpy.first().first(), QStringLiteral("ok")); + QCOMPARE(window->effectWindow()->data(WindowAddedGrabRole).value(), nullptr); +} + +void ScriptedEffectsTest::testRedirect_data() +{ + QTest::addColumn("file"); + QTest::addColumn("shouldTerminate"); + QTest::newRow("animate/DontTerminateAtSource") << "redirectAnimateDontTerminateTest" << false; + QTest::newRow("animate/TerminateAtSource") << "redirectAnimateTerminateTest" << true; + QTest::newRow("set/DontTerminate") << "redirectSetDontTerminateTest" << false; + QTest::newRow("set/Terminate") << "redirectSetTerminateTest" << true; +} + +void ScriptedEffectsTest::testRedirect() +{ + // this test verifies that redirect() works + + // load the test effect + auto effect = new ScriptedEffectWithDebugSpy; + QFETCH(QString, file); + QVERIFY(effect->load(file)); + + // create test window + using namespace KWayland::Client; + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QCOMPARE(workspace()->activeWindow(), window); + + auto around = [](std::chrono::milliseconds elapsed, + std::chrono::milliseconds pivot, + std::chrono::milliseconds margin) { + return qAbs(elapsed.count() - pivot.count()) < margin.count(); + }; + + // initially, the test animation is at the source position + + { + const auto state = effect->state(); + QCOMPARE(state.count(), 1); + QCOMPARE(state.firstKey(), window->effectWindow()); + const QList animations = state.first().first; + QCOMPARE(animations.count(), 1); + QCOMPARE(animations[0].timeLine.direction(), TimeLine::Forward); + QVERIFY(around(animations[0].timeLine.elapsed(), 0ms, 50ms)); + } + + // minimize the test window after 250ms, when the test effect sees that + // a window was minimized, it will try to reverse animation for it + QTest::qWait(250); + + QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); + + window->setMinimized(true); + + QCOMPARE(effectOutputSpy.count(), 1); + QCOMPARE(effectOutputSpy.first().first(), QStringLiteral("ok")); + + { + const auto state = effect->state(); + QCOMPARE(state.count(), 1); + QCOMPARE(state.firstKey(), window->effectWindow()); + const QList animations = state.first().first; + QCOMPARE(animations.count(), 1); + QCOMPARE(animations[0].timeLine.direction(), TimeLine::Backward); + QVERIFY(around(animations[0].timeLine.elapsed(), 1000ms - 250ms, 50ms)); + } + + // wait for the animation to reach the start position, 100ms is an extra + // safety margin + QTest::qWait(250 + 100); + + QFETCH(bool, shouldTerminate); + if (shouldTerminate) { + const auto state = effect->state(); + QCOMPARE(state.count(), 0); + } else { + const auto state = effect->state(); + QCOMPARE(state.count(), 1); + QCOMPARE(state.firstKey(), window->effectWindow()); + const QList animations = state.first().first; + QCOMPARE(animations.count(), 1); + QCOMPARE(animations[0].timeLine.direction(), TimeLine::Backward); + QCOMPARE(animations[0].timeLine.elapsed(), 1000ms); + QCOMPARE(animations[0].timeLine.value(), 0.0); + } +} + +void ScriptedEffectsTest::testComplete() +{ + // this test verifies that complete works + + // load the test effect + auto effect = new ScriptedEffectWithDebugSpy; + QVERIFY(effect->load(QStringLiteral("completeTest"))); + + // create test window + using namespace KWayland::Client; + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QCOMPARE(workspace()->activeWindow(), window); + + auto around = [](std::chrono::milliseconds elapsed, + std::chrono::milliseconds pivot, + std::chrono::milliseconds margin) { + return qAbs(elapsed.count() - pivot.count()) < margin.count(); + }; + + // initially, the test animation should be at the start position + { + const auto state = effect->state(); + QCOMPARE(state.count(), 1); + QCOMPARE(state.firstKey(), window->effectWindow()); + const QList animations = state.first().first; + QCOMPARE(animations.count(), 1); + QVERIFY(around(animations[0].timeLine.elapsed(), 0ms, 50ms)); + QVERIFY(!animations[0].timeLine.done()); + } + + // wait for 250ms + QTest::qWait(250); + + { + const auto state = effect->state(); + QCOMPARE(state.count(), 1); + QCOMPARE(state.firstKey(), window->effectWindow()); + const QList animations = state.first().first; + QCOMPARE(animations.count(), 1); + QVERIFY(around(animations[0].timeLine.elapsed(), 250ms, 50ms)); + QVERIFY(!animations[0].timeLine.done()); + } + + // minimize the test window, when the test effect sees that a window was + // minimized, it will try to complete animation for it + QSignalSpy effectOutputSpy(effect, &ScriptedEffectWithDebugSpy::testOutput); + + window->setMinimized(true); + + QCOMPARE(effectOutputSpy.count(), 1); + QCOMPARE(effectOutputSpy.first().first(), QStringLiteral("ok")); + + { + const auto state = effect->state(); + QCOMPARE(state.count(), 1); + QCOMPARE(state.firstKey(), window->effectWindow()); + const QList animations = state.first().first; + QCOMPARE(animations.count(), 1); + QCOMPARE(animations[0].timeLine.elapsed(), 1000ms); + QVERIFY(animations[0].timeLine.done()); + } +} + +WAYLANDTEST_MAIN(ScriptedEffectsTest) +#include "scripted_effects_test.moc" diff --git a/autotests/integration/effects/scripts/animationTest.js b/autotests/integration/effects/scripts/animationTest.js new file mode 100644 index 0000000..29b9663 --- /dev/null +++ b/autotests/integration/effects/scripts/animationTest.js @@ -0,0 +1,12 @@ +effects.windowAdded.connect(function(w) { + w.anim1 = effect.animate(w, Effect.Scale, 100, 1.4, 0.2, 0, QEasingCurve.OutCubic); + sendTestResponse(typeof(w.anim1) == "number"); +}); + +effects.windowUnminimized.connect(function(w) { + cancel(w.anim1); +}); + +effects.windowMinimized.connect(function(w) { + retarget(w.anim1, 1.5, 200); +}); diff --git a/autotests/integration/effects/scripts/animationTestMulti.js b/autotests/integration/effects/scripts/animationTestMulti.js new file mode 100644 index 0000000..ab77286 --- /dev/null +++ b/autotests/integration/effects/scripts/animationTestMulti.js @@ -0,0 +1,24 @@ +effects.windowAdded.connect(function(w) { + w.anim1 = animate({ + window: w, + duration: 100, + animations: [{ + type: Effect.Scale, + to: 1.4, + curve: QEasingCurve.OutCubic + }, { + type: Effect.Opacity, + curve: QEasingCurve.OutCubic, + to: 0.0 + }] + }); + sendTestResponse(typeof(w.anim1) == "object" && Array.isArray(w.anim1)); +}); + +effects.windowUnminimized.connect(function(w) { + cancel(w.anim1); +}); + +effects.windowMinimized.connect(function(w) { + retarget(w.anim1, 1.5, 200); +}); diff --git a/autotests/integration/effects/scripts/completeTest.js b/autotests/integration/effects/scripts/completeTest.js new file mode 100644 index 0000000..af9a70c --- /dev/null +++ b/autotests/integration/effects/scripts/completeTest.js @@ -0,0 +1,19 @@ +effects.windowAdded.connect(function (window) { + window.animation = set({ + window: window, + curve: QEasingCurve.Linear, + duration: animationTime(1000), + type: Effect.Opacity, + from: 0, + to: 1, + keepAlive: false + }); +}); + +effects.windowMinimized.connect(function (window) { + if (complete(window.animation)) { + sendTestResponse('ok'); + } else { + sendTestResponse('fail'); + } +}); diff --git a/autotests/integration/effects/scripts/effectContext.js b/autotests/integration/effects/scripts/effectContext.js new file mode 100644 index 0000000..193afab --- /dev/null +++ b/autotests/integration/effects/scripts/effectContext.js @@ -0,0 +1,6 @@ +sendTestResponse(displayWidth() + "x" + displayHeight()); +sendTestResponse(animationTime(100)); + +//test enums for Effect / QEasingCurve +sendTestResponse(Effect.Saturation) +sendTestResponse(QEasingCurve.Linear) diff --git a/autotests/integration/effects/scripts/effectsHandler.js b/autotests/integration/effects/scripts/effectsHandler.js new file mode 100644 index 0000000..661b181 --- /dev/null +++ b/autotests/integration/effects/scripts/effectsHandler.js @@ -0,0 +1,16 @@ +effects.windowAdded.connect(function(window) { + sendTestResponse("windowAdded - " + window.caption); + sendTestResponse("stackingOrder - " + effects.stackingOrder.length + " " + effects.stackingOrder[0].caption); +}); +effects.windowClosed.connect(function(window) { + sendTestResponse("windowClosed - " + window.caption); +}); +effects.windowMinimized.connect(function(window) { + sendTestResponse("windowMinimized - " + window.caption); +}); +effects.windowUnminimized.connect(function(window) { + sendTestResponse("windowUnminimized - " + window.caption); +}); +effects['desktopChanged(int,int)'].connect(function(old, current) { + sendTestResponse("desktopChanged - " + old + " " + current); +}); diff --git a/autotests/integration/effects/scripts/fullScreenEffectTest.js b/autotests/integration/effects/scripts/fullScreenEffectTest.js new file mode 100644 index 0000000..014ef47 --- /dev/null +++ b/autotests/integration/effects/scripts/fullScreenEffectTest.js @@ -0,0 +1,8 @@ +effects['desktopChanged(int,int)'].connect(function(old, current) { + var stackingOrder = effects.stackingOrder; + for (var i=0; i + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "composite.h" +#include "core/platform.h" +#include "core/renderbackend.h" +#include "cursor.h" +#include "deleted.h" +#include "effectloader.h" +#include "effects.h" +#include "kwin_wayland_test.h" +#include "wayland_server.h" +#include "workspace.h" +#include "x11window.h" + +#include + +#include +#include +#include +#include + +#include +#include + +using namespace KWin; +static const QString s_socketName = QStringLiteral("wayland_test_effects_slidingpopups-0"); + +class SlidingPopupsTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testWithOtherEffect_data(); + void testWithOtherEffect(); + void testWithOtherEffectWayland_data(); + void testWithOtherEffectWayland(); +}; + +void SlidingPopupsTest::initTestCase() +{ + qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + // disable all effects - we don't want to have it interact with the rendering + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup plugins(config, QStringLiteral("Plugins")); + const auto builtinNames = EffectLoader().listOfKnownEffects(); + for (QString name : builtinNames) { + plugins.writeEntry(name + QStringLiteral("Enabled"), false); + } + KConfigGroup wobblyGroup = config->group("Effect-Wobbly"); + wobblyGroup.writeEntry(QStringLiteral("Settings"), QStringLiteral("Custom")); + wobblyGroup.writeEntry(QStringLiteral("OpenEffect"), true); + wobblyGroup.writeEntry(QStringLiteral("CloseEffect"), true); + + config->sync(); + kwinApp()->setConfig(config); + + qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2")); + qputenv("KWIN_EFFECTS_FORCE_ANIMATIONS", "1"); + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + QVERIFY(Compositor::self()); + + QCOMPARE(Compositor::self()->backend()->compositingType(), KWin::OpenGLCompositing); +} + +void SlidingPopupsTest::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration)); +} + +void SlidingPopupsTest::cleanup() +{ + Test::destroyWaylandConnection(); + EffectsHandlerImpl *e = static_cast(effects); + while (!e->loadedEffects().isEmpty()) { + const QString effect = e->loadedEffects().first(); + e->unloadEffect(effect); + QVERIFY(!e->isEffectLoaded(effect)); + } +} + +struct XcbConnectionDeleter +{ + void operator()(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } +}; + +void SlidingPopupsTest::testWithOtherEffect_data() +{ + QTest::addColumn("effectsToLoad"); + + QTest::newRow("fade, slide") << QStringList{QStringLiteral("kwin4_effect_fade"), QStringLiteral("slidingpopups")}; + QTest::newRow("slide, fade") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("kwin4_effect_fade")}; + QTest::newRow("scale, slide") << QStringList{QStringLiteral("kwin4_effect_scale"), QStringLiteral("slidingpopups")}; + QTest::newRow("slide, scale") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("kwin4_effect_scale")}; + + if (effects->compositingType() & KWin::OpenGLCompositing) { + QTest::newRow("glide, slide") << QStringList{QStringLiteral("glide"), QStringLiteral("slidingpopups")}; + QTest::newRow("slide, glide") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("glide")}; + QTest::newRow("wobblywindows, slide") << QStringList{QStringLiteral("wobblywindows"), QStringLiteral("slidingpopups")}; + QTest::newRow("slide, wobblywindows") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("wobblywindows")}; + QTest::newRow("fallapart, slide") << QStringList{QStringLiteral("fallapart"), QStringLiteral("slidingpopups")}; + QTest::newRow("slide, fallapart") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("fallapart")}; + } +} + +void SlidingPopupsTest::testWithOtherEffect() +{ + // this test verifies that slidingpopups effect grabs the window added role + // independently of the sequence how the effects are loaded. + // see BUG 336866 + EffectsHandlerImpl *e = static_cast(effects); + // find the effectsloader + auto effectloader = e->findChild(); + QVERIFY(effectloader); + QSignalSpy effectLoadedSpy(effectloader, &AbstractEffectLoader::effectLoaded); + + Effect *slidingPoupus = nullptr; + Effect *otherEffect = nullptr; + QFETCH(QStringList, effectsToLoad); + for (const QString &effectName : effectsToLoad) { + QVERIFY(!e->isEffectLoaded(effectName)); + QVERIFY(e->loadEffect(effectName)); + QVERIFY(e->isEffectLoaded(effectName)); + + QCOMPARE(effectLoadedSpy.count(), 1); + Effect *effect = effectLoadedSpy.first().first().value(); + if (effectName == QStringLiteral("slidingpopups")) { + slidingPoupus = effect; + } else { + otherEffect = effect; + } + effectLoadedSpy.clear(); + } + QVERIFY(slidingPoupus); + QVERIFY(otherEffect); + + QVERIFY(!slidingPoupus->isActive()); + QVERIFY(!otherEffect->isActive()); + + // give the compositor some time to render + QTest::qWait(50); + + QSignalSpy windowAddedSpy(effects, &EffectsHandler::windowAdded); + + // create an xcb window + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + NETWinInfo winInfo(c.get(), windowId, rootWindow(), NET::Properties(), NET::Properties2()); + winInfo.setWindowType(NET::Normal); + + // and get the slide atom + const QByteArray effectAtomName = QByteArrayLiteral("_KDE_SLIDE"); + xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c.get(), false, effectAtomName.length(), effectAtomName.constData()); + const int size = 2; + int32_t data[size]; + data[0] = 0; + data[1] = 0; + UniqueCPtr atom(xcb_intern_atom_reply(c.get(), atomCookie, nullptr)); + QVERIFY(atom != nullptr); + xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atom->atom, atom->atom, 32, size, data); + + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QVERIFY(window->isNormalWindow()); + + // sliding popups should be active + QVERIFY(windowAddedSpy.wait()); + QTRY_VERIFY(slidingPoupus->isActive()); + QVERIFY(!otherEffect->isActive()); + + // wait till effect ends + QTRY_VERIFY(!slidingPoupus->isActive()); + QTest::qWait(300); + QVERIFY(!otherEffect->isActive()); + + // and destroy the window again + xcb_unmap_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + + QSignalSpy windowDeletedSpy(effects, &EffectsHandler::windowDeleted); + QVERIFY(windowClosedSpy.wait()); + + // again we should have the sliding popups active + QVERIFY(slidingPoupus->isActive()); + QVERIFY(!otherEffect->isActive()); + + QVERIFY(windowDeletedSpy.wait()); + + QCOMPARE(windowDeletedSpy.count(), 1); + QTRY_VERIFY(!slidingPoupus->isActive()); + QTest::qWait(300); + QVERIFY(!otherEffect->isActive()); + xcb_destroy_window(c.get(), windowId); + c.reset(); +} + +void SlidingPopupsTest::testWithOtherEffectWayland_data() +{ + QTest::addColumn("effectsToLoad"); + + QTest::newRow("fade, slide") << QStringList{QStringLiteral("kwin4_effect_fade"), QStringLiteral("slidingpopups")}; + QTest::newRow("slide, fade") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("kwin4_effect_fade")}; + QTest::newRow("scale, slide") << QStringList{QStringLiteral("kwin4_effect_scale"), QStringLiteral("slidingpopups")}; + QTest::newRow("slide, scale") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("kwin4_effect_scale")}; + + if (effects->compositingType() & KWin::OpenGLCompositing) { + QTest::newRow("glide, slide") << QStringList{QStringLiteral("glide"), QStringLiteral("slidingpopups")}; + QTest::newRow("slide, glide") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("glide")}; + QTest::newRow("wobblywindows, slide") << QStringList{QStringLiteral("wobblywindows"), QStringLiteral("slidingpopups")}; + QTest::newRow("slide, wobblywindows") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("wobblywindows")}; + QTest::newRow("fallapart, slide") << QStringList{QStringLiteral("fallapart"), QStringLiteral("slidingpopups")}; + QTest::newRow("slide, fallapart") << QStringList{QStringLiteral("slidingpopups"), QStringLiteral("fallapart")}; + } +} + +void SlidingPopupsTest::testWithOtherEffectWayland() +{ + // this test verifies that slidingpopups effect grabs the window added role + // independently of the sequence how the effects are loaded. + // see BUG 336866 + // the test is like testWithOtherEffect, but simulates using a Wayland window + EffectsHandlerImpl *e = static_cast(effects); + // find the effectsloader + auto effectloader = e->findChild(); + QVERIFY(effectloader); + QSignalSpy effectLoadedSpy(effectloader, &AbstractEffectLoader::effectLoaded); + + Effect *slidingPoupus = nullptr; + Effect *otherEffect = nullptr; + QFETCH(QStringList, effectsToLoad); + for (const QString &effectName : effectsToLoad) { + QVERIFY(!e->isEffectLoaded(effectName)); + QVERIFY(e->loadEffect(effectName)); + QVERIFY(e->isEffectLoaded(effectName)); + + QCOMPARE(effectLoadedSpy.count(), 1); + Effect *effect = effectLoadedSpy.first().first().value(); + if (effectName == QStringLiteral("slidingpopups")) { + slidingPoupus = effect; + } else { + otherEffect = effect; + } + effectLoadedSpy.clear(); + } + QVERIFY(slidingPoupus); + QVERIFY(otherEffect); + + QVERIFY(!slidingPoupus->isActive()); + QVERIFY(!otherEffect->isActive()); + QSignalSpy windowAddedSpy(effects, &EffectsHandler::windowAdded); + + using namespace KWayland::Client; + // the test created the slide protocol, let's create a Registry and listen for it + std::unique_ptr registry(new Registry); + registry->create(Test::waylandConnection()); + + QSignalSpy interfacesAnnouncedSpy(registry.get(), &Registry::interfacesAnnounced); + registry->setup(); + QVERIFY(interfacesAnnouncedSpy.wait()); + auto slideInterface = registry->interface(Registry::Interface::Slide); + QVERIFY(slideInterface.name != 0); + std::unique_ptr slideManager(registry->createSlideManager(slideInterface.name, slideInterface.version)); + QVERIFY(slideManager); + + // create Wayland window + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface); + std::unique_ptr slide(slideManager->createSlide(surface.get())); + slide->setLocation(Slide::Location::Left); + slide->commit(); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface); + QCOMPARE(windowAddedSpy.count(), 0); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(10, 20), Qt::blue); + QVERIFY(window); + QVERIFY(window->isNormalWindow()); + + // sliding popups should be active + QCOMPARE(windowAddedSpy.count(), 1); + QTRY_VERIFY(slidingPoupus->isActive()); + QVERIFY(!otherEffect->isActive()); + + // wait till effect ends + QTRY_VERIFY(!slidingPoupus->isActive()); + QTest::qWait(300); + QVERIFY(!otherEffect->isActive()); + + // and destroy the window again + shellSurface.reset(); + surface.reset(); + + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + + QSignalSpy windowDeletedSpy(effects, &EffectsHandler::windowDeleted); + QVERIFY(windowClosedSpy.wait()); + + // again we should have the sliding popups active + QVERIFY(slidingPoupus->isActive()); + QVERIFY(!otherEffect->isActive()); + + QVERIFY(windowDeletedSpy.wait()); + + QCOMPARE(windowDeletedSpy.count(), 1); + QTRY_VERIFY(!slidingPoupus->isActive()); + QTest::qWait(300); + QVERIFY(!otherEffect->isActive()); +} + +WAYLANDTEST_MAIN(SlidingPopupsTest) +#include "slidingpopups_test.moc" diff --git a/autotests/integration/effects/toplevel_open_close_animation_test.cpp b/autotests/integration/effects/toplevel_open_close_animation_test.cpp new file mode 100644 index 0000000..9ec1fd9 --- /dev/null +++ b/autotests/integration/effects/toplevel_open_close_animation_test.cpp @@ -0,0 +1,204 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2018 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "kwin_wayland_test.h" + +#include "composite.h" +#include "core/platform.h" +#include "core/renderbackend.h" +#include "deleted.h" +#include "effectloader.h" +#include "effects.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include + +using namespace KWin; + +static const QString s_socketName = QStringLiteral("wayland_test_effects_toplevel_open_close_animation-0"); + +class ToplevelOpenCloseAnimationTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testAnimateToplevels_data(); + void testAnimateToplevels(); + void testDontAnimatePopups_data(); + void testDontAnimatePopups(); +}; + +void ToplevelOpenCloseAnimationTest::initTestCase() +{ + qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); + + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup plugins(config, QStringLiteral("Plugins")); + const auto builtinNames = EffectLoader().listOfKnownEffects(); + for (const QString &name : builtinNames) { + plugins.writeEntry(name + QStringLiteral("Enabled"), false); + } + config->sync(); + kwinApp()->setConfig(config); + + qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2")); + qputenv("KWIN_EFFECTS_FORCE_ANIMATIONS", QByteArrayLiteral("1")); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + + QCOMPARE(Compositor::self()->backend()->compositingType(), KWin::OpenGLCompositing); +} + +void ToplevelOpenCloseAnimationTest::init() +{ + QVERIFY(Test::setupWaylandConnection()); +} + +void ToplevelOpenCloseAnimationTest::cleanup() +{ + auto effectsImpl = qobject_cast(effects); + QVERIFY(effectsImpl); + effectsImpl->unloadAllEffects(); + QVERIFY(effectsImpl->loadedEffects().isEmpty()); + + Test::destroyWaylandConnection(); +} + +void ToplevelOpenCloseAnimationTest::testAnimateToplevels_data() +{ + QTest::addColumn("effectName"); + + QTest::newRow("Fade") << QStringLiteral("kwin4_effect_fade"); + QTest::newRow("Glide") << QStringLiteral("glide"); + QTest::newRow("Scale") << QStringLiteral("kwin4_effect_scale"); +} + +void ToplevelOpenCloseAnimationTest::testAnimateToplevels() +{ + // This test verifies that window open/close animation effects try to + // animate the appearing and the disappearing of toplevel windows. + + // Make sure that we have the right effects ptr. + auto effectsImpl = qobject_cast(effects); + QVERIFY(effectsImpl); + + // Load effect that will be tested. + QFETCH(QString, effectName); + QVERIFY(effectsImpl->loadEffect(effectName)); + QCOMPARE(effectsImpl->loadedEffects().count(), 1); + QCOMPARE(effectsImpl->loadedEffects().first(), effectName); + Effect *effect = effectsImpl->findEffect(effectName); + QVERIFY(effect); + QVERIFY(!effect->isActive()); + + // Create the test window. + using namespace KWayland::Client; + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(effect->isActive()); + + // Eventually, the animation will be complete. + QTRY_VERIFY(!effect->isActive()); + + // Close the test window, the effect should start animating the disappearing + // of the window. + QSignalSpy windowClosedSpy(window, &Window::windowClosed); + shellSurface.reset(); + surface.reset(); + QVERIFY(windowClosedSpy.wait()); + QVERIFY(effect->isActive()); + + // Eventually, the animation will be complete. + QTRY_VERIFY(!effect->isActive()); +} + +void ToplevelOpenCloseAnimationTest::testDontAnimatePopups_data() +{ + QTest::addColumn("effectName"); + + QTest::newRow("Fade") << QStringLiteral("kwin4_effect_fade"); + QTest::newRow("Glide") << QStringLiteral("glide"); + QTest::newRow("Scale") << QStringLiteral("kwin4_effect_scale"); +} + +void ToplevelOpenCloseAnimationTest::testDontAnimatePopups() +{ + // This test verifies that window open/close animation effects don't try + // to animate popups(e.g. popup menus, tooltips, etc). + + // Make sure that we have the right effects ptr. + auto effectsImpl = qobject_cast(effects); + QVERIFY(effectsImpl); + + // Create the main window. + using namespace KWayland::Client; + std::unique_ptr mainWindowSurface(Test::createSurface()); + QVERIFY(mainWindowSurface != nullptr); + std::unique_ptr mainWindowShellSurface(Test::createXdgToplevelSurface(mainWindowSurface.get())); + QVERIFY(mainWindowShellSurface != nullptr); + Window *mainWindow = Test::renderAndWaitForShown(mainWindowSurface.get(), QSize(100, 50), Qt::blue); + QVERIFY(mainWindow); + + // Load effect that will be tested. + QFETCH(QString, effectName); + QVERIFY(effectsImpl->loadEffect(effectName)); + QCOMPARE(effectsImpl->loadedEffects().count(), 1); + QCOMPARE(effectsImpl->loadedEffects().first(), effectName); + Effect *effect = effectsImpl->findEffect(effectName); + QVERIFY(effect); + QVERIFY(!effect->isActive()); + + // Create a popup, it should not be animated. + std::unique_ptr popupSurface(Test::createSurface()); + QVERIFY(popupSurface != nullptr); + std::unique_ptr positioner(Test::createXdgPositioner()); + QVERIFY(positioner); + positioner->set_size(20, 20); + positioner->set_anchor_rect(0, 0, 10, 10); + positioner->set_gravity(Test::XdgPositioner::gravity_bottom_right); + positioner->set_anchor(Test::XdgPositioner::anchor_bottom_left); + std::unique_ptr popupShellSurface(Test::createXdgPopupSurface(popupSurface.get(), mainWindowShellSurface->xdgSurface(), positioner.get())); + QVERIFY(popupShellSurface != nullptr); + Window *popup = Test::renderAndWaitForShown(popupSurface.get(), QSize(20, 20), Qt::red); + QVERIFY(popup); + QVERIFY(popup->isPopupWindow()); + QCOMPARE(popup->transientFor(), mainWindow); + QVERIFY(!effect->isActive()); + + // Destroy the popup, it should not be animated. + QSignalSpy popupClosedSpy(popup, &Window::windowClosed); + popupShellSurface.reset(); + popupSurface.reset(); + QVERIFY(popupClosedSpy.wait()); + QVERIFY(!effect->isActive()); + + // Destroy the main window. + mainWindowSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(mainWindow)); +} + +WAYLANDTEST_MAIN(ToplevelOpenCloseAnimationTest) +#include "toplevel_open_close_animation_test.moc" diff --git a/autotests/integration/effects/translucency_test.cpp b/autotests/integration/effects/translucency_test.cpp new file mode 100644 index 0000000..e76f522 --- /dev/null +++ b/autotests/integration/effects/translucency_test.cpp @@ -0,0 +1,227 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "composite.h" +#include "core/platform.h" +#include "cursor.h" +#include "effectloader.h" +#include "effects.h" +#include "wayland_server.h" +#include "workspace.h" +#include "x11window.h" + +#include + +#include +#include + +using namespace KWin; +static const QString s_socketName = QStringLiteral("wayland_test_effects_translucency-0"); + +class TranslucencyTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testMoveAfterDesktopChange(); + void testDialogClose(); + +private: + Effect *m_translucencyEffect = nullptr; +}; + +void TranslucencyTest::initTestCase() +{ + qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + // disable all effects - we don't want to have it interact with the rendering + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup plugins(config, QStringLiteral("Plugins")); + const auto builtinNames = EffectLoader().listOfKnownEffects(); + for (QString name : builtinNames) { + plugins.writeEntry(name + QStringLiteral("Enabled"), false); + } + config->group("Outline").writeEntry(QStringLiteral("QmlPath"), QString("/does/not/exist.qml")); + config->group("Effect-kwin4_effect_translucency").writeEntry(QStringLiteral("Dialogs"), 90); + + config->sync(); + kwinApp()->setConfig(config); + + qputenv("KWIN_EFFECTS_FORCE_ANIMATIONS", "1"); + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + QVERIFY(Compositor::self()); +} + +void TranslucencyTest::init() +{ + // load the translucency effect + EffectsHandlerImpl *e = static_cast(effects); + // find the effectsloader + auto effectloader = e->findChild(); + QVERIFY(effectloader); + QSignalSpy effectLoadedSpy(effectloader, &AbstractEffectLoader::effectLoaded); + + QVERIFY(!e->isEffectLoaded(QStringLiteral("kwin4_effect_translucency"))); + QVERIFY(e->loadEffect(QStringLiteral("kwin4_effect_translucency"))); + QVERIFY(e->isEffectLoaded(QStringLiteral("kwin4_effect_translucency"))); + + QCOMPARE(effectLoadedSpy.count(), 1); + m_translucencyEffect = effectLoadedSpy.first().first().value(); + QVERIFY(m_translucencyEffect); +} + +void TranslucencyTest::cleanup() +{ + EffectsHandlerImpl *e = static_cast(effects); + if (e->isEffectLoaded(QStringLiteral("kwin4_effect_translucency"))) { + e->unloadEffect(QStringLiteral("kwin4_effect_translucency")); + } + QVERIFY(!e->isEffectLoaded(QStringLiteral("kwin4_effect_translucency"))); + m_translucencyEffect = nullptr; +} + +struct XcbConnectionDeleter +{ + void operator()(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } +}; + +void TranslucencyTest::testMoveAfterDesktopChange() +{ + // test tries to simulate the condition of bug 366081 + QVERIFY(!m_translucencyEffect->isActive()); + + QSignalSpy windowAddedSpy(effects, &EffectsHandler::windowAdded); + + // create an xcb window + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QVERIFY(window->isDecorated()); + + QVERIFY(windowAddedSpy.wait()); + QVERIFY(!m_translucencyEffect->isActive()); + // let's send the window to desktop 2 + effects->setNumberOfDesktops(2); + QCOMPARE(effects->numberOfDesktops(), 2); + workspace()->sendWindowToDesktop(window, 2, false); + effects->setCurrentDesktop(2); + QVERIFY(!m_translucencyEffect->isActive()); + KWin::Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + workspace()->performWindowOperation(window, Options::MoveOp); + QVERIFY(m_translucencyEffect->isActive()); + QTest::qWait(200); + QVERIFY(m_translucencyEffect->isActive()); + // now end move resize + window->endInteractiveMoveResize(); + QVERIFY(m_translucencyEffect->isActive()); + QTest::qWait(500); + QTRY_VERIFY(!m_translucencyEffect->isActive()); + + // and destroy the window again + xcb_unmap_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + QVERIFY(windowClosedSpy.wait()); + xcb_destroy_window(c.get(), windowId); + c.reset(); +} + +void TranslucencyTest::testDialogClose() +{ + // this test simulates the condition of BUG 342716 + // with translucency settings for window type dialog the effect never ends when the window gets destroyed + QVERIFY(!m_translucencyEffect->isActive()); + QSignalSpy windowAddedSpy(effects, &EffectsHandler::windowAdded); + + // create an xcb window + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + NETWinInfo winInfo(c.get(), windowId, rootWindow(), NET::Properties(), NET::Properties2()); + winInfo.setWindowType(NET::Dialog); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QVERIFY(window->isDecorated()); + QVERIFY(window->isDialog()); + + QVERIFY(windowAddedSpy.wait()); + QTRY_VERIFY(m_translucencyEffect->isActive()); + // and destroy the window again + xcb_unmap_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + + QSignalSpy windowDeletedSpy(effects, &EffectsHandler::windowDeleted); + QVERIFY(windowClosedSpy.wait()); + if (windowDeletedSpy.isEmpty()) { + QVERIFY(windowDeletedSpy.wait()); + } + QCOMPARE(windowDeletedSpy.count(), 1); + QTRY_VERIFY(!m_translucencyEffect->isActive()); + xcb_destroy_window(c.get(), windowId); + c.reset(); +} + +WAYLANDTEST_MAIN(TranslucencyTest) +#include "translucency_test.moc" diff --git a/autotests/integration/effects/wobbly_shade_test.cpp b/autotests/integration/effects/wobbly_shade_test.cpp new file mode 100644 index 0000000..2d01b05 --- /dev/null +++ b/autotests/integration/effects/wobbly_shade_test.cpp @@ -0,0 +1,178 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2018 Martin Flöser + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "composite.h" +#include "core/platform.h" +#include "core/renderbackend.h" +#include "cursor.h" +#include "effectloader.h" +#include "effects.h" +#include "wayland_server.h" +#include "workspace.h" +#include "x11window.h" + +#include + +#include +#include +#include +#include + +#include +#include + +using namespace KWin; +static const QString s_socketName = QStringLiteral("wayland_test_effects_wobbly_shade-0"); + +class WobblyWindowsShadeTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testShadeMove(); +}; + +void WobblyWindowsShadeTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + // disable all effects - we don't want to have it interact with the rendering + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup plugins(config, QStringLiteral("Plugins")); + const auto builtinNames = EffectLoader().listOfKnownEffects(); + for (QString name : builtinNames) { + plugins.writeEntry(name + QStringLiteral("Enabled"), false); + } + + config->sync(); + kwinApp()->setConfig(config); + + qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2")); + qputenv("KWIN_EFFECTS_FORCE_ANIMATIONS", "1"); + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + QVERIFY(Compositor::self()); + + QCOMPARE(Compositor::self()->backend()->compositingType(), KWin::OpenGLCompositing); +} + +void WobblyWindowsShadeTest::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration)); +} + +void WobblyWindowsShadeTest::cleanup() +{ + Test::destroyWaylandConnection(); + + auto effectsImpl = static_cast(effects); + effectsImpl->unloadAllEffects(); + QVERIFY(effectsImpl->loadedEffects().isEmpty()); +} + +struct XcbConnectionDeleter +{ + void operator()(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } +}; + +void WobblyWindowsShadeTest::testShadeMove() +{ + // this test simulates the condition from BUG 390953 + EffectsHandlerImpl *e = static_cast(effects); + QVERIFY(e->loadEffect(QStringLiteral("wobblywindows"))); + QVERIFY(e->isEffectLoaded(QStringLiteral("wobblywindows"))); + + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QVERIFY(window->isDecorated()); + QVERIFY(window->isShadeable()); + QVERIFY(!window->isShade()); + QVERIFY(window->isActive()); + + QSignalSpy windowShownSpy(window, &Window::windowShown); + QVERIFY(windowShownSpy.wait()); + + // now shade the window + workspace()->slotWindowShade(); + QVERIFY(window->isShade()); + + QSignalSpy windowStartUserMovedResizedSpy(e, &EffectsHandler::windowStartUserMovedResized); + + // begin move + QVERIFY(workspace()->moveResizeWindow() == nullptr); + QCOMPARE(window->isInteractiveMove(), false); + workspace()->slotWindowMove(); + QCOMPARE(workspace()->moveResizeWindow(), window); + QCOMPARE(window->isInteractiveMove(), true); + QCOMPARE(windowStartUserMovedResizedSpy.count(), 1); + + // wait for frame rendered + QTest::qWait(100); + + // send some key events, not going through input redirection + window->keyPressEvent(Qt::Key_Right); + window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + + // wait for frame rendered + QTest::qWait(100); + + window->keyPressEvent(Qt::Key_Right); + window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + + // wait for frame rendered + QTest::qWait(100); + + window->keyPressEvent(Qt::Key_Down | Qt::ALT); + window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + + // wait for frame rendered + QTest::qWait(100); + + // let's end + window->keyPressEvent(Qt::Key_Enter); + + // wait for frame rendered + QTest::qWait(100); +} + +WAYLANDTEST_MAIN(WobblyWindowsShadeTest) +#include "wobbly_shade_test.moc" diff --git a/autotests/integration/fakes/CMakeLists.txt b/autotests/integration/fakes/CMakeLists.txt new file mode 100644 index 0000000..e62ffea --- /dev/null +++ b/autotests/integration/fakes/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(org.kde.kdecoration2) + diff --git a/autotests/integration/fakes/org.kde.kdecoration2/CMakeLists.txt b/autotests/integration/fakes/org.kde.kdecoration2/CMakeLists.txt new file mode 100644 index 0000000..1b233ed --- /dev/null +++ b/autotests/integration/fakes/org.kde.kdecoration2/CMakeLists.txt @@ -0,0 +1,15 @@ +######################################################## +# FakeDecoWithShadows +######################################################## +add_library(fakedecoshadows MODULE fakedecoration_with_shadows.cpp) +set_target_properties(fakedecoshadows PROPERTIES + PREFIX "" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/fakes/org.kde.kdecoration2") +target_link_libraries(fakedecoshadows + PUBLIC + Qt::Core + Qt::Gui + PRIVATE + KDecoration2::KDecoration + KF5::CoreAddons) + diff --git a/autotests/integration/fakes/org.kde.kdecoration2/fakedecoration_with_shadows.cpp b/autotests/integration/fakes/org.kde.kdecoration2/fakedecoration_with_shadows.cpp new file mode 100644 index 0000000..3aba0d0 --- /dev/null +++ b/autotests/integration/fakes/org.kde.kdecoration2/fakedecoration_with_shadows.cpp @@ -0,0 +1,65 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2018 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include + +#include +#include + +class FakeDecoWithShadows : public KDecoration2::Decoration +{ + Q_OBJECT + +public: + explicit FakeDecoWithShadows(QObject *parent = nullptr, const QVariantList &args = QVariantList()) + : Decoration(parent, args) + { + } + ~FakeDecoWithShadows() override + { + } + + void paint(QPainter *painter, const QRect &repaintRegion) override + { + Q_UNUSED(painter) + Q_UNUSED(repaintRegion) + } + +public Q_SLOTS: + void init() override + { + const int shadowSize = 128; + const int offsetTop = 64; + const int offsetLeft = 48; + const QRect shadowRect(0, 0, 4 * shadowSize + 1, 4 * shadowSize + 1); + + QImage shadowTexture(shadowRect.size(), QImage::Format_ARGB32_Premultiplied); + shadowTexture.fill(Qt::transparent); + + const QMargins padding( + shadowSize - offsetLeft, + shadowSize - offsetTop, + shadowSize + offsetLeft, + shadowSize + offsetTop); + + auto decoShadow = QSharedPointer::create(); + decoShadow->setPadding(padding); + decoShadow->setInnerShadowRect(QRect(shadowRect.center(), QSize(1, 1))); + decoShadow->setShadow(shadowTexture); + + setShadow(decoShadow); + } +}; + +K_PLUGIN_FACTORY_WITH_JSON( + FakeDecoWithShadowsFactory, + "fakedecoration_with_shadows.json", + registerPlugin();) + +#include "fakedecoration_with_shadows.moc" diff --git a/autotests/integration/fakes/org.kde.kdecoration2/fakedecoration_with_shadows.json b/autotests/integration/fakes/org.kde.kdecoration2/fakedecoration_with_shadows.json new file mode 100644 index 0000000..16577df --- /dev/null +++ b/autotests/integration/fakes/org.kde.kdecoration2/fakedecoration_with_shadows.json @@ -0,0 +1,15 @@ +{ + "KPlugin": { + "Description": "Window decoration to test shadow tile overlaps", + "EnabledByDefault": false, + "Id": "org.kde.test.fakedecowithshadows", + "Name": "Fake Decoration With Shadows", + "ServiceTypes": [ + "org.kde.kdecoration2" + ] + }, + "org.kde.kdecoration2": { + "blur": false, + "kcmodule": false + } +} diff --git a/autotests/integration/generic_scene_opengl_test.cpp b/autotests/integration/generic_scene_opengl_test.cpp new file mode 100644 index 0000000..ea34a30 --- /dev/null +++ b/autotests/integration/generic_scene_opengl_test.cpp @@ -0,0 +1,86 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "generic_scene_opengl_test.h" +#include "composite.h" +#include "core/platform.h" +#include "core/renderbackend.h" +#include "cursor.h" +#include "effectloader.h" +#include "scene.h" +#include "wayland_server.h" +#include "window.h" + +#include + +using namespace KWin; +static const QString s_socketName = QStringLiteral("wayland_test_kwin_scene_opengl-0"); + +GenericSceneOpenGLTest::GenericSceneOpenGLTest(const QByteArray &envVariable) + : QObject() + , m_envVariable(envVariable) +{ +} + +GenericSceneOpenGLTest::~GenericSceneOpenGLTest() +{ +} + +void GenericSceneOpenGLTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void GenericSceneOpenGLTest::initTestCase() +{ + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + // disable all effects - we don't want to have it interact with the rendering + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup plugins(config, QStringLiteral("Plugins")); + const auto builtinNames = EffectLoader().listOfKnownEffects(); + for (QString name : builtinNames) { + plugins.writeEntry(name + QStringLiteral("Enabled"), false); + } + + config->sync(); + kwinApp()->setConfig(config); + + qputenv("XCURSOR_THEME", QByteArrayLiteral("DMZ-White")); + qputenv("XCURSOR_SIZE", QByteArrayLiteral("24")); + qputenv("KWIN_COMPOSE", m_envVariable); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + QVERIFY(Compositor::self()); + + QCOMPARE(Compositor::self()->backend()->compositingType(), KWin::OpenGLCompositing); + QCOMPARE(kwinApp()->platform()->selectedCompositor(), KWin::OpenGLCompositing); +} + +void GenericSceneOpenGLTest::testRestart() +{ + // simple restart of the OpenGL compositor without any windows being shown + QSignalSpy sceneCreatedSpy(KWin::Compositor::self(), &Compositor::sceneCreated); + KWin::Compositor::self()->reinitialize(); + if (sceneCreatedSpy.isEmpty()) { + QVERIFY(sceneCreatedSpy.wait()); + } + QCOMPARE(sceneCreatedSpy.count(), 1); + QCOMPARE(Compositor::self()->backend()->compositingType(), KWin::OpenGLCompositing); + QCOMPARE(kwinApp()->platform()->selectedCompositor(), KWin::OpenGLCompositing); + + // trigger a repaint + KWin::Compositor::self()->scene()->addRepaintFull(); + // and wait 100 msec to ensure it's rendered + // TODO: introduce frameRendered signal in SceneOpenGL + QTest::qWait(100); +} diff --git a/autotests/integration/generic_scene_opengl_test.h b/autotests/integration/generic_scene_opengl_test.h new file mode 100644 index 0000000..85cb7e7 --- /dev/null +++ b/autotests/integration/generic_scene_opengl_test.h @@ -0,0 +1,29 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2017 Martin Flöser + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#pragma once +#include "kwin_wayland_test.h" + +#include + +class GenericSceneOpenGLTest : public QObject +{ + Q_OBJECT +public: + ~GenericSceneOpenGLTest() override; + +protected: + GenericSceneOpenGLTest(const QByteArray &envVariable); +private Q_SLOTS: + void initTestCase(); + void cleanup(); + void testRestart(); + +private: + QByteArray m_envVariable; +}; diff --git a/autotests/integration/globalshortcuts_test.cpp b/autotests/integration/globalshortcuts_test.cpp new file mode 100644 index 0000000..a82e693 --- /dev/null +++ b/autotests/integration/globalshortcuts_test.cpp @@ -0,0 +1,446 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/platform.h" +#include "cursor.h" +#include "input.h" +#include "internalwindow.h" +#include "keyboard_input.h" +#include "useractions.h" +#include "wayland/keyboard_interface.h" +#include "wayland/seat_interface.h" +#include "wayland_server.h" +#include "workspace.h" +#include "x11window.h" + +#include + +#include +#include + +#include +#include + +using namespace KWin; +using namespace KWayland::Client; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_globalshortcuts-0"); + +class GlobalShortcutsTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testNonLatinLayout_data(); + void testNonLatinLayout(); + void testConsumedShift(); + void testRepeatedTrigger(); + void testUserActionsMenu(); + void testMetaShiftW(); + void testComponseKey(); + void testX11WindowShortcut(); + void testWaylandWindowShortcut(); + void testSetupWindowShortcut(); +}; + +void GlobalShortcutsTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); + qputenv("KWIN_XKB_DEFAULT_KEYMAP", "1"); + qputenv("XKB_DEFAULT_RULES", "evdev"); + qputenv("XKB_DEFAULT_LAYOUT", "us,ru"); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); +} + +void GlobalShortcutsTest::init() +{ + QVERIFY(Test::setupWaylandConnection()); + workspace()->setActiveOutput(QPoint(640, 512)); + KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512)); + + auto xkb = input()->keyboard()->xkb(); + xkb->switchToLayout(0); +} + +void GlobalShortcutsTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +Q_DECLARE_METATYPE(Qt::Modifier) + +void GlobalShortcutsTest::testNonLatinLayout_data() +{ + QTest::addColumn("modifierKey"); + QTest::addColumn("qtModifier"); + QTest::addColumn("key"); + QTest::addColumn("qtKey"); + + for (const auto &modifier : + QVector>{ + {KEY_LEFTCTRL, Qt::CTRL}, + {KEY_LEFTALT, Qt::ALT}, + {KEY_LEFTSHIFT, Qt::SHIFT}, + {KEY_LEFTMETA, Qt::META}, + }) { + for (const auto &key : + QVector> { + // Tab is example of a key usually the same on different layouts, check it first + {KEY_TAB, Qt::Key_Tab}, + + // Then check a key with a Latin letter. + // The symbol will probably be differ on non-Latin layout. + // On Russian layout, "w" key has a cyrillic letter "ц" + {KEY_W, Qt::Key_W}, + +#if QT_VERSION_MAJOR > 5 // since Qt 5 LTS is frozen + // More common case with any Latin1 symbol keys, including punctuation, should work also. + // "`" key has a "ё" letter on Russian layout + // FIXME: QTBUG-90611 + {KEY_GRAVE, Qt::Key_QuoteLeft}, +#endif + }) { + QTest::newRow(QKeySequence(modifier.second + key.second).toString().toLatin1().constData()) + << modifier.first << modifier.second << key.first << key.second; + } + } +} + +void GlobalShortcutsTest::testNonLatinLayout() +{ + // Shortcuts on non-Latin layouts should still work, see BUG 375518 + auto xkb = input()->keyboard()->xkb(); + xkb->switchToLayout(1); + QCOMPARE(xkb->layoutName(), QStringLiteral("Russian")); + + QFETCH(int, modifierKey); + QFETCH(Qt::Modifier, qtModifier); + QFETCH(int, key); + QFETCH(Qt::Key, qtKey); + + const QKeySequence seq(qtModifier + qtKey); + + std::unique_ptr action(new QAction(nullptr)); + action->setProperty("componentName", QStringLiteral(KWIN_NAME)); + action->setObjectName("globalshortcuts-test-non-latin-layout"); + + QSignalSpy triggeredSpy(action.get(), &QAction::triggered); + + KGlobalAccel::self()->stealShortcutSystemwide(seq); + KGlobalAccel::self()->setShortcut(action.get(), {seq}, KGlobalAccel::NoAutoloading); + input()->registerShortcut(seq, action.get()); + + quint32 timestamp = 0; + Test::keyboardKeyPressed(modifierKey, timestamp++); + QCOMPARE(input()->keyboardModifiers(), qtModifier); + Test::keyboardKeyPressed(key, timestamp++); + + Test::keyboardKeyReleased(key, timestamp++); + Test::keyboardKeyReleased(modifierKey, timestamp++); + + QTRY_COMPARE_WITH_TIMEOUT(triggeredSpy.count(), 1, 100); +} + +void GlobalShortcutsTest::testConsumedShift() +{ + // this test verifies that a shortcut with a consumed shift modifier triggers + // create the action + std::unique_ptr action(new QAction(nullptr)); + action->setProperty("componentName", QStringLiteral(KWIN_NAME)); + action->setObjectName(QStringLiteral("globalshortcuts-test-consumed-shift")); + QSignalSpy triggeredSpy(action.get(), &QAction::triggered); + KGlobalAccel::self()->setShortcut(action.get(), QList{Qt::Key_Percent}, KGlobalAccel::NoAutoloading); + input()->registerShortcut(Qt::Key_Percent, action.get()); + + // press shift+5 + quint32 timestamp = 0; + Test::keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); + QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier); + Test::keyboardKeyPressed(KEY_5, timestamp++); + QTRY_COMPARE(triggeredSpy.count(), 1); + Test::keyboardKeyReleased(KEY_5, timestamp++); + + // release shift + Test::keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); +} + +void GlobalShortcutsTest::testRepeatedTrigger() +{ + // this test verifies that holding a key, triggers repeated global shortcut + // in addition pressing another key should stop triggering the shortcut + + std::unique_ptr action(new QAction(nullptr)); + action->setProperty("componentName", QStringLiteral(KWIN_NAME)); + action->setObjectName(QStringLiteral("globalshortcuts-test-consumed-shift")); + QSignalSpy triggeredSpy(action.get(), &QAction::triggered); + KGlobalAccel::self()->setShortcut(action.get(), QList{Qt::Key_Percent}, KGlobalAccel::NoAutoloading); + input()->registerShortcut(Qt::Key_Percent, action.get()); + + // we need to configure the key repeat first. It is only enabled on libinput + waylandServer()->seat()->keyboard()->setRepeatInfo(25, 300); + + // press shift+5 + quint32 timestamp = 0; + Test::keyboardKeyPressed(KEY_WAKEUP, timestamp++); + Test::keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); + QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier); + Test::keyboardKeyPressed(KEY_5, timestamp++); + QTRY_COMPARE(triggeredSpy.count(), 1); + // and should repeat + QVERIFY(triggeredSpy.wait()); + QVERIFY(triggeredSpy.wait()); + // now release the key + Test::keyboardKeyReleased(KEY_5, timestamp++); + QVERIFY(!triggeredSpy.wait(50)); + + Test::keyboardKeyReleased(KEY_WAKEUP, timestamp++); + QVERIFY(!triggeredSpy.wait(50)); + + // release shift + Test::keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); +} + +void GlobalShortcutsTest::testUserActionsMenu() +{ + // this test tries to trigger the user actions menu with Alt+F3 + // the problem here is that pressing F3 consumes modifiers as it's part of the + // Ctrl+alt+F3 keysym for vt switching. xkbcommon considers all modifiers as consumed + // which a transformation to any keysym would cause + // for more information see: + // https://bugs.freedesktop.org/show_bug.cgi?id=92818 + // https://github.com/xkbcommon/libxkbcommon/issues/17 + + // first create a window + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(window->isActive()); + + quint32 timestamp = 0; + QVERIFY(!workspace()->userActionsMenu()->isShown()); + Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); + Test::keyboardKeyPressed(KEY_F3, timestamp++); + Test::keyboardKeyReleased(KEY_F3, timestamp++); + QTRY_VERIFY(workspace()->userActionsMenu()->isShown()); + Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); +} + +void GlobalShortcutsTest::testMetaShiftW() +{ + // BUG 370341 + std::unique_ptr action(new QAction(nullptr)); + action->setProperty("componentName", QStringLiteral(KWIN_NAME)); + action->setObjectName(QStringLiteral("globalshortcuts-test-meta-shift-w")); + QSignalSpy triggeredSpy(action.get(), &QAction::triggered); + KGlobalAccel::self()->setShortcut(action.get(), QList{Qt::META | Qt::SHIFT | Qt::Key_W}, KGlobalAccel::NoAutoloading); + input()->registerShortcut(Qt::META | Qt::SHIFT | Qt::Key_W, action.get()); + + // press meta+shift+w + quint32 timestamp = 0; + Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++); + QCOMPARE(input()->keyboardModifiers(), Qt::MetaModifier); + Test::keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); + QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier | Qt::MetaModifier); + Test::keyboardKeyPressed(KEY_W, timestamp++); + QTRY_COMPARE(triggeredSpy.count(), 1); + Test::keyboardKeyReleased(KEY_W, timestamp++); + + // release meta+shift + Test::keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++); +} + +void GlobalShortcutsTest::testComponseKey() +{ + // BUG 390110 + std::unique_ptr action(new QAction(nullptr)); + action->setProperty("componentName", QStringLiteral(KWIN_NAME)); + action->setObjectName(QStringLiteral("globalshortcuts-accent")); + QSignalSpy triggeredSpy(action.get(), &QAction::triggered); + KGlobalAccel::self()->setShortcut(action.get(), QList{Qt::NoModifier}, KGlobalAccel::NoAutoloading); + input()->registerShortcut(Qt::NoModifier, action.get()); + + // press & release ` + quint32 timestamp = 0; + Test::keyboardKeyPressed(KEY_RESERVED, timestamp++); + Test::keyboardKeyReleased(KEY_RESERVED, timestamp++); + + QTRY_COMPARE(triggeredSpy.count(), 0); +} + +struct XcbConnectionDeleter +{ + void operator()(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } +}; + +void GlobalShortcutsTest::testX11WindowShortcut() +{ +#ifdef NO_XWAYLAND + QSKIP("x11 test, unnecessary without xwayland"); +#endif + // create an X11 window + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + xcb_window_t windowId = xcb_generate_id(c.get()); + const QRect windowGeometry = QRect(0, 0, 10, 20); + const uint32_t values[] = { + XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW}; + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + NETWinInfo info(c.get(), windowId, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); + info.setWindowType(NET::Normal); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.last().first().value(); + QVERIFY(window); + + QCOMPARE(workspace()->activeWindow(), window); + QVERIFY(window->isActive()); + QCOMPARE(window->shortcut(), QKeySequence()); + const QKeySequence seq(Qt::META | Qt::SHIFT | Qt::Key_Y); + QVERIFY(workspace()->shortcutAvailable(seq)); + window->setShortcut(seq.toString()); + QCOMPARE(window->shortcut(), seq); + QVERIFY(!workspace()->shortcutAvailable(seq)); + QCOMPARE(window->caption(), QStringLiteral(" {Meta+Shift+Y}")); + + // it's delayed + QCoreApplication::processEvents(); + + workspace()->activateWindow(nullptr); + QVERIFY(!workspace()->activeWindow()); + QVERIFY(!window->isActive()); + + // now let's trigger the shortcut + quint32 timestamp = 0; + Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++); + Test::keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); + Test::keyboardKeyPressed(KEY_Y, timestamp++); + QTRY_COMPARE(workspace()->activeWindow(), window); + Test::keyboardKeyReleased(KEY_Y, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++); + + // destroy window again + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + xcb_unmap_window(c.get(), windowId); + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + QVERIFY(windowClosedSpy.wait()); +} + +void GlobalShortcutsTest::testWaylandWindowShortcut() +{ + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + + QCOMPARE(workspace()->activeWindow(), window); + QVERIFY(window->isActive()); + QCOMPARE(window->shortcut(), QKeySequence()); + const QKeySequence seq(Qt::META | Qt::SHIFT | Qt::Key_Y); + QVERIFY(workspace()->shortcutAvailable(seq)); + window->setShortcut(seq.toString()); + QCOMPARE(window->shortcut(), seq); + QVERIFY(!workspace()->shortcutAvailable(seq)); + QCOMPARE(window->caption(), QStringLiteral(" {Meta+Shift+Y}")); + + workspace()->activateWindow(nullptr); + QVERIFY(!workspace()->activeWindow()); + QVERIFY(!window->isActive()); + + // now let's trigger the shortcut + quint32 timestamp = 0; + Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++); + Test::keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); + Test::keyboardKeyPressed(KEY_Y, timestamp++); + QTRY_COMPARE(workspace()->activeWindow(), window); + Test::keyboardKeyReleased(KEY_Y, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++); + + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); + QTRY_VERIFY_WITH_TIMEOUT(workspace()->shortcutAvailable(seq), 500); // we need the try since KGlobalAccelPrivate::unregister is async +} + +void GlobalShortcutsTest::testSetupWindowShortcut() +{ + // QTBUG-62102 + + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + + QCOMPARE(workspace()->activeWindow(), window); + QVERIFY(window->isActive()); + QCOMPARE(window->shortcut(), QKeySequence()); + + QSignalSpy shortcutDialogAddedSpy(workspace(), &Workspace::internalWindowAdded); + workspace()->slotSetupWindowShortcut(); + QTRY_COMPARE(shortcutDialogAddedSpy.count(), 1); + auto dialog = shortcutDialogAddedSpy.first().first().value(); + QVERIFY(dialog); + QVERIFY(dialog->isInternal()); + auto sequenceEdit = workspace()->shortcutDialog()->findChild(); + QVERIFY(sequenceEdit); + + // the QKeySequenceEdit field does not get focus, we need to pass it focus manually + QEXPECT_FAIL("", "Edit does not have focus", Continue); + QVERIFY(sequenceEdit->hasFocus()); + sequenceEdit->setFocus(); + QTRY_VERIFY(sequenceEdit->hasFocus()); + + quint32 timestamp = 0; + Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++); + Test::keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); + Test::keyboardKeyPressed(KEY_Y, timestamp++); + Test::keyboardKeyReleased(KEY_Y, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++); + + // the sequence gets accepted after one second, so wait a bit longer + QTest::qWait(2000); + // now send in enter + Test::keyboardKeyPressed(KEY_ENTER, timestamp++); + Test::keyboardKeyReleased(KEY_ENTER, timestamp++); + QTRY_COMPARE(window->shortcut(), QKeySequence(Qt::META | Qt::SHIFT | Qt::Key_Y)); +} + +WAYLANDTEST_MAIN(GlobalShortcutsTest) +#include "globalshortcuts_test.moc" diff --git a/autotests/integration/helper/CMakeLists.txt b/autotests/integration/helper/CMakeLists.txt new file mode 100644 index 0000000..1e6f9c9 --- /dev/null +++ b/autotests/integration/helper/CMakeLists.txt @@ -0,0 +1,11 @@ +add_executable(copy copy.cpp) +target_link_libraries(copy Qt::Gui) +ecm_mark_as_test(copy) +###################### +add_executable(paste paste.cpp) +target_link_libraries(paste Qt::Gui) +ecm_mark_as_test(paste) +###################### +add_executable(kill kill.cpp) +target_link_libraries(kill Qt::Widgets) +ecm_mark_as_test(kill) diff --git a/autotests/integration/helper/copy.cpp b/autotests/integration/helper/copy.cpp new file mode 100644 index 0000000..97b183a --- /dev/null +++ b/autotests/integration/helper/copy.cpp @@ -0,0 +1,60 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include +#include +#include +#include +#include + +class Window : public QRasterWindow +{ + Q_OBJECT +public: + explicit Window(); + ~Window() override; + +protected: + void paintEvent(QPaintEvent *event) override; + void focusInEvent(QFocusEvent *event) override; +}; + +Window::Window() + : QRasterWindow() +{ +} + +Window::~Window() = default; + +void Window::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + QPainter p(this); + p.fillRect(0, 0, width(), height(), Qt::red); +} + +void Window::focusInEvent(QFocusEvent *event) +{ + QRasterWindow::focusInEvent(event); + // TODO: make it work without singleshot + QTimer::singleShot(100, [] { + qApp->clipboard()->setText(QStringLiteral("test")); + }); +} + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + std::unique_ptr w(new Window); + w->setGeometry(QRect(0, 0, 100, 200)); + w->show(); + + return app.exec(); +} + +#include "copy.moc" diff --git a/autotests/integration/helper/kill.cpp b/autotests/integration/helper/kill.cpp new file mode 100644 index 0000000..2f24cc6 --- /dev/null +++ b/autotests/integration/helper/kill.cpp @@ -0,0 +1,35 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + SPDX-FileCopyrightText: 2019 David Edmundson + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include +#include +#include +#include +#include + +#include + +int main(int argc, char *argv[]) +{ + qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("wayland")); + QApplication app(argc, argv); + QWidget w; + w.setGeometry(QRect(0, 0, 100, 200)); + w.show(); + + auto freezeHandler = [](int) { + while (true) { + sleep(10000); + } + }; + + signal(SIGUSR1, freezeHandler); + + return app.exec(); +} diff --git a/autotests/integration/helper/paste.cpp b/autotests/integration/helper/paste.cpp new file mode 100644 index 0000000..32ead5b --- /dev/null +++ b/autotests/integration/helper/paste.cpp @@ -0,0 +1,56 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include +#include +#include +#include +#include + +class Window : public QRasterWindow +{ + Q_OBJECT +public: + explicit Window(); + ~Window() override; + +protected: + void paintEvent(QPaintEvent *event) override; +}; + +Window::Window() + : QRasterWindow() +{ +} + +Window::~Window() = default; + +void Window::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + QPainter p(this); + p.fillRect(0, 0, width(), height(), Qt::blue); +} + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + QObject::connect(app.clipboard(), &QClipboard::changed, &app, + [] { + if (qApp->clipboard()->text() == QLatin1String("test")) { + QTimer::singleShot(100, qApp, &QCoreApplication::quit); + } + }); + std::unique_ptr w(new Window); + w->setGeometry(QRect(0, 0, 100, 200)); + w->show(); + + return app.exec(); +} + +#include "paste.moc" diff --git a/autotests/integration/idle_inhibition_test.cpp b/autotests/integration/idle_inhibition_test.cpp new file mode 100644 index 0000000..750ba01 --- /dev/null +++ b/autotests/integration/idle_inhibition_test.cpp @@ -0,0 +1,304 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2017 Martin Flöser + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/platform.h" +#include "virtualdesktops.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include + +using namespace KWin; +using namespace KWayland::Client; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_idle_inhbition_test-0"); + +class TestIdleInhibition : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testInhibit(); + void testDontInhibitWhenNotOnCurrentDesktop(); + void testDontInhibitWhenMinimized(); + void testDontInhibitWhenUnmapped(); + void testDontInhibitWhenLeftCurrentDesktop(); +}; + +void TestIdleInhibition::initTestCase() +{ + qRegisterMetaType(); + + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); +} + +void TestIdleInhibition::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::IdleInhibitV1)); +} + +void TestIdleInhibition::cleanup() +{ + Test::destroyWaylandConnection(); + + VirtualDesktopManager::self()->setCount(1); + QCOMPARE(VirtualDesktopManager::self()->count(), 1u); +} + +void TestIdleInhibition::testInhibit() +{ + // no idle inhibitors at the start + QCOMPARE(input()->idleInhibitors(), QList{}); + + // now create window + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + + // now create inhibition on window + std::unique_ptr inhibitor(Test::createIdleInhibitorV1(surface.get())); + QVERIFY(inhibitor); + + // render the window + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // this should inhibit our server object + QCOMPARE(input()->idleInhibitors(), QList{window}); + + // deleting the object should uninhibit again + inhibitor.reset(); + Test::flushWaylandConnection(); // don't use QTRY_COMPARE(), it doesn't spin event loop + QGuiApplication::processEvents(); + QCOMPARE(input()->idleInhibitors(), QList{}); + + // inhibit again and destroy window + std::unique_ptr inhibitor2(Test::createIdleInhibitorV1(surface.get())); + Test::flushWaylandConnection(); + QGuiApplication::processEvents(); + QCOMPARE(input()->idleInhibitors(), QList{window}); + + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); + QCOMPARE(input()->idleInhibitors(), QList{}); +} + +void TestIdleInhibition::testDontInhibitWhenNotOnCurrentDesktop() +{ + // This test verifies that the idle inhibitor object is not honored when + // the associated surface is not on the current virtual desktop. + + VirtualDesktopManager::self()->setCount(2); + QCOMPARE(VirtualDesktopManager::self()->count(), 2u); + + // Create the test window. + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + + // Create the inhibitor object. + std::unique_ptr inhibitor(Test::createIdleInhibitorV1(surface.get())); + QVERIFY(inhibitor); + + // Render the window. + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // The test window should be only on the first virtual desktop. + QCOMPARE(window->desktops().count(), 1); + QCOMPARE(window->desktops().first(), VirtualDesktopManager::self()->desktops().first()); + + // This should inhibit our server object. + QCOMPARE(input()->idleInhibitors(), QList{window}); + + // Switch to the second virtual desktop. + VirtualDesktopManager::self()->setCurrent(2); + + // The surface is no longer visible, so the compositor don't have to honor the + // idle inhibitor object. + QCOMPARE(input()->idleInhibitors(), QList{}); + + // Switch back to the first virtual desktop. + VirtualDesktopManager::self()->setCurrent(1); + + // The test window became visible again, so the compositor has to honor the idle + // inhibitor object back again. + QCOMPARE(input()->idleInhibitors(), QList{window}); + + // Destroy the test window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); + QCOMPARE(input()->idleInhibitors(), QList{}); +} + +void TestIdleInhibition::testDontInhibitWhenMinimized() +{ + // This test verifies that the idle inhibitor object is not honored when the + // associated surface is minimized. + + // Create the test window. + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + + // Create the inhibitor object. + std::unique_ptr inhibitor(Test::createIdleInhibitorV1(surface.get())); + QVERIFY(inhibitor); + + // Render the window. + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // This should inhibit our server object. + QCOMPARE(input()->idleInhibitors(), QList{window}); + + // Minimize the window, the idle inhibitor object should not be honored. + window->minimize(); + QCOMPARE(input()->idleInhibitors(), QList{}); + + // Unminimize the window, the idle inhibitor object should be honored back again. + window->unminimize(); + QCOMPARE(input()->idleInhibitors(), QList{window}); + + // Destroy the test window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); + QCOMPARE(input()->idleInhibitors(), QList{}); +} + +void TestIdleInhibition::testDontInhibitWhenUnmapped() +{ + // This test verifies that the idle inhibitor object is not honored by KWin + // when the associated window is unmapped. + + // Create the test window. + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + + // Create the inhibitor object. + std::unique_ptr inhibitor(Test::createIdleInhibitorV1(surface.get())); + QVERIFY(inhibitor); + + // Map the window. + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + Test::render(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(windowAddedSpy.isEmpty()); + QVERIFY(windowAddedSpy.wait()); + QCOMPARE(windowAddedSpy.count(), 1); + Window *window = windowAddedSpy.last().first().value(); + QVERIFY(window); + QCOMPARE(window->readyForPainting(), true); + + // The compositor will respond with a configure event when the surface becomes active. + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + + // This should inhibit our server object. + QCOMPARE(input()->idleInhibitors(), QList{window}); + + // Unmap the window. + surface->attachBuffer(Buffer::Ptr()); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(Test::waitForWindowDestroyed(window)); + + // The surface is no longer visible, so the compositor doesn't have to honor the + // idle inhibitor object. + QCOMPARE(input()->idleInhibitors(), QList{}); + + // Tell the compositor that we want to map the surface. + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // The compositor will respond with a configure event. + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); + + // Map the window. + Test::render(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(windowAddedSpy.wait()); + QCOMPARE(windowAddedSpy.count(), 2); + window = windowAddedSpy.last().first().value(); + QVERIFY(window); + QCOMPARE(window->readyForPainting(), true); + + // The test window became visible again, so the compositor has to honor the idle + // inhibitor object back again. + QCOMPARE(input()->idleInhibitors(), QList{window}); + + // Destroy the test window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); + QCOMPARE(input()->idleInhibitors(), QList{}); +} + +void TestIdleInhibition::testDontInhibitWhenLeftCurrentDesktop() +{ + // This test verifies that the idle inhibitor object is not honored by KWin + // when the associated surface leaves the current virtual desktop. + + VirtualDesktopManager::self()->setCount(2); + QCOMPARE(VirtualDesktopManager::self()->count(), 2u); + + // Create the test window. + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + + // Create the inhibitor object. + std::unique_ptr inhibitor(Test::createIdleInhibitorV1(surface.get())); + QVERIFY(inhibitor); + + // Render the window. + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // The test window should be only on the first virtual desktop. + QCOMPARE(window->desktops().count(), 1); + QCOMPARE(window->desktops().first(), VirtualDesktopManager::self()->desktops().first()); + + // This should inhibit our server object. + QCOMPARE(input()->idleInhibitors(), QList{window}); + + // Let the window enter the second virtual desktop. + window->enterDesktop(VirtualDesktopManager::self()->desktops().at(1)); + QCOMPARE(input()->idleInhibitors(), QList{window}); + + // If the window leaves the first virtual desktop, then the associated idle + // inhibitor object should not be honored. + window->leaveDesktop(VirtualDesktopManager::self()->desktops().at(0)); + QCOMPARE(input()->idleInhibitors(), QList{}); + + // If the window enters the first desktop, then the associated idle inhibitor + // object should be honored back again. + window->enterDesktop(VirtualDesktopManager::self()->desktops().at(0)); + QCOMPARE(input()->idleInhibitors(), QList{window}); + + // Destroy the test window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); + QCOMPARE(input()->idleInhibitors(), QList{}); +} + +WAYLANDTEST_MAIN(TestIdleInhibition) +#include "idle_inhibition_test.moc" diff --git a/autotests/integration/input_stacking_order.cpp b/autotests/integration/input_stacking_order.cpp new file mode 100644 index 0000000..6b16438 --- /dev/null +++ b/autotests/integration/input_stacking_order.cpp @@ -0,0 +1,160 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "deleted.h" +#include "wayland/seat_interface.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_input_stacking_order-0"); + +class InputStackingOrderTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testPointerFocusUpdatesOnStackingOrderChange(); + +private: + void render(KWayland::Client::Surface *surface); +}; + +void InputStackingOrderTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); + setenv("QT_QPA_PLATFORM", "wayland", true); +} + +void InputStackingOrderTest::init() +{ + using namespace KWayland::Client; + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat)); + QVERIFY(Test::waitForWaylandPointer()); + + workspace()->setActiveOutput(QPoint(640, 512)); + Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void InputStackingOrderTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void InputStackingOrderTest::render(KWayland::Client::Surface *surface) +{ + Test::render(surface, QSize(100, 50), Qt::blue); + Test::flushWaylandConnection(); +} + +void InputStackingOrderTest::testPointerFocusUpdatesOnStackingOrderChange() +{ + // this test creates two windows which overlap + // the pointer is in the overlapping area which means the top most window has focus + // as soon as the top most window gets lowered the window should lose focus and the + // other window should gain focus without a mouse event in between + using namespace KWayland::Client; + // create pointer and signal spy for enter and leave signals + auto pointer = Test::waylandSeat()->createPointer(Test::waylandSeat()); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + QSignalSpy enteredSpy(pointer, &Pointer::entered); + QSignalSpy leftSpy(pointer, &Pointer::left); + + // now create the two windows and make them overlap + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + std::unique_ptr surface1 = Test::createSurface(); + QVERIFY(surface1); + Test::XdgToplevel *shellSurface1 = Test::createXdgToplevelSurface(surface1.get(), surface1.get()); + QVERIFY(shellSurface1); + render(surface1.get()); + QVERIFY(windowAddedSpy.wait()); + Window *window1 = workspace()->activeWindow(); + QVERIFY(window1); + + std::unique_ptr surface2 = Test::createSurface(); + QVERIFY(surface2); + Test::XdgToplevel *shellSurface2 = Test::createXdgToplevelSurface(surface2.get(), surface2.get()); + QVERIFY(shellSurface2); + render(surface2.get()); + QVERIFY(windowAddedSpy.wait()); + + Window *window2 = workspace()->activeWindow(); + QVERIFY(window2); + QVERIFY(window1 != window2); + + // now make windows overlap + window2->move(window1->pos()); + QCOMPARE(window1->frameGeometry(), window2->frameGeometry()); + + // enter + Test::pointerMotion(QPointF(25, 25), 1); + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.count(), 1); + // window 2 should have focus + QCOMPARE(pointer->enteredSurface(), surface2.get()); + // also on the server + QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window2->surface()); + + // raise window 1 above window 2 + QVERIFY(leftSpy.isEmpty()); + workspace()->raiseWindow(window1); + // should send leave to window2 + QVERIFY(leftSpy.wait()); + QCOMPARE(leftSpy.count(), 1); + // and an enter to window1 + QCOMPARE(enteredSpy.count(), 2); + QCOMPARE(pointer->enteredSurface(), surface1.get()); + QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window1->surface()); + + // let's destroy window1, that should pass focus to window2 again + QSignalSpy windowClosedSpy(window1, &Window::windowClosed); + surface1.reset(); + QVERIFY(windowClosedSpy.wait()); + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.count(), 3); + QCOMPARE(pointer->enteredSurface(), surface2.get()); + QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window2->surface()); +} + +} + +WAYLANDTEST_MAIN(KWin::InputStackingOrderTest) +#include "input_stacking_order.moc" diff --git a/autotests/integration/inputmethod_test.cpp b/autotests/integration/inputmethod_test.cpp new file mode 100644 index 0000000..0f2c845 --- /dev/null +++ b/autotests/integration/inputmethod_test.cpp @@ -0,0 +1,694 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2020 Marco Martin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "deleted.h" +#include "effects.h" +#include "inputmethod.h" +#include "keyboard_input.h" +#include "qwayland-input-method-unstable-v1.h" +#include "qwayland-text-input-unstable-v3.h" +#include "virtualkeyboard_dbus.h" +#include "wayland/clientconnection.h" +#include "wayland/display.h" +#include "wayland/seat_interface.h" +#include "wayland/surface_interface.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" +#include "xkb.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace KWin; +using namespace KWayland::Client; +using KWin::VirtualKeyboardDBus; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_inputmethod-0"); + +class InputMethodTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testOpenClose(); + void testEnableDisableV3(); + void testEnableActive(); + void testHidePanel(); + void testSwitchFocusedSurfaces(); + void testV2V3SameClient(); + void testV3Styling(); + void testDisableShowInputPanel(); + void testModifierForwarding(); + void testFakeEventFallback(); + +private: + void touchNow() + { + static int time = 0; + Test::touchDown(0, {100, 100}, ++time); + Test::touchUp(0, ++time); + } +}; + +void InputMethodTest::initTestCase() +{ + QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.kwin.testvirtualkeyboard")); + + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + static_cast(kwinApp())->setInputMethodServerToStart("internal"); + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); +} + +void InputMethodTest::init() +{ + touchNow(); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::TextInputManagerV2 | Test::AdditionalWaylandInterface::InputMethodV1 | Test::AdditionalWaylandInterface::TextInputManagerV3)); + + workspace()->setActiveOutput(QPoint(640, 512)); + KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512)); + + kwinApp()->inputMethod()->setEnabled(true); +} + +void InputMethodTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void InputMethodTest::testOpenClose() +{ + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + QSignalSpy windowRemovedSpy(workspace(), &Workspace::windowRemoved); + + // Create an xdg_toplevel surface and wait for the compositor to catch up. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::red); + QVERIFY(window); + QVERIFY(window->isActive()); + QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024)); + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + + std::unique_ptr textInput(Test::waylandTextInputManager()->createTextInput(Test::waylandSeat())); + QVERIFY(textInput != nullptr); + + // Show the keyboard + touchNow(); + textInput->enable(surface.get()); + textInput->showInputPanel(); + QSignalSpy paneladded(kwinApp()->inputMethod(), &KWin::InputMethod::panelChanged); + QVERIFY(windowAddedSpy.wait()); + QCOMPARE(paneladded.count(), 1); + + Window *keyboardClient = windowAddedSpy.last().first().value(); + QVERIFY(keyboardClient); + QVERIFY(keyboardClient->isInputMethod()); + + // Do the actual resize + QVERIFY(surfaceConfigureRequestedSpy.wait()); + + Test::render(surface.get(), toplevelConfigureRequestedSpy.last().first().value(), Qt::red); + QVERIFY(frameGeometryChangedSpy.wait()); + + QCOMPARE(window->frameGeometry().height(), 1024 - keyboardClient->inputGeometry().height()); + + // Hide the keyboard + textInput->hideInputPanel(); + + QVERIFY(surfaceConfigureRequestedSpy.wait()); + Test::render(surface.get(), toplevelConfigureRequestedSpy.last().first().value(), Qt::red); + QVERIFY(frameGeometryChangedSpy.wait()); + + QCOMPARE(window->frameGeometry().height(), 1024); + + // show the keyboard again + touchNow(); + textInput->enable(surface.get()); + textInput->showInputPanel(); + + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QVERIFY(keyboardClient->isShown()); + + // Destroy the test window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void InputMethodTest::testEnableDisableV3() +{ + // Create an xdg_toplevel surface and wait for the compositor to catch up. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::red); + QVERIFY(window); + QVERIFY(window->isActive()); + QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024)); + + auto textInputV3 = std::make_unique(); + textInputV3->init(Test::waylandTextInputManagerV3()->get_text_input(*(Test::waylandSeat()))); + + // Show the keyboard + touchNow(); + textInputV3->enable(); + + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + QSignalSpy inputMethodActiveSpy(kwinApp()->inputMethod(), &InputMethod::activeChanged); + // just enabling the text-input should not show it but rather on commit + QVERIFY(!kwinApp()->inputMethod()->isActive()); + textInputV3->commit(); + QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait()); + QVERIFY(kwinApp()->inputMethod()->isActive()); + + QVERIFY(windowAddedSpy.wait()); + Window *keyboardClient = windowAddedSpy.last().first().value(); + QVERIFY(keyboardClient); + QVERIFY(keyboardClient->isInputMethod()); + QVERIFY(keyboardClient->isShown()); + + // Text input v3 doesn't have hideInputPanel, just simiulate the hide from dbus call + kwinApp()->inputMethod()->hide(); + QVERIFY(!keyboardClient->isShown()); + + QSignalSpy windowShownSpy(keyboardClient, &Window::windowShown); + // Force enable the text input object. This is what's done by Gtk. + textInputV3->enable(); + textInputV3->commit(); + + windowShownSpy.wait(); + QVERIFY(keyboardClient->isShown()); + + // disable text input and ensure that it is not hiding input panel without commit + inputMethodActiveSpy.clear(); + QVERIFY(kwinApp()->inputMethod()->isActive()); + textInputV3->disable(); + textInputV3->commit(); + QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait()); + QVERIFY(!kwinApp()->inputMethod()->isActive()); +} + +void InputMethodTest::testEnableActive() +{ + // This test verifies that enabling text-input twice won't change the active input method status. + QVERIFY(!kwinApp()->inputMethod()->isActive()); + + // Create an xdg_toplevel surface and wait for the compositor to catch up. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::red); + QVERIFY(window); + QVERIFY(window->isActive()); + + // Show the keyboard + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + std::unique_ptr textInput(Test::waylandTextInputManager()->createTextInput(Test::waylandSeat())); + textInput->enable(surface.get()); + QSignalSpy paneladded(kwinApp()->inputMethod(), &KWin::InputMethod::panelChanged); + QVERIFY(paneladded.wait()); + textInput->showInputPanel(); + QVERIFY(windowAddedSpy.wait()); + QVERIFY(kwinApp()->inputMethod()->isActive()); + + // Ask the keyboard to be shown again. + QSignalSpy activateSpy(kwinApp()->inputMethod(), &InputMethod::activeChanged); + textInput->enable(surface.get()); + textInput->showInputPanel(); + activateSpy.wait(200); + QVERIFY(activateSpy.isEmpty()); + QVERIFY(kwinApp()->inputMethod()->isActive()); + + // Destroy the test window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void InputMethodTest::testHidePanel() +{ + QVERIFY(!kwinApp()->inputMethod()->isActive()); + + touchNow(); + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + QSignalSpy windowRemovedSpy(workspace(), &Workspace::windowRemoved); + + QSignalSpy activateSpy(kwinApp()->inputMethod(), &InputMethod::activeChanged); + std::unique_ptr textInput(Test::waylandTextInputManager()->createTextInput(Test::waylandSeat())); + + // Create an xdg_toplevel surface and wait for the compositor to catch up. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::red); + waylandServer()->seat()->setFocusedTextInputSurface(window->surface()); + + textInput->enable(surface.get()); + QSignalSpy paneladded(kwinApp()->inputMethod(), &KWin::InputMethod::panelChanged); + QVERIFY(paneladded.wait()); + textInput->showInputPanel(); + QVERIFY(windowAddedSpy.wait()); + + QCOMPARE(workspace()->activeWindow(), window); + + QCOMPARE(windowAddedSpy.count(), 2); + QVERIFY(activateSpy.count() || activateSpy.wait()); + QVERIFY(kwinApp()->inputMethod()->isActive()); + + auto keyboardWindow = kwinApp()->inputMethod()->panel(); + auto ipsurface = Test::inputPanelSurface(); + QVERIFY(keyboardWindow); + windowRemovedSpy.clear(); + delete ipsurface; + QVERIFY(kwinApp()->inputMethod()->isVisible()); + QVERIFY(windowRemovedSpy.count() || windowRemovedSpy.wait()); + QVERIFY(!kwinApp()->inputMethod()->isVisible()); + + // Destroy the test window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void InputMethodTest::testSwitchFocusedSurfaces() +{ + touchNow(); + QVERIFY(!kwinApp()->inputMethod()->isActive()); + + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + QSignalSpy windowRemovedSpy(workspace(), &Workspace::windowRemoved); + + QSignalSpy activateSpy(kwinApp()->inputMethod(), &InputMethod::activeChanged); + std::unique_ptr textInput(Test::waylandTextInputManager()->createTextInput(Test::waylandSeat())); + + QVector windows; + std::vector> surfaces; + QVector toplevels; + // We create 3 surfaces + for (int i = 0; i < 3; ++i) { + std::unique_ptr surface = Test::createSurface(); + auto shellSurface = Test::createXdgToplevelSurface(surface.get()); + windows += Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::red); + QCOMPARE(workspace()->activeWindow(), windows.constLast()); + surfaces.push_back(std::move(surface)); + toplevels += shellSurface; + } + QCOMPARE(windowAddedSpy.count(), 3); + waylandServer()->seat()->setFocusedTextInputSurface(windows.constFirst()->surface()); + + QVERIFY(!kwinApp()->inputMethod()->isActive()); + textInput->enable(surfaces.back().get()); + QVERIFY(!kwinApp()->inputMethod()->isActive()); + waylandServer()->seat()->setFocusedTextInputSurface(windows.first()->surface()); + QVERIFY(!kwinApp()->inputMethod()->isActive()); + activateSpy.clear(); + waylandServer()->seat()->setFocusedTextInputSurface(windows.last()->surface()); + QVERIFY(activateSpy.count() || activateSpy.wait()); + QVERIFY(kwinApp()->inputMethod()->isActive()); + + activateSpy.clear(); + waylandServer()->seat()->setFocusedTextInputSurface(windows.first()->surface()); + QVERIFY(activateSpy.count() || activateSpy.wait()); + QVERIFY(!kwinApp()->inputMethod()->isActive()); + + // Destroy the test window. + for (int i = 0; i < windows.count(); ++i) { + delete toplevels[i]; + QVERIFY(Test::waitForWindowDestroyed(windows[i])); + } +} + +void InputMethodTest::testV2V3SameClient() +{ + touchNow(); + QVERIFY(!kwinApp()->inputMethod()->isActive()); + + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + QSignalSpy windowRemovedSpy(workspace(), &Workspace::windowRemoved); + + QSignalSpy activateSpy(kwinApp()->inputMethod(), &InputMethod::activeChanged); + std::unique_ptr textInput(Test::waylandTextInputManager()->createTextInput(Test::waylandSeat())); + + auto textInputV3 = std::make_unique(); + textInputV3->init(Test::waylandTextInputManagerV3()->get_text_input(*(Test::waylandSeat()))); + + std::unique_ptr surface = Test::createSurface(); + std::unique_ptr toplevel(Test::createXdgToplevelSurface(surface.get())); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::red); + QCOMPARE(workspace()->activeWindow(), window); + QCOMPARE(windowAddedSpy.count(), 1); + waylandServer()->seat()->setFocusedTextInputSurface(window->surface()); + QVERIFY(!kwinApp()->inputMethod()->isActive()); + + // Enable and disable v2 + textInput->enable(surface.get()); + QVERIFY(activateSpy.count() || activateSpy.wait()); + QVERIFY(kwinApp()->inputMethod()->isActive()); + + activateSpy.clear(); + textInput->disable(surface.get()); + QVERIFY(activateSpy.count() || activateSpy.wait()); + QVERIFY(!kwinApp()->inputMethod()->isActive()); + + // Enable and disable v3 + activateSpy.clear(); + textInputV3->enable(); + textInputV3->commit(); + QVERIFY(activateSpy.count() || activateSpy.wait()); + QVERIFY(kwinApp()->inputMethod()->isActive()); + + activateSpy.clear(); + textInputV3->disable(); + textInputV3->commit(); + activateSpy.clear(); + QVERIFY(activateSpy.count() || activateSpy.wait()); + QVERIFY(!kwinApp()->inputMethod()->isActive()); + + // Enable v2 and v3 + activateSpy.clear(); + textInputV3->enable(); + textInputV3->commit(); + textInput->enable(surface.get()); + QVERIFY(activateSpy.count() || activateSpy.wait()); + QVERIFY(kwinApp()->inputMethod()->isActive()); + + // Disable v3, should still be active since v2 is active. + activateSpy.clear(); + textInputV3->disable(); + textInputV3->commit(); + activateSpy.wait(200); + QVERIFY(kwinApp()->inputMethod()->isActive()); + + // Disable v2 + activateSpy.clear(); + textInput->disable(surface.get()); + QVERIFY(activateSpy.count() || activateSpy.wait()); + QVERIFY(!kwinApp()->inputMethod()->isActive()); + + toplevel.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void InputMethodTest::testV3Styling() +{ + // Create an xdg_toplevel surface and wait for the compositor to catch up. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::red); + QVERIFY(window); + QVERIFY(window->isActive()); + QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024)); + + auto textInputV3 = std::make_unique(); + textInputV3->init(Test::waylandTextInputManagerV3()->get_text_input(*(Test::waylandSeat()))); + textInputV3->enable(); + + QSignalSpy inputMethodActiveSpy(kwinApp()->inputMethod(), &InputMethod::activeChanged); + QSignalSpy inputMethodActivateSpy(Test::inputMethod(), &Test::MockInputMethod::activate); + // just enabling the text-input should not show it but rather on commit + QVERIFY(!kwinApp()->inputMethod()->isActive()); + textInputV3->commit(); + QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait()); + QVERIFY(kwinApp()->inputMethod()->isActive()); + QVERIFY(inputMethodActivateSpy.wait()); + auto context = Test::inputMethod()->context(); + QSignalSpy textInputPreeditSpy(textInputV3.get(), &Test::TextInputV3::preeditString); + zwp_input_method_context_v1_preedit_cursor(context, 0); + zwp_input_method_context_v1_preedit_styling(context, 0, 3, 7); + zwp_input_method_context_v1_preedit_string(context, 0, "ABCD", "ABCD"); + QVERIFY(textInputPreeditSpy.wait()); + QCOMPARE(textInputPreeditSpy.last().at(0), QString("ABCD")); + QCOMPARE(textInputPreeditSpy.last().at(1), 0); + QCOMPARE(textInputPreeditSpy.last().at(2), 0); + + zwp_input_method_context_v1_preedit_cursor(context, 1); + zwp_input_method_context_v1_preedit_styling(context, 0, 3, 7); + zwp_input_method_context_v1_preedit_string(context, 0, "ABCDE", "ABCDE"); + QVERIFY(textInputPreeditSpy.wait()); + QCOMPARE(textInputPreeditSpy.last().at(0), QString("ABCDE")); + QCOMPARE(textInputPreeditSpy.last().at(1), 1); + QCOMPARE(textInputPreeditSpy.last().at(2), 1); + + zwp_input_method_context_v1_preedit_cursor(context, 2); + // Use selection for [2, 2+2) + zwp_input_method_context_v1_preedit_styling(context, 2, 2, 6); + // Use high light for [3, 3+3) + zwp_input_method_context_v1_preedit_styling(context, 3, 3, 4); + zwp_input_method_context_v1_preedit_string(context, 0, "ABCDEF", "ABCDEF"); + QVERIFY(textInputPreeditSpy.wait()); + QCOMPARE(textInputPreeditSpy.last().at(0), QString("ABCDEF")); + // Merged range should be [2, 6) + QCOMPARE(textInputPreeditSpy.last().at(1), 2); + QCOMPARE(textInputPreeditSpy.last().at(2), 6); + + zwp_input_method_context_v1_preedit_cursor(context, 2); + // Use selection for [0, 0+2) + zwp_input_method_context_v1_preedit_styling(context, 0, 2, 6); + // Use high light for [3, 3+3) + zwp_input_method_context_v1_preedit_styling(context, 3, 3, 4); + zwp_input_method_context_v1_preedit_string(context, 0, "ABCDEF", "ABCDEF"); + QVERIFY(textInputPreeditSpy.wait()); + QCOMPARE(textInputPreeditSpy.last().at(0), QString("ABCDEF")); + // Merged range should be none, because of the disjunction highlight. + QCOMPARE(textInputPreeditSpy.last().at(1), 2); + QCOMPARE(textInputPreeditSpy.last().at(2), 2); + + zwp_input_method_context_v1_preedit_cursor(context, 1); + // Use selection for [0, 0+2) + zwp_input_method_context_v1_preedit_styling(context, 0, 2, 6); + // Use high light for [2, 2+3) + zwp_input_method_context_v1_preedit_styling(context, 2, 3, 4); + zwp_input_method_context_v1_preedit_string(context, 0, "ABCDEF", "ABCDEF"); + QVERIFY(textInputPreeditSpy.wait()); + QCOMPARE(textInputPreeditSpy.last().at(0), QString("ABCDEF")); + // Merged range should be none, starting offset does not match. + QCOMPARE(textInputPreeditSpy.last().at(1), 1); + QCOMPARE(textInputPreeditSpy.last().at(2), 1); + + // Use different order of styling and cursor + // Use high light for [3, 3+3) + zwp_input_method_context_v1_preedit_styling(context, 3, 3, 4); + zwp_input_method_context_v1_preedit_cursor(context, 1); + // Use selection for [1, 1+2) + zwp_input_method_context_v1_preedit_styling(context, 1, 2, 6); + zwp_input_method_context_v1_preedit_string(context, 0, "ABCDEF", "ABCDEF"); + QVERIFY(textInputPreeditSpy.wait()); + QCOMPARE(textInputPreeditSpy.last().at(0), QString("ABCDEF")); + // Merged range should be [1,6). + QCOMPARE(textInputPreeditSpy.last().at(1), 1); + QCOMPARE(textInputPreeditSpy.last().at(2), 6); +} + +void InputMethodTest::testDisableShowInputPanel() +{ + // Create an xdg_toplevel surface and wait for the compositor to catch up. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::red); + QVERIFY(window); + QVERIFY(window->isActive()); + QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024)); + + std::unique_ptr textInputV2(Test::waylandTextInputManager()->createTextInput(Test::waylandSeat())); + + QSignalSpy inputMethodActiveSpy(kwinApp()->inputMethod(), &InputMethod::activeChanged); + // just enabling the text-input should not show it but rather on commit + QVERIFY(!kwinApp()->inputMethod()->isActive()); + textInputV2->enable(surface.get()); + QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait()); + QVERIFY(kwinApp()->inputMethod()->isActive()); + + // disable text input and ensure that it is not hiding input panel without commit + inputMethodActiveSpy.clear(); + QVERIFY(kwinApp()->inputMethod()->isActive()); + textInputV2->disable(surface.get()); + QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait()); + QVERIFY(!kwinApp()->inputMethod()->isActive()); + + QSignalSpy requestShowInputPanelSpy(waylandServer()->seat()->textInputV2(), &KWaylandServer::TextInputV2Interface::requestShowInputPanel); + textInputV2->showInputPanel(); + QVERIFY(requestShowInputPanelSpy.count() || requestShowInputPanelSpy.wait()); + QVERIFY(!kwinApp()->inputMethod()->isActive()); +} + +void InputMethodTest::testModifierForwarding() +{ + // Create an xdg_toplevel surface and wait for the compositor to catch up. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::red); + QVERIFY(window); + QVERIFY(window->isActive()); + QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024)); + + auto textInputV3 = std::make_unique(); + textInputV3->init(Test::waylandTextInputManagerV3()->get_text_input(*(Test::waylandSeat()))); + textInputV3->enable(); + + QSignalSpy inputMethodActiveSpy(kwinApp()->inputMethod(), &InputMethod::activeChanged); + QSignalSpy inputMethodActivateSpy(Test::inputMethod(), &Test::MockInputMethod::activate); + // just enabling the text-input should not show it but rather on commit + QVERIFY(!kwinApp()->inputMethod()->isActive()); + textInputV3->commit(); + QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait()); + QVERIFY(kwinApp()->inputMethod()->isActive()); + QVERIFY(inputMethodActivateSpy.wait()); + auto context = Test::inputMethod()->context(); + std::unique_ptr keyboardGrab(new KWayland::Client::Keyboard); + keyboardGrab->setup(zwp_input_method_context_v1_grab_keyboard(context)); + QSignalSpy modifierSpy(keyboardGrab.get(), &Keyboard::modifiersChanged); + // Wait for initial modifiers update + QVERIFY(modifierSpy.wait()); + + quint32 timestamp = 1; + + QSignalSpy keySpy(keyboardGrab.get(), &Keyboard::keyChanged); + bool keyChanged = false; + bool modifiersChanged = false; + // We want to verify the order of two signals, so SignalSpy is not very useful here. + auto keyChangedConnection = connect(keyboardGrab.get(), &Keyboard::keyChanged, [&keyChanged, &modifiersChanged]() { + QVERIFY(!modifiersChanged); + keyChanged = true; + }); + auto modifiersChangedConnection = connect(keyboardGrab.get(), &Keyboard::modifiersChanged, [&keyChanged, &modifiersChanged]() { + QVERIFY(keyChanged); + modifiersChanged = true; + }); + Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + QVERIFY(keySpy.count() == 1 || keySpy.wait()); + QVERIFY(modifierSpy.count() == 2 || modifierSpy.wait()); + disconnect(keyChangedConnection); + disconnect(modifiersChangedConnection); + + Test::keyboardKeyPressed(KEY_A, timestamp++); + QVERIFY(keySpy.count() == 2 || keySpy.wait()); + QVERIFY(modifierSpy.count() == 2 || modifierSpy.wait()); + + // verify the order of key and modifiers again. Key first, then modifiers. + keyChanged = false; + modifiersChanged = false; + keyChangedConnection = connect(keyboardGrab.get(), &Keyboard::keyChanged, [&keyChanged, &modifiersChanged]() { + QVERIFY(!modifiersChanged); + keyChanged = true; + }); + modifiersChangedConnection = connect(keyboardGrab.get(), &Keyboard::modifiersChanged, [&keyChanged, &modifiersChanged]() { + QVERIFY(keyChanged); + modifiersChanged = true; + }); + Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(keySpy.count() == 3 || keySpy.wait()); + QVERIFY(modifierSpy.count() == 3 || modifierSpy.wait()); + disconnect(keyChangedConnection); + disconnect(modifiersChangedConnection); +} + +void InputMethodTest::testFakeEventFallback() +{ + // Create an xdg_toplevel surface and wait for the compositor to catch up. + std::unique_ptr surface = Test::createSurface(); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::red); + QVERIFY(window); + QVERIFY(window->isActive()); + QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024)); + + // Since we don't have a way to communicate with the client, manually activate + // the input method. + QSignalSpy inputMethodActiveSpy(Test::inputMethod(), &Test::MockInputMethod::activate); + kwinApp()->inputMethod()->setActive(true); + QVERIFY(inputMethodActiveSpy.count() || inputMethodActiveSpy.wait()); + + // Without a way to communicate to the client, we send fake key events. This + // means the client needs to be able to receive them, so create a keyboard for + // the client and listen whether it gets the right events. + auto keyboard = Test::waylandSeat()->createKeyboard(window); + QSignalSpy keySpy(keyboard, &KWayland::Client::Keyboard::keyChanged); + + auto context = Test::inputMethod()->context(); + QVERIFY(context); + + // First, send a simple one-character string and check to see if that + // generates a key press followed by a key release on the client side. + zwp_input_method_context_v1_commit_string(context, 0, "a"); + + keySpy.wait(); + QVERIFY(keySpy.count() == 2); + + auto compare = [](const QList &input, quint32 key, Keyboard::KeyState state) { + auto inputKey = input.at(0).toInt(); + auto inputState = input.at(1).value(); + QCOMPARE(inputKey, key); + QCOMPARE(inputState, state); + }; + + compare(keySpy.at(0), KEY_A, Keyboard::KeyState::Pressed); + compare(keySpy.at(1), KEY_A, Keyboard::KeyState::Released); + + keySpy.clear(); + + // Capital letters are recognised and sent as a combination of Shift + the + // letter. + + zwp_input_method_context_v1_commit_string(context, 0, "A"); + + keySpy.wait(); + QVERIFY(keySpy.count() == 4); + + compare(keySpy.at(0), KEY_LEFTSHIFT, Keyboard::KeyState::Pressed); + compare(keySpy.at(1), KEY_A, Keyboard::KeyState::Pressed); + compare(keySpy.at(2), KEY_A, Keyboard::KeyState::Released); + compare(keySpy.at(3), KEY_LEFTSHIFT, Keyboard::KeyState::Released); + + keySpy.clear(); + + // Special keys are not sent through commit_string but instead use keysym. + auto enter = input()->keyboard()->xkb()->toKeysym(KEY_ENTER); + zwp_input_method_context_v1_keysym(context, 0, 0, enter, uint32_t(KWaylandServer::KeyboardKeyState::Pressed), 0); + zwp_input_method_context_v1_keysym(context, 0, 1, enter, uint32_t(KWaylandServer::KeyboardKeyState::Released), 0); + + keySpy.wait(); + QVERIFY(keySpy.count() == 2); + + compare(keySpy.at(0), KEY_ENTER, Keyboard::KeyState::Pressed); + compare(keySpy.at(1), KEY_ENTER, Keyboard::KeyState::Released); +} + +WAYLANDTEST_MAIN(InputMethodTest) + +#include "inputmethod_test.moc" diff --git a/autotests/integration/internal_window.cpp b/autotests/integration/internal_window.cpp new file mode 100644 index 0000000..17ea860 --- /dev/null +++ b/autotests/integration/internal_window.cpp @@ -0,0 +1,824 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "deleted.h" +#include "effects.h" +#include "internalwindow.h" +#include "wayland/surface_interface.h" +#include "wayland_server.h" +#include "workspace.h" + +#include +#include + +#include +#include +#include +#include + +#include + +using namespace KWayland::Client; + +Q_DECLARE_METATYPE(NET::WindowType); + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_internal_window-0"); + +class InternalWindowTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testEnterLeave(); + void testPointerPressRelease(); + void testPointerAxis(); + void testKeyboard_data(); + void testKeyboard(); + void testKeyboardShowWithoutActivating(); + void testKeyboardTriggersLeave(); + void testTouch(); + void testOpacity(); + void testMove(); + void testSkipCloseAnimation_data(); + void testSkipCloseAnimation(); + void testModifierClickUnrestrictedMove(); + void testModifierScroll(); + void testPopup(); + void testScale(); + void testWindowType_data(); + void testWindowType(); + void testChangeWindowType_data(); + void testChangeWindowType(); + void testEffectWindow(); + void testReentrantMoveResize(); + void testDismissPopup(); +}; + +class HelperWindow : public QRasterWindow +{ + Q_OBJECT +public: + HelperWindow(); + ~HelperWindow() override; + + QPoint latestGlobalMousePos() const + { + return m_latestGlobalMousePos; + } + Qt::MouseButtons pressedButtons() const + { + return m_pressedButtons; + } + +Q_SIGNALS: + void entered(); + void left(); + void mouseMoved(const QPoint &global); + void mousePressed(); + void mouseReleased(); + void wheel(); + void keyPressed(); + void keyReleased(); + +protected: + void paintEvent(QPaintEvent *event) override; + bool event(QEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void wheelEvent(QWheelEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; + +private: + QPoint m_latestGlobalMousePos; + Qt::MouseButtons m_pressedButtons = Qt::MouseButtons(); +}; + +HelperWindow::HelperWindow() + : QRasterWindow(nullptr) +{ + setFlags(Qt::FramelessWindowHint); +} + +HelperWindow::~HelperWindow() = default; + +void HelperWindow::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + QPainter p(this); + p.fillRect(0, 0, width(), height(), Qt::red); +} + +bool HelperWindow::event(QEvent *event) +{ + if (event->type() == QEvent::Enter) { + Q_EMIT entered(); + } + if (event->type() == QEvent::Leave) { + Q_EMIT left(); + } + return QRasterWindow::event(event); +} + +void HelperWindow::mouseMoveEvent(QMouseEvent *event) +{ + m_latestGlobalMousePos = event->globalPos(); + Q_EMIT mouseMoved(event->globalPos()); +} + +void HelperWindow::mousePressEvent(QMouseEvent *event) +{ + m_latestGlobalMousePos = event->globalPos(); + m_pressedButtons = event->buttons(); + Q_EMIT mousePressed(); +} + +void HelperWindow::mouseReleaseEvent(QMouseEvent *event) +{ + m_latestGlobalMousePos = event->globalPos(); + m_pressedButtons = event->buttons(); + Q_EMIT mouseReleased(); +} + +void HelperWindow::wheelEvent(QWheelEvent *event) +{ + Q_UNUSED(event) + Q_EMIT wheel(); +} + +void HelperWindow::keyPressEvent(QKeyEvent *event) +{ + Q_UNUSED(event) + Q_EMIT keyPressed(); +} + +void HelperWindow::keyReleaseEvent(QKeyEvent *event) +{ + Q_UNUSED(event) + Q_EMIT keyReleased(); +} + +void InternalWindowTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); +} + +void InternalWindowTest::init() +{ + Cursors::self()->mouse()->setPos(QPoint(512, 512)); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat)); + QVERIFY(Test::waitForWaylandKeyboard()); +} + +void InternalWindowTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void InternalWindowTest::testEnterLeave() +{ + QSignalSpy windowAddedSpy(workspace(), &Workspace::internalWindowAdded); + HelperWindow win; + QVERIFY(!workspace()->findInternal(nullptr)); + QVERIFY(!workspace()->findInternal(&win)); + win.setGeometry(0, 0, 100, 100); + win.show(); + + QTRY_COMPARE(windowAddedSpy.count(), 1); + QVERIFY(!workspace()->activeWindow()); + InternalWindow *window = windowAddedSpy.first().first().value(); + QVERIFY(window); + QVERIFY(window->isInternal()); + QVERIFY(!window->isDecorated()); + QCOMPARE(workspace()->findInternal(&win), window); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 100)); + QVERIFY(window->isShown()); + QVERIFY(workspace()->stackingOrder().contains(window)); + + QSignalSpy enterSpy(&win, &HelperWindow::entered); + QSignalSpy leaveSpy(&win, &HelperWindow::left); + QSignalSpy moveSpy(&win, &HelperWindow::mouseMoved); + + quint32 timestamp = 1; + Test::pointerMotion(QPoint(50, 50), timestamp++); + QTRY_COMPARE(moveSpy.count(), 1); + + Test::pointerMotion(QPoint(60, 50), timestamp++); + QTRY_COMPARE(moveSpy.count(), 2); + QCOMPARE(moveSpy[1].first().toPoint(), QPoint(60, 50)); + + Test::pointerMotion(QPoint(101, 50), timestamp++); + QTRY_COMPARE(leaveSpy.count(), 1); + + // set a mask on the window + win.setMask(QRegion(10, 20, 30, 40)); + // outside the mask we should not get an enter + Test::pointerMotion(QPoint(5, 5), timestamp++); + QVERIFY(!enterSpy.wait(100)); + QCOMPARE(enterSpy.count(), 1); + // inside the mask we should still get an enter + Test::pointerMotion(QPoint(25, 27), timestamp++); + QTRY_COMPARE(enterSpy.count(), 2); +} + +void InternalWindowTest::testPointerPressRelease() +{ + QSignalSpy windowAddedSpy(workspace(), &Workspace::internalWindowAdded); + HelperWindow win; + win.setGeometry(0, 0, 100, 100); + win.show(); + QSignalSpy pressSpy(&win, &HelperWindow::mousePressed); + QSignalSpy releaseSpy(&win, &HelperWindow::mouseReleased); + + QTRY_COMPARE(windowAddedSpy.count(), 1); + + quint32 timestamp = 1; + Test::pointerMotion(QPoint(50, 50), timestamp++); + + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + QTRY_COMPARE(pressSpy.count(), 1); + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + QTRY_COMPARE(releaseSpy.count(), 1); +} + +void InternalWindowTest::testPointerAxis() +{ + QSignalSpy windowAddedSpy(workspace(), &Workspace::internalWindowAdded); + HelperWindow win; + win.setGeometry(0, 0, 100, 100); + win.show(); + QSignalSpy wheelSpy(&win, &HelperWindow::wheel); + QTRY_COMPARE(windowAddedSpy.count(), 1); + + quint32 timestamp = 1; + Test::pointerMotion(QPoint(50, 50), timestamp++); + + Test::pointerAxisVertical(5.0, timestamp++); + QTRY_COMPARE(wheelSpy.count(), 1); + Test::pointerAxisHorizontal(5.0, timestamp++); + QTRY_COMPARE(wheelSpy.count(), 2); +} + +void InternalWindowTest::testKeyboard_data() +{ + QTest::addColumn("cursorPos"); + + QTest::newRow("on Window") << QPoint(50, 50); + QTest::newRow("outside Window") << QPoint(250, 250); +} + +void InternalWindowTest::testKeyboard() +{ + QSignalSpy windowAddedSpy(workspace(), &Workspace::internalWindowAdded); + HelperWindow win; + win.setGeometry(0, 0, 100, 100); + win.show(); + QSignalSpy pressSpy(&win, &HelperWindow::keyPressed); + QSignalSpy releaseSpy(&win, &HelperWindow::keyReleased); + QTRY_COMPARE(windowAddedSpy.count(), 1); + auto internalWindow = windowAddedSpy.first().first().value(); + QVERIFY(internalWindow); + QVERIFY(internalWindow->isInternal()); + QVERIFY(internalWindow->readyForPainting()); + + quint32 timestamp = 1; + QFETCH(QPoint, cursorPos); + Test::pointerMotion(cursorPos, timestamp++); + + Test::keyboardKeyPressed(KEY_A, timestamp++); + QTRY_COMPARE(pressSpy.count(), 1); + QCOMPARE(releaseSpy.count(), 0); + Test::keyboardKeyReleased(KEY_A, timestamp++); + QTRY_COMPARE(releaseSpy.count(), 1); + QCOMPARE(pressSpy.count(), 1); +} + +void InternalWindowTest::testKeyboardShowWithoutActivating() +{ + QSignalSpy windowAddedSpy(workspace(), &Workspace::internalWindowAdded); + HelperWindow win; + win.setProperty("_q_showWithoutActivating", true); + win.setGeometry(0, 0, 100, 100); + win.show(); + QSignalSpy pressSpy(&win, &HelperWindow::keyPressed); + QSignalSpy releaseSpy(&win, &HelperWindow::keyReleased); + QTRY_COMPARE(windowAddedSpy.count(), 1); + auto internalWindow = windowAddedSpy.first().first().value(); + QVERIFY(internalWindow); + QVERIFY(internalWindow->isInternal()); + QVERIFY(internalWindow->readyForPainting()); + + quint32 timestamp = 1; + const QPoint cursorPos = QPoint(50, 50); + Test::pointerMotion(cursorPos, timestamp++); + + Test::keyboardKeyPressed(KEY_A, timestamp++); + QCOMPARE(pressSpy.count(), 0); + QVERIFY(!pressSpy.wait(100)); + QCOMPARE(releaseSpy.count(), 0); + Test::keyboardKeyReleased(KEY_A, timestamp++); + QCOMPARE(releaseSpy.count(), 0); + QVERIFY(!releaseSpy.wait(100)); + QCOMPARE(pressSpy.count(), 0); +} + +void InternalWindowTest::testKeyboardTriggersLeave() +{ + // this test verifies that a leave event is sent to a window when an internal window + // gets a key event + std::unique_ptr keyboard(Test::waylandSeat()->createKeyboard()); + QVERIFY(keyboard != nullptr); + QVERIFY(keyboard->isValid()); + QSignalSpy enteredSpy(keyboard.get(), &Keyboard::entered); + QSignalSpy leftSpy(keyboard.get(), &Keyboard::left); + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + + // now let's render + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(window->isActive()); + QVERIFY(!window->isInternal()); + + if (enteredSpy.isEmpty()) { + QVERIFY(enteredSpy.wait()); + } + QCOMPARE(enteredSpy.count(), 1); + + // create internal window + QSignalSpy windowAddedSpy(workspace(), &Workspace::internalWindowAdded); + HelperWindow win; + win.setGeometry(0, 0, 100, 100); + win.show(); + QSignalSpy pressSpy(&win, &HelperWindow::keyPressed); + QSignalSpy releaseSpy(&win, &HelperWindow::keyReleased); + QTRY_COMPARE(windowAddedSpy.count(), 1); + auto internalWindow = windowAddedSpy.first().first().value(); + QVERIFY(internalWindow); + QVERIFY(internalWindow->isInternal()); + QVERIFY(internalWindow->readyForPainting()); + + QVERIFY(leftSpy.isEmpty()); + QVERIFY(!leftSpy.wait(100)); + + // now let's trigger a key, which should result in a leave + quint32 timestamp = 1; + Test::keyboardKeyPressed(KEY_A, timestamp++); + QVERIFY(leftSpy.wait()); + QCOMPARE(pressSpy.count(), 1); + + Test::keyboardKeyReleased(KEY_A, timestamp++); + QTRY_COMPARE(releaseSpy.count(), 1); + + // after hiding the internal window, next key press should trigger an enter + win.hide(); + Test::keyboardKeyPressed(KEY_A, timestamp++); + QVERIFY(enteredSpy.wait()); + Test::keyboardKeyReleased(KEY_A, timestamp++); + + // Destroy the test window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void InternalWindowTest::testTouch() +{ + // touch events for internal windows are emulated through mouse events + QSignalSpy windowAddedSpy(workspace(), &Workspace::internalWindowAdded); + HelperWindow win; + win.setGeometry(0, 0, 100, 100); + win.show(); + QTRY_COMPARE(windowAddedSpy.count(), 1); + + QSignalSpy pressSpy(&win, &HelperWindow::mousePressed); + QSignalSpy releaseSpy(&win, &HelperWindow::mouseReleased); + QSignalSpy moveSpy(&win, &HelperWindow::mouseMoved); + + quint32 timestamp = 1; + QCOMPARE(win.pressedButtons(), Qt::MouseButtons()); + Test::touchDown(0, QPointF(50, 50), timestamp++); + QCOMPARE(pressSpy.count(), 1); + QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50)); + QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); + + // further touch down should not trigger + Test::touchDown(1, QPointF(75, 75), timestamp++); + QCOMPARE(pressSpy.count(), 1); + Test::touchUp(1, timestamp++); + QCOMPARE(releaseSpy.count(), 0); + QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50)); + QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); + + // another press + Test::touchDown(1, QPointF(10, 10), timestamp++); + QCOMPARE(pressSpy.count(), 1); + QCOMPARE(win.latestGlobalMousePos(), QPoint(50, 50)); + QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); + + // simulate the move + QCOMPARE(moveSpy.count(), 0); + Test::touchMotion(0, QPointF(80, 90), timestamp++); + QCOMPARE(moveSpy.count(), 1); + QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90)); + QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); + + // move on other ID should not do anything + Test::touchMotion(1, QPointF(20, 30), timestamp++); + QCOMPARE(moveSpy.count(), 1); + QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90)); + QCOMPARE(win.pressedButtons(), Qt::MouseButtons(Qt::LeftButton)); + + // now up our main point + Test::touchUp(0, timestamp++); + QCOMPARE(releaseSpy.count(), 1); + QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90)); + QCOMPARE(win.pressedButtons(), Qt::MouseButtons()); + + // and up the additional point + Test::touchUp(1, timestamp++); + QCOMPARE(releaseSpy.count(), 1); + QCOMPARE(moveSpy.count(), 1); + QCOMPARE(win.latestGlobalMousePos(), QPoint(80, 90)); + QCOMPARE(win.pressedButtons(), Qt::MouseButtons()); +} + +void InternalWindowTest::testOpacity() +{ + // this test verifies that opacity is properly synced from QWindow to InternalClient + QSignalSpy windowAddedSpy(workspace(), &Workspace::internalWindowAdded); + HelperWindow win; + win.setOpacity(0.5); + win.setGeometry(0, 0, 100, 100); + win.show(); + QTRY_COMPARE(windowAddedSpy.count(), 1); + auto internalWindow = windowAddedSpy.first().first().value(); + QVERIFY(internalWindow); + QVERIFY(internalWindow->isInternal()); + QCOMPARE(internalWindow->opacity(), 0.5); + + QSignalSpy opacityChangedSpy(internalWindow, &InternalWindow::opacityChanged); + win.setOpacity(0.75); + QCOMPARE(opacityChangedSpy.count(), 1); + QCOMPARE(internalWindow->opacity(), 0.75); +} + +void InternalWindowTest::testMove() +{ + QSignalSpy windowAddedSpy(workspace(), &Workspace::internalWindowAdded); + HelperWindow win; + win.setOpacity(0.5); + win.setGeometry(0, 0, 100, 100); + win.show(); + QTRY_COMPARE(windowAddedSpy.count(), 1); + auto internalWindow = windowAddedSpy.first().first().value(); + QVERIFY(internalWindow); + QCOMPARE(internalWindow->frameGeometry(), QRect(0, 0, 100, 100)); + + // normal move should be synced + internalWindow->move(QPoint(5, 10)); + QCOMPARE(internalWindow->frameGeometry(), QRect(5, 10, 100, 100)); + QTRY_COMPARE(win.geometry(), QRect(5, 10, 100, 100)); + // another move should also be synced + internalWindow->move(QPoint(10, 20)); + QCOMPARE(internalWindow->frameGeometry(), QRect(10, 20, 100, 100)); + QTRY_COMPARE(win.geometry(), QRect(10, 20, 100, 100)); + + // now move with a Geometry update blocker + { + GeometryUpdatesBlocker blocker(internalWindow); + internalWindow->move(QPoint(5, 10)); + // not synced! + QCOMPARE(win.geometry(), QRect(10, 20, 100, 100)); + } + // after destroying the blocker it should be synced + QTRY_COMPARE(win.geometry(), QRect(5, 10, 100, 100)); +} + +void InternalWindowTest::testSkipCloseAnimation_data() +{ + QTest::addColumn("initial"); + + QTest::newRow("set") << true; + QTest::newRow("not set") << false; +} + +void InternalWindowTest::testSkipCloseAnimation() +{ + QSignalSpy windowAddedSpy(workspace(), &Workspace::internalWindowAdded); + HelperWindow win; + win.setOpacity(0.5); + win.setGeometry(0, 0, 100, 100); + QFETCH(bool, initial); + win.setProperty("KWIN_SKIP_CLOSE_ANIMATION", initial); + win.show(); + QTRY_COMPARE(windowAddedSpy.count(), 1); + auto internalWindow = windowAddedSpy.first().first().value(); + QVERIFY(internalWindow); + QCOMPARE(internalWindow->skipsCloseAnimation(), initial); + QSignalSpy skipCloseChangedSpy(internalWindow, &Window::skipCloseAnimationChanged); + win.setProperty("KWIN_SKIP_CLOSE_ANIMATION", !initial); + QCOMPARE(skipCloseChangedSpy.count(), 1); + QCOMPARE(internalWindow->skipsCloseAnimation(), !initial); + win.setProperty("KWIN_SKIP_CLOSE_ANIMATION", initial); + QCOMPARE(skipCloseChangedSpy.count(), 2); + QCOMPARE(internalWindow->skipsCloseAnimation(), initial); +} + +void InternalWindowTest::testModifierClickUnrestrictedMove() +{ + QSignalSpy windowAddedSpy(workspace(), &Workspace::internalWindowAdded); + HelperWindow win; + win.setGeometry(0, 0, 100, 100); + win.setFlags(win.flags() & ~Qt::FramelessWindowHint); + win.show(); + QTRY_COMPARE(windowAddedSpy.count(), 1); + auto internalWindow = windowAddedSpy.first().first().value(); + QVERIFY(internalWindow); + QVERIFY(internalWindow->isDecorated()); + + KConfigGroup group = kwinApp()->config()->group("MouseBindings"); + group.writeEntry("CommandAllKey", "Meta"); + group.writeEntry("CommandAll1", "Move"); + group.writeEntry("CommandAll2", "Move"); + group.writeEntry("CommandAll3", "Move"); + group.sync(); + workspace()->slotReconfigure(); + QCOMPARE(options->commandAllModifier(), Qt::MetaModifier); + QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove); + QCOMPARE(options->commandAll2(), Options::MouseUnrestrictedMove); + QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedMove); + + // move cursor on window + Cursors::self()->mouse()->setPos(internalWindow->frameGeometry().center()); + + // simulate modifier+click + quint32 timestamp = 1; + Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++); + QVERIFY(!internalWindow->isInteractiveMove()); + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + QVERIFY(internalWindow->isInteractiveMove()); + // release modifier should not change it + Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++); + QVERIFY(internalWindow->isInteractiveMove()); + // but releasing the key should end move/resize + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + QVERIFY(!internalWindow->isInteractiveMove()); +} + +void InternalWindowTest::testModifierScroll() +{ + QSignalSpy windowAddedSpy(workspace(), &Workspace::internalWindowAdded); + HelperWindow win; + win.setGeometry(0, 0, 100, 100); + win.setFlags(win.flags() & ~Qt::FramelessWindowHint); + win.show(); + QTRY_COMPARE(windowAddedSpy.count(), 1); + auto internalWindow = windowAddedSpy.first().first().value(); + QVERIFY(internalWindow); + QVERIFY(internalWindow->isDecorated()); + + KConfigGroup group = kwinApp()->config()->group("MouseBindings"); + group.writeEntry("CommandAllKey", "Meta"); + group.writeEntry("CommandAllWheel", "change opacity"); + group.sync(); + workspace()->slotReconfigure(); + + // move cursor on window + Cursors::self()->mouse()->setPos(internalWindow->frameGeometry().center()); + + // set the opacity to 0.5 + internalWindow->setOpacity(0.5); + QCOMPARE(internalWindow->opacity(), 0.5); + quint32 timestamp = 1; + Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++); + Test::pointerAxisVertical(-5, timestamp++); + QCOMPARE(internalWindow->opacity(), 0.6); + Test::pointerAxisVertical(5, timestamp++); + QCOMPARE(internalWindow->opacity(), 0.5); + Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++); +} + +void InternalWindowTest::testPopup() +{ + QSignalSpy windowAddedSpy(workspace(), &Workspace::internalWindowAdded); + HelperWindow win; + win.setGeometry(0, 0, 100, 100); + win.setFlags(win.flags() | Qt::Popup); + win.show(); + QTRY_COMPARE(windowAddedSpy.count(), 1); + auto internalWindow = windowAddedSpy.first().first().value(); + QVERIFY(internalWindow); + QCOMPARE(internalWindow->isPopupWindow(), true); +} + +void InternalWindowTest::testScale() +{ + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, + Q_ARG(int, 2), + Q_ARG(QVector, QVector({QRect(0, 0, 1280, 1024), QRect(1280 / 2, 0, 1280, 1024)})), + Q_ARG(QVector, QVector({2, 2}))); + + QSignalSpy windowAddedSpy(workspace(), &Workspace::internalWindowAdded); + HelperWindow win; + win.setGeometry(0, 0, 100, 100); + win.setFlags(win.flags() | Qt::Popup); + win.show(); + QCOMPARE(win.devicePixelRatio(), 2.0); + QTRY_COMPARE(windowAddedSpy.count(), 1); + auto internalWindow = windowAddedSpy.first().first().value(); + QCOMPARE(internalWindow->bufferScale(), 2); +} + +void InternalWindowTest::testWindowType_data() +{ + QTest::addColumn("windowType"); + + QTest::newRow("normal") << NET::Normal; + QTest::newRow("desktop") << NET::Desktop; + QTest::newRow("Dock") << NET::Dock; + QTest::newRow("Toolbar") << NET::Toolbar; + QTest::newRow("Menu") << NET::Menu; + QTest::newRow("Dialog") << NET::Dialog; + QTest::newRow("Utility") << NET::Utility; + QTest::newRow("Splash") << NET::Splash; + QTest::newRow("DropdownMenu") << NET::DropdownMenu; + QTest::newRow("PopupMenu") << NET::PopupMenu; + QTest::newRow("Tooltip") << NET::Tooltip; + QTest::newRow("Notification") << NET::Notification; + QTest::newRow("ComboBox") << NET::ComboBox; + QTest::newRow("OnScreenDisplay") << NET::OnScreenDisplay; + QTest::newRow("CriticalNotification") << NET::CriticalNotification; + QTest::newRow("AppletPopup") << NET::AppletPopup; +} + +void InternalWindowTest::testWindowType() +{ + QSignalSpy windowAddedSpy(workspace(), &Workspace::internalWindowAdded); + HelperWindow win; + win.setGeometry(0, 0, 100, 100); + QFETCH(NET::WindowType, windowType); + KWindowSystem::setType(win.winId(), windowType); + win.show(); + QTRY_COMPARE(windowAddedSpy.count(), 1); + auto internalWindow = windowAddedSpy.first().first().value(); + QVERIFY(internalWindow); + QCOMPARE(internalWindow->windowType(), windowType); +} + +void InternalWindowTest::testChangeWindowType_data() +{ + QTest::addColumn("windowType"); + + QTest::newRow("desktop") << NET::Desktop; + QTest::newRow("Dock") << NET::Dock; + QTest::newRow("Toolbar") << NET::Toolbar; + QTest::newRow("Menu") << NET::Menu; + QTest::newRow("Dialog") << NET::Dialog; + QTest::newRow("Utility") << NET::Utility; + QTest::newRow("Splash") << NET::Splash; + QTest::newRow("DropdownMenu") << NET::DropdownMenu; + QTest::newRow("PopupMenu") << NET::PopupMenu; + QTest::newRow("Tooltip") << NET::Tooltip; + QTest::newRow("Notification") << NET::Notification; + QTest::newRow("ComboBox") << NET::ComboBox; + QTest::newRow("OnScreenDisplay") << NET::OnScreenDisplay; + QTest::newRow("CriticalNotification") << NET::CriticalNotification; + QTest::newRow("AppletPopup") << NET::AppletPopup; +} + +void InternalWindowTest::testChangeWindowType() +{ + QSignalSpy windowAddedSpy(workspace(), &Workspace::internalWindowAdded); + HelperWindow win; + win.setGeometry(0, 0, 100, 100); + win.show(); + QTRY_COMPARE(windowAddedSpy.count(), 1); + auto internalWindow = windowAddedSpy.first().first().value(); + QVERIFY(internalWindow); + QCOMPARE(internalWindow->windowType(), NET::Normal); + + QFETCH(NET::WindowType, windowType); + KWindowSystem::setType(win.winId(), windowType); + QTRY_COMPARE(internalWindow->windowType(), windowType); + + KWindowSystem::setType(win.winId(), NET::Normal); + QTRY_COMPARE(internalWindow->windowType(), NET::Normal); +} + +void InternalWindowTest::testEffectWindow() +{ + QSignalSpy windowAddedSpy(workspace(), &Workspace::internalWindowAdded); + HelperWindow win; + win.setGeometry(0, 0, 100, 100); + win.show(); + QTRY_COMPARE(windowAddedSpy.count(), 1); + auto internalWindow = windowAddedSpy.first().first().value(); + QVERIFY(internalWindow); + QVERIFY(internalWindow->effectWindow()); + QCOMPARE(internalWindow->effectWindow()->internalWindow(), &win); + + QCOMPARE(effects->findWindow(&win), internalWindow->effectWindow()); + QCOMPARE(effects->findWindow(&win)->internalWindow(), &win); +} + +void InternalWindowTest::testReentrantMoveResize() +{ + // This test verifies that calling moveResize() from a slot connected directly + // to the frameGeometryChanged() signal won't cause an infinite recursion. + + // Create an internal window. + QSignalSpy windowAddedSpy(workspace(), &Workspace::internalWindowAdded); + HelperWindow win; + win.setGeometry(0, 0, 100, 100); + win.show(); + QTRY_COMPARE(windowAddedSpy.count(), 1); + auto window = windowAddedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->pos(), QPoint(0, 0)); + + // Let's pretend that there is a script that really wants the window to be at (100, 100). + connect(window, &Window::frameGeometryChanged, this, [window]() { + window->moveResize(QRectF(QPointF(100, 100), window->size())); + }); + + // Trigger the lambda above. + window->move(QPoint(40, 50)); + + // Eventually, the window will end up at (100, 100). + QCOMPARE(window->pos(), QPoint(100, 100)); +} + +void InternalWindowTest::testDismissPopup() +{ + // This test verifies that a popup window created by the compositor will be dismissed + // when user clicks another window. + + // Create a toplevel window. + QSignalSpy windowAddedSpy(workspace(), &Workspace::internalWindowAdded); + HelperWindow clientToplevel; + clientToplevel.setGeometry(0, 0, 100, 100); + clientToplevel.show(); + QTRY_COMPARE(windowAddedSpy.count(), 1); + auto serverToplevel = windowAddedSpy.last().first().value(); + QVERIFY(serverToplevel); + + // Create a popup window. + QRasterWindow clientPopup; + clientPopup.setFlag(Qt::Popup); + clientPopup.setTransientParent(&clientToplevel); + clientPopup.setGeometry(0, 0, 50, 50); + clientPopup.show(); + QTRY_COMPARE(windowAddedSpy.count(), 2); + auto serverPopup = windowAddedSpy.last().first().value(); + QVERIFY(serverPopup); + + // Create the other window to click + HelperWindow otherClientToplevel; + otherClientToplevel.setGeometry(100, 100, 100, 100); + otherClientToplevel.show(); + QTRY_COMPARE(windowAddedSpy.count(), 3); + auto serverOtherToplevel = windowAddedSpy.last().first().value(); + QVERIFY(serverOtherToplevel); + + // Click somewhere outside the popup window. + QSignalSpy popupClosedSpy(serverPopup, &InternalWindow::windowClosed); + quint32 timestamp = 0; + Test::pointerMotion(serverOtherToplevel->frameGeometry().center(), timestamp++); + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + QTRY_COMPARE(popupClosedSpy.count(), 1); +} + +} + +WAYLANDTEST_MAIN(KWin::InternalWindowTest) +#include "internal_window.moc" diff --git a/autotests/integration/keyboard_layout_test.cpp b/autotests/integration/keyboard_layout_test.cpp new file mode 100644 index 0000000..01063bd --- /dev/null +++ b/autotests/integration/keyboard_layout_test.cpp @@ -0,0 +1,558 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2017 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/platform.h" +#include "keyboard_input.h" +#include "keyboard_layout.h" +#include "virtualdesktops.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +using namespace KWin; +using namespace KWayland::Client; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_keyboard_laout-0"); + +class KeyboardLayoutTest : public QObject +{ + Q_OBJECT +public: + KeyboardLayoutTest() + : layoutsReconfiguredSpy(this, &KeyboardLayoutTest::layoutListChanged) + , layoutChangedSpy(this, &KeyboardLayoutTest::layoutChanged) + { + + QVERIFY(QDBusConnection::sessionBus().connect(QStringLiteral("org.kde.keyboard"), QStringLiteral("/Layouts"), QStringLiteral("org.kde.KeyboardLayouts"), QStringLiteral("layoutListChanged"), this, SIGNAL(layoutListChanged()))); + QVERIFY(QDBusConnection::sessionBus().connect(QStringLiteral("org.kde.keyboard"), QStringLiteral("/Layouts"), QStringLiteral("org.kde.KeyboardLayouts"), QStringLiteral("layoutChanged"), this, SIGNAL(layoutChanged(uint)))); + } + +Q_SIGNALS: + void layoutChanged(uint index); + void layoutListChanged(); + +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testReconfigure(); + void testChangeLayoutThroughDBus(); + void testPerLayoutShortcut(); + void testDBusServiceExport(); + void testVirtualDesktopPolicy(); + void testWindowPolicy(); + void testApplicationPolicy(); + void testNumLock(); + +private: + void reconfigureLayouts(); + void resetLayouts(); + auto changeLayout(uint index); + void callSession(const QString &method); + QSignalSpy layoutsReconfiguredSpy; + QSignalSpy layoutChangedSpy; + KConfigGroup layoutGroup; +}; + +void KeyboardLayoutTest::reconfigureLayouts() +{ + // create DBus signal to reload + QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/Layouts"), QStringLiteral("org.kde.keyboard"), QStringLiteral("reloadConfig")); + QVERIFY(QDBusConnection::sessionBus().send(message)); + + QVERIFY(layoutsReconfiguredSpy.wait(1000)); + QCOMPARE(layoutsReconfiguredSpy.count(), 1); + layoutsReconfiguredSpy.clear(); +} + +void KeyboardLayoutTest::resetLayouts() +{ + /* Switch Policy to destroy layouts from memory. + * On return to original Policy they should reload from disk. + */ + callSession(QStringLiteral("aboutToSaveSession")); + + const QString policy = layoutGroup.readEntry("SwitchMode", "Global"); + + if (policy == QLatin1String("Global")) { + layoutGroup.writeEntry("SwitchMode", "Desktop"); + } else { + layoutGroup.deleteEntry("SwitchMode"); + } + reconfigureLayouts(); + + layoutGroup.writeEntry("SwitchMode", policy); + reconfigureLayouts(); + + callSession(QStringLiteral("loadSession")); +} + +auto KeyboardLayoutTest::changeLayout(uint index) +{ + QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.keyboard"), + QStringLiteral("/Layouts"), + QStringLiteral("org.kde.KeyboardLayouts"), + QStringLiteral("setLayout")); + msg << index; + return QDBusConnection::sessionBus().asyncCall(msg); +} + +void KeyboardLayoutTest::callSession(const QString &method) +{ + QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"), + QStringLiteral("/Session"), + QStringLiteral("org.kde.KWin.Session"), + method); + msg << QLatin1String(); // session name + QVERIFY(QDBusConnection::sessionBus().call(msg).type() != QDBusMessage::ErrorMessage); +} + +void KeyboardLayoutTest::initTestCase() +{ + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); + kwinApp()->setKxkbConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); + + layoutGroup = kwinApp()->kxkbConfig()->group("Layout"); + layoutGroup.deleteGroup(); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + + // don't get DBus signal on one-layout configuration + // QVERIFY(layoutsReconfiguredSpy.wait()); + // QCOMPARE(layoutsReconfiguredSpy.count(), 1); + // layoutsReconfiguredSpy.clear(); +} + +void KeyboardLayoutTest::init() +{ + QVERIFY(Test::setupWaylandConnection()); +} + +void KeyboardLayoutTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void KeyboardLayoutTest::testReconfigure() +{ + // verifies that we can change the keymap + + // default should be a keymap with only us layout + auto xkb = input()->keyboard()->xkb(); + QCOMPARE(xkb->numberOfLayouts(), 1u); + QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)")); + QCOMPARE(xkb->numberOfLayouts(), 1); + QCOMPARE(xkb->layoutName(0), QStringLiteral("English (US)")); + + // create a new keymap + KConfigGroup layoutGroup = kwinApp()->kxkbConfig()->group("Layout"); + layoutGroup.writeEntry("LayoutList", QStringLiteral("de,us")); + layoutGroup.sync(); + + reconfigureLayouts(); + // now we should have two layouts + QCOMPARE(xkb->numberOfLayouts(), 2u); + // default layout is German + QCOMPARE(xkb->layoutName(), QStringLiteral("German")); + QCOMPARE(xkb->numberOfLayouts(), 2); + QCOMPARE(xkb->layoutName(0), QStringLiteral("German")); + QCOMPARE(xkb->layoutName(1), QStringLiteral("English (US)")); +} + +void KeyboardLayoutTest::testChangeLayoutThroughDBus() +{ + // this test verifies that the layout can be changed through DBus + // first configure layouts + enum Layout { + de, + us, + de_neo, + bad, + }; + layoutGroup.writeEntry("LayoutList", QStringLiteral("de,us,de(neo)")); + layoutGroup.sync(); + reconfigureLayouts(); + // now we should have three layouts + auto xkb = input()->keyboard()->xkb(); + QCOMPARE(xkb->numberOfLayouts(), 3u); + // default layout is German + xkb->switchToLayout(0); + QCOMPARE(xkb->layoutName(), QStringLiteral("German")); + + // place garbage to layout entry + layoutGroup.writeEntry("LayoutDefaultFoo", "garbage"); + // make sure the garbage is wiped out on saving + resetLayouts(); + QVERIFY(!layoutGroup.hasKey("LayoutDefaultFoo")); + + // now change through DBus to English + auto reply = changeLayout(Layout::us); + reply.waitForFinished(); + QVERIFY(!reply.isError()); + QCOMPARE(reply.reply().arguments().first().toBool(), true); + QVERIFY(layoutChangedSpy.wait()); + QCOMPARE(layoutChangedSpy.count(), 1); + layoutChangedSpy.clear(); + + // layout should persist after reset + resetLayouts(); + QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)")); + QVERIFY(layoutChangedSpy.wait()); + QCOMPARE(layoutChangedSpy.count(), 1); + layoutChangedSpy.clear(); + + // switch to a layout which does not exist + reply = changeLayout(Layout::bad); + QVERIFY(!reply.isError()); + QCOMPARE(reply.reply().arguments().first().toBool(), false); + QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)")); + QVERIFY(!layoutChangedSpy.wait(1000)); + + // switch to another layout should work + reply = changeLayout(Layout::de); + QVERIFY(!reply.isError()); + QCOMPARE(reply.reply().arguments().first().toBool(), true); + QCOMPARE(xkb->layoutName(), QStringLiteral("German")); + QVERIFY(layoutChangedSpy.wait(1000)); + QCOMPARE(layoutChangedSpy.count(), 1); + + // switching to same layout should also work + reply = changeLayout(Layout::de); + QVERIFY(!reply.isError()); + QCOMPARE(reply.reply().arguments().first().toBool(), true); + QCOMPARE(xkb->layoutName(), QStringLiteral("German")); + QVERIFY(!layoutChangedSpy.wait(1000)); +} + +void KeyboardLayoutTest::testPerLayoutShortcut() +{ + // this test verifies that per-layout global shortcuts are working correctly. + // first configure layouts + layoutGroup.writeEntry("LayoutList", QStringLiteral("us,de,de(neo)")); + layoutGroup.sync(); + + // and create the global shortcuts + const QString componentName = QStringLiteral("KDE Keyboard Layout Switcher"); + QAction *a = new QAction(this); + a->setObjectName(QStringLiteral("Switch keyboard layout to English (US)")); + a->setProperty("componentName", componentName); + KGlobalAccel::self()->setShortcut(a, QList{Qt::CTRL | Qt::ALT | Qt::Key_1}, KGlobalAccel::NoAutoloading); + delete a; + a = new QAction(this); + a->setObjectName(QStringLiteral("Switch keyboard layout to German")); + a->setProperty("componentName", componentName); + KGlobalAccel::self()->setShortcut(a, QList{Qt::CTRL | Qt::ALT | Qt::Key_2}, KGlobalAccel::NoAutoloading); + delete a; + + // now we should have three layouts + auto xkb = input()->keyboard()->xkb(); + reconfigureLayouts(); + QCOMPARE(xkb->numberOfLayouts(), 3u); + // default layout is English + xkb->switchToLayout(0); + QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)")); + + // now switch to English through the global shortcut + quint32 timestamp = 1; + Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); + Test::keyboardKeyPressed(KEY_2, timestamp++); + QVERIFY(layoutChangedSpy.wait()); + // now layout should be German + QCOMPARE(xkb->layoutName(), QStringLiteral("German")); + // release keys again + Test::keyboardKeyReleased(KEY_2, timestamp++); + // switch back to English + Test::keyboardKeyPressed(KEY_1, timestamp++); + QVERIFY(layoutChangedSpy.wait()); + QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)")); + // release keys again + Test::keyboardKeyReleased(KEY_1, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); +} + +void KeyboardLayoutTest::testDBusServiceExport() +{ + // verifies that the dbus service is only exported if there are at least two layouts + + // first configure layouts, with just one layout + layoutGroup.writeEntry("LayoutList", QStringLiteral("us")); + layoutGroup.sync(); + reconfigureLayouts(); + auto xkb = input()->keyboard()->xkb(); + QCOMPARE(xkb->numberOfLayouts(), 1u); + // default layout is English + QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)")); + // with one layout we should not have the dbus interface + QVERIFY(!QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.keyboard")).value()); + + // reconfigure to two layouts + layoutGroup.writeEntry("LayoutList", QStringLiteral("us,de")); + layoutGroup.sync(); + reconfigureLayouts(); + QCOMPARE(xkb->numberOfLayouts(), 2u); + QVERIFY(QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.keyboard")).value()); + + // and back to one layout + layoutGroup.writeEntry("LayoutList", QStringLiteral("us")); + layoutGroup.sync(); + reconfigureLayouts(); + QCOMPARE(xkb->numberOfLayouts(), 1u); + QVERIFY(!QDBusConnection::sessionBus().interface()->isServiceRegistered(QStringLiteral("org.kde.keyboard")).value()); +} + +void KeyboardLayoutTest::testVirtualDesktopPolicy() +{ + layoutGroup.writeEntry("LayoutList", QStringLiteral("us,de,de(neo)")); + layoutGroup.writeEntry("SwitchMode", QStringLiteral("Desktop")); + layoutGroup.sync(); + reconfigureLayouts(); + auto xkb = input()->keyboard()->xkb(); + QCOMPARE(xkb->numberOfLayouts(), 3u); + QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)")); + + VirtualDesktopManager::self()->setCount(4); + QCOMPARE(VirtualDesktopManager::self()->count(), 4u); + auto desktops = VirtualDesktopManager::self()->desktops(); + QCOMPARE(desktops.count(), 4); + + // give desktops different layouts + uint desktop, layout; + for (desktop = 0; desktop < VirtualDesktopManager::self()->count(); ++desktop) { + // switch to another virtual desktop + VirtualDesktopManager::self()->setCurrent(desktops.at(desktop)); + QCOMPARE(desktops.at(desktop), VirtualDesktopManager::self()->currentDesktop()); + // should be reset to English + QCOMPARE(xkb->currentLayout(), 0); + // change first desktop to German + layout = (desktop + 1) % xkb->numberOfLayouts(); + changeLayout(layout).waitForFinished(); + QCOMPARE(xkb->currentLayout(), layout); + } + + // imitate app restart to test layouts saving feature + resetLayouts(); + + // check layout set on desktop switching as intended + for (--desktop;;) { + QCOMPARE(desktops.at(desktop), VirtualDesktopManager::self()->currentDesktop()); + layout = (desktop + 1) % xkb->numberOfLayouts(); + QCOMPARE(xkb->currentLayout(), layout); + if (--desktop >= VirtualDesktopManager::self()->count()) { // overflow + break; + } + VirtualDesktopManager::self()->setCurrent(desktops.at(desktop)); + } + + // remove virtual desktops + desktop = 0; + const KWin::VirtualDesktop *deletedDesktop = desktops.last(); + VirtualDesktopManager::self()->setCount(1); + QCOMPARE(xkb->currentLayout(), layout = (desktop + 1) % xkb->numberOfLayouts()); + QCOMPARE(xkb->layoutName(), QStringLiteral("German")); + + // add another desktop + VirtualDesktopManager::self()->setCount(2); + // switching to it should result in going to default + desktops = VirtualDesktopManager::self()->desktops(); + QCOMPARE(desktops.count(), 2); + QCOMPARE(desktops.first(), VirtualDesktopManager::self()->currentDesktop()); + VirtualDesktopManager::self()->setCurrent(desktops.last()); + QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)")); + + // check there are no more layouts left in config than the last actual non-default layouts number + QSignalSpy deletedDesktopSpy(deletedDesktop, &VirtualDesktop::aboutToBeDestroyed); + QVERIFY(deletedDesktopSpy.wait()); + resetLayouts(); + QCOMPARE(layoutGroup.keyList().filter(QStringLiteral("LayoutDefault")).count(), 1); +} + +void KeyboardLayoutTest::testWindowPolicy() +{ + enum Layout { + us, + de, + de_neo, + bad, + }; + layoutGroup.writeEntry("LayoutList", QStringLiteral("us,de,de(neo)")); + layoutGroup.writeEntry("SwitchMode", QStringLiteral("Window")); + layoutGroup.sync(); + reconfigureLayouts(); + auto xkb = input()->keyboard()->xkb(); + QCOMPARE(xkb->numberOfLayouts(), 3u); + QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)")); + + // create a window + using namespace KWayland::Client; + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto c1 = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::blue); + QVERIFY(c1); + + // now switch layout + auto reply = changeLayout(Layout::de); + reply.waitForFinished(); + QCOMPARE(xkb->layoutName(), QStringLiteral("German")); + + // create a second window + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + auto c2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 100), Qt::red); + QVERIFY(c2); + // this should have switched back to English + QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)")); + // now change to another layout + reply = changeLayout(Layout::de_neo); + reply.waitForFinished(); + QCOMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)")); + + // activate other window + workspace()->activateWindow(c1); + QCOMPARE(xkb->layoutName(), QStringLiteral("German")); + workspace()->activateWindow(c2); + QCOMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)")); +} + +void KeyboardLayoutTest::testApplicationPolicy() +{ + enum Layout { + us, + de, + de_neo, + bad, + }; + layoutGroup.writeEntry("LayoutList", QStringLiteral("us,de,de(neo)")); + layoutGroup.writeEntry("SwitchMode", QStringLiteral("WinClass")); + layoutGroup.sync(); + reconfigureLayouts(); + auto xkb = input()->keyboard()->xkb(); + QCOMPARE(xkb->numberOfLayouts(), 3u); + QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)")); + + // create a window + using namespace KWayland::Client; + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + shellSurface->set_app_id(QStringLiteral("org.kde.foo")); + auto c1 = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::blue); + QVERIFY(c1); + + // create a second window + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + shellSurface2->set_app_id(QStringLiteral("org.kde.foo")); + auto c2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 100), Qt::red); + QVERIFY(c2); + // now switch layout + layoutChangedSpy.clear(); + changeLayout(Layout::de_neo); + QVERIFY(layoutChangedSpy.wait()); + QCOMPARE(layoutChangedSpy.count(), 1); + layoutChangedSpy.clear(); + QCOMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)")); + + resetLayouts(); + // to trigger layout application for current client + workspace()->activateWindow(c1); + workspace()->activateWindow(c2); + QVERIFY(layoutChangedSpy.wait()); + QCOMPARE(layoutChangedSpy.count(), 1); + QCOMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)")); + + // activate other window + workspace()->activateWindow(c1); + // it is the same application and should not switch the layout + QVERIFY(!layoutChangedSpy.wait(1000)); + QCOMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)")); + workspace()->activateWindow(c2); + QVERIFY(!layoutChangedSpy.wait(1000)); + QCOMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)")); + + shellSurface2.reset(); + surface2.reset(); + QVERIFY(Test::waitForWindowDestroyed(c2)); + QVERIFY(!layoutChangedSpy.wait(1000)); + QCOMPARE(xkb->layoutName(), QStringLiteral("German (Neo 2)")); + + resetLayouts(); + QCOMPARE(layoutGroup.keyList().filter(QStringLiteral("LayoutDefault")).count(), 1); +} + +void KeyboardLayoutTest::testNumLock() +{ + qputenv("KWIN_FORCE_NUM_LOCK_EVALUATION", "1"); + layoutGroup.writeEntry("LayoutList", QStringLiteral("us")); + layoutGroup.sync(); + reconfigureLayouts(); + + auto xkb = input()->keyboard()->xkb(); + QCOMPARE(xkb->numberOfLayouts(), 1u); + QCOMPARE(xkb->layoutName(), QStringLiteral("English (US)")); + + // by default not set + QVERIFY(!xkb->leds().testFlag(LED::NumLock)); + quint32 timestamp = 0; + Test::keyboardKeyPressed(KEY_NUMLOCK, timestamp++); + Test::keyboardKeyReleased(KEY_NUMLOCK, timestamp++); + // now it should be on + QVERIFY(xkb->leds().testFlag(LED::NumLock)); + // and back to off + Test::keyboardKeyPressed(KEY_NUMLOCK, timestamp++); + Test::keyboardKeyReleased(KEY_NUMLOCK, timestamp++); + QVERIFY(!xkb->leds().testFlag(LED::NumLock)); + + // let's reconfigure to enable through config + auto group = InputConfig::self()->inputConfig()->group("Keyboard"); + group.writeEntry("NumLock", 0); + group.sync(); + xkb->reconfigure(); + // now it should be on + QVERIFY(xkb->leds().testFlag(LED::NumLock)); + // pressing should result in it being off + Test::keyboardKeyPressed(KEY_NUMLOCK, timestamp++); + Test::keyboardKeyReleased(KEY_NUMLOCK, timestamp++); + QVERIFY(!xkb->leds().testFlag(LED::NumLock)); + + // pressing again should enable it + Test::keyboardKeyPressed(KEY_NUMLOCK, timestamp++); + Test::keyboardKeyReleased(KEY_NUMLOCK, timestamp++); + QVERIFY(xkb->leds().testFlag(LED::NumLock)); + + // now reconfigure to disable on load + group.writeEntry("NumLock", 1); + group.sync(); + xkb->reconfigure(); + QVERIFY(!xkb->leds().testFlag(LED::NumLock)); +} + +WAYLANDTEST_MAIN(KeyboardLayoutTest) +#include "keyboard_layout_test.moc" diff --git a/autotests/integration/keymap_creation_failure_test.cpp b/autotests/integration/keymap_creation_failure_test.cpp new file mode 100644 index 0000000..cee35e5 --- /dev/null +++ b/autotests/integration/keymap_creation_failure_test.cpp @@ -0,0 +1,89 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2017 Martin Flöser + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/platform.h" +#include "keyboard_input.h" +#include "keyboard_layout.h" +#include "virtualdesktops.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include +#include + +#include + +using namespace KWin; +using namespace KWayland::Client; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_keymap_creation_failure-0"); + +class KeymapCreationFailureTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testPointerButton(); +}; + +void KeymapCreationFailureTest::initTestCase() +{ + // situation for for BUG 381210 + // this will fail to create keymap + qputenv("XKB_DEFAULT_RULES", "no"); + qputenv("XKB_DEFAULT_MODEL", "no"); + qputenv("XKB_DEFAULT_LAYOUT", "no"); + qputenv("XKB_DEFAULT_VARIANT", "no"); + qputenv("XKB_DEFAULT_OPTIONS", "no"); + + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); + kwinApp()->setKxkbConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); + KConfigGroup layoutGroup = kwinApp()->kxkbConfig()->group("Layout"); + layoutGroup.writeEntry("LayoutList", QStringLiteral("no")); + layoutGroup.writeEntry("Model", "no"); + layoutGroup.writeEntry("Options", "no"); + layoutGroup.sync(); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); +} + +void KeymapCreationFailureTest::init() +{ + QVERIFY(Test::setupWaylandConnection()); +} + +void KeymapCreationFailureTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void KeymapCreationFailureTest::testPointerButton() +{ + // test case for BUG 381210 + // pressing a pointer button results in crash + + // now create the crashing condition + // which is sending in a pointer event + Test::pointerButtonPressed(BTN_LEFT, 0); + Test::pointerButtonReleased(BTN_LEFT, 1); +} + +WAYLANDTEST_MAIN(KeymapCreationFailureTest) +#include "keymap_creation_failure_test.moc" diff --git a/autotests/integration/kwin_wayland_test.cpp b/autotests/integration/kwin_wayland_test.cpp new file mode 100644 index 0000000..0f0e011 --- /dev/null +++ b/autotests/integration/kwin_wayland_test.cpp @@ -0,0 +1,199 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2015 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "backends/virtual/virtual_backend.h" +#include "composite.h" +#include "core/platform.h" +#include "core/session.h" +#include "effects.h" +#include "inputmethod.h" +#include "placement.h" +#include "pluginmanager.h" +#include "utils/xcbutils.h" +#include "wayland_server.h" +#include "workspace.h" +#include "xwayland/xwayland.h" + +#include + +#include +#include +#include +#include +#include + +// system +#include +#include +#include + +Q_IMPORT_PLUGIN(KWinIntegrationPlugin) +Q_IMPORT_PLUGIN(KGlobalAccelImpl) +Q_IMPORT_PLUGIN(KWindowSystemKWinPlugin) +Q_IMPORT_PLUGIN(KWinIdleTimePoller) + +namespace KWin +{ + +WaylandTestApplication::WaylandTestApplication(OperationMode mode, int &argc, char **argv) + : Application(mode, argc, argv) +{ + QStandardPaths::setTestModeEnabled(true); + // TODO: add a test move to kglobalaccel instead? + QFile{QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("kglobalshortcutsrc"))}.remove(); + QIcon::setThemeName(QStringLiteral("breeze")); +#if KWIN_BUILD_ACTIVITIES + setUseKActivities(false); +#endif + qputenv("KWIN_COMPOSE", QByteArrayLiteral("Q")); + qputenv("XDG_CURRENT_DESKTOP", QByteArrayLiteral("KDE")); + qunsetenv("XKB_DEFAULT_RULES"); + qunsetenv("XKB_DEFAULT_MODEL"); + qunsetenv("XKB_DEFAULT_LAYOUT"); + qunsetenv("XKB_DEFAULT_VARIANT"); + qunsetenv("XKB_DEFAULT_OPTIONS"); + + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup windowsGroup = config->group("Windows"); + windowsGroup.writeEntry("Placement", Placement::policyToString(PlacementSmart)); + windowsGroup.sync(); + setConfig(config); + + const auto ownPath = libraryPaths().last(); + removeLibraryPath(ownPath); + addLibraryPath(ownPath); + + setSession(Session::create(Session::Type::Noop)); + setPlatform(std::make_unique()); + WaylandServer::create(this); + setProcessStartupEnvironment(QProcessEnvironment::systemEnvironment()); +} + +WaylandTestApplication::~WaylandTestApplication() +{ + setTerminating(); + // need to unload all effects prior to destroying X connection as they might do X calls + // also before destroy Workspace, as effects might call into Workspace + if (effects) { + static_cast(effects)->unloadAllEffects(); + } + m_xwayland.reset(); + destroyVirtualInputDevices(); + destroyColorManager(); + destroyWorkspace(); + destroyInputMethod(); + destroyCompositor(); + destroyInput(); +} + +void WaylandTestApplication::createVirtualInputDevices() +{ + m_virtualKeyboard.reset(new Test::VirtualInputDevice()); + m_virtualKeyboard->setName(QStringLiteral("Virtual Keyboard 1")); + m_virtualKeyboard->setKeyboard(true); + + m_virtualPointer.reset(new Test::VirtualInputDevice()); + m_virtualPointer->setName(QStringLiteral("Virtual Pointer 1")); + m_virtualPointer->setPointer(true); + + m_virtualTouch.reset(new Test::VirtualInputDevice()); + m_virtualTouch->setName(QStringLiteral("Virtual Touch 1")); + m_virtualTouch->setTouch(true); + + input()->addInputDevice(m_virtualPointer.get()); + input()->addInputDevice(m_virtualTouch.get()); + input()->addInputDevice(m_virtualKeyboard.get()); +} + +void WaylandTestApplication::destroyVirtualInputDevices() +{ + input()->removeInputDevice(m_virtualPointer.get()); + input()->removeInputDevice(m_virtualTouch.get()); + input()->removeInputDevice(m_virtualKeyboard.get()); +} + +void WaylandTestApplication::performStartup() +{ + if (!m_inputMethodServerToStart.isEmpty()) { + createInputMethod(); + if (m_inputMethodServerToStart != QStringLiteral("internal")) { + inputMethod()->setInputMethodCommand(m_inputMethodServerToStart); + inputMethod()->setEnabled(true); + } + } + + // first load options - done internally by a different thread + createOptions(); + if (!platform()->initialize()) { + std::exit(1); + } + + // try creating the Wayland Backend + createInput(); + createVirtualInputDevices(); + + WaylandCompositor::create(); + connect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::continueStartupWithScene); +} + +void WaylandTestApplication::finalizeStartup() +{ + if (m_xwayland) { + disconnect(m_xwayland.get(), &Xwl::Xwayland::errorOccurred, this, &WaylandTestApplication::finalizeStartup); + disconnect(m_xwayland.get(), &Xwl::Xwayland::started, this, &WaylandTestApplication::finalizeStartup); + } + notifyStarted(); +} + +void WaylandTestApplication::continueStartupWithScene() +{ + disconnect(Compositor::self(), &Compositor::sceneCreated, this, &WaylandTestApplication::continueStartupWithScene); + + createWorkspace(); + createColorManager(); + createPlugins(); + + waylandServer()->initWorkspace(); + + if (!waylandServer()->start()) { + qFatal("Failed to initialize the Wayland server, exiting now"); + } + + if (operationMode() == OperationModeWaylandOnly) { + finalizeStartup(); + return; + } + + m_xwayland = std::make_unique(this); + connect(m_xwayland.get(), &Xwl::Xwayland::errorOccurred, this, &WaylandTestApplication::finalizeStartup); + connect(m_xwayland.get(), &Xwl::Xwayland::started, this, &WaylandTestApplication::finalizeStartup); + m_xwayland->start(); +} + +Test::VirtualInputDevice *WaylandTestApplication::virtualPointer() const +{ + return m_virtualPointer.get(); +} + +Test::VirtualInputDevice *WaylandTestApplication::virtualKeyboard() const +{ + return m_virtualKeyboard.get(); +} + +Test::VirtualInputDevice *WaylandTestApplication::virtualTouch() const +{ + return m_virtualTouch.get(); +} + +XwaylandInterface *WaylandTestApplication::xwayland() const +{ + return m_xwayland.get(); +} +} diff --git a/autotests/integration/kwin_wayland_test.h b/autotests/integration/kwin_wayland_test.h new file mode 100644 index 0000000..8486748 --- /dev/null +++ b/autotests/integration/kwin_wayland_test.h @@ -0,0 +1,686 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2015 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#ifndef KWIN_WAYLAND_TEST_H +#define KWIN_WAYLAND_TEST_H + +#include "core/inputdevice.h" +#include "main.h" +#include "window.h" + +// Qt +#include + +#include + +#include "qwayland-idle-inhibit-unstable-v1.h" +#include "qwayland-input-method-unstable-v1.h" +#include "qwayland-kde-output-device-v2.h" +#include "qwayland-kde-output-management-v2.h" +#include "qwayland-text-input-unstable-v3.h" +#include "qwayland-wlr-layer-shell-unstable-v1.h" +#include "qwayland-xdg-decoration-unstable-v1.h" +#include "qwayland-xdg-shell.h" + +namespace KWayland +{ +namespace Client +{ +class AppMenuManager; +class ConnectionThread; +class Compositor; +class Output; +class PlasmaShell; +class PlasmaWindowManagement; +class PointerConstraints; +class Seat; +class ServerSideDecorationManager; +class ShadowManager; +class ShmPool; +class SubCompositor; +class SubSurface; +class Surface; +class TextInputManager; +} +} + +namespace QtWayland +{ +class zwp_input_panel_surface_v1; +class zwp_text_input_v3; +class zwp_text_input_manager_v3; +} + +namespace KWin +{ +namespace Xwl +{ +class Xwayland; +} + +namespace Test +{ +class VirtualInputDevice; +} + +class WaylandTestApplication : public Application +{ + Q_OBJECT +public: + WaylandTestApplication(OperationMode mode, int &argc, char **argv); + ~WaylandTestApplication() override; + + void setInputMethodServerToStart(const QString &inputMethodServer) + { + m_inputMethodServerToStart = inputMethodServer; + } + + Test::VirtualInputDevice *virtualPointer() const; + Test::VirtualInputDevice *virtualKeyboard() const; + Test::VirtualInputDevice *virtualTouch() const; + XwaylandInterface *xwayland() const override; + +protected: + void performStartup() override; + +private: + void continueStartupWithScene(); + void finalizeStartup(); + + void createVirtualInputDevices(); + void destroyVirtualInputDevices(); + + std::unique_ptr m_xwayland; + QString m_inputMethodServerToStart; + + std::unique_ptr m_virtualPointer; + std::unique_ptr m_virtualKeyboard; + std::unique_ptr m_virtualTouch; +}; + +namespace Test +{ + +class MockInputMethod; + +class TextInputManagerV3 : public QtWayland::zwp_text_input_manager_v3 +{ +public: + ~TextInputManagerV3() override + { + destroy(); + } +}; + +class TextInputV3 : public QObject, public QtWayland::zwp_text_input_v3 +{ + Q_OBJECT +public: + ~TextInputV3() override + { + destroy(); + } + +Q_SIGNALS: + void preeditString(const QString &text, int cursor_begin, int cursor_end); + +protected: + void zwp_text_input_v3_preedit_string(const QString &text, int32_t cursor_begin, int32_t cursor_end) override + { + Q_EMIT preeditString(text, cursor_begin, cursor_end); + } +}; + +class LayerShellV1 : public QtWayland::zwlr_layer_shell_v1 +{ +public: + ~LayerShellV1() override; +}; + +class LayerSurfaceV1 : public QObject, public QtWayland::zwlr_layer_surface_v1 +{ + Q_OBJECT + +public: + ~LayerSurfaceV1() override; + +protected: + void zwlr_layer_surface_v1_configure(uint32_t serial, uint32_t width, uint32_t height) override; + void zwlr_layer_surface_v1_closed() override; + +Q_SIGNALS: + void closeRequested(); + void configureRequested(quint32 serial, const QSize &size); +}; + +/** + * The XdgShell class represents the @c xdg_wm_base global. + */ +class XdgShell : public QtWayland::xdg_wm_base +{ +public: + ~XdgShell() override; + void xdg_wm_base_ping(uint32_t serial) override + { + pong(serial); + } +}; + +/** + * The XdgSurface class represents an xdg_surface object. + */ +class XdgSurface : public QObject, public QtWayland::xdg_surface +{ + Q_OBJECT + +public: + explicit XdgSurface(XdgShell *shell, KWayland::Client::Surface *surface, QObject *parent = nullptr); + ~XdgSurface() override; + + KWayland::Client::Surface *surface() const; + +Q_SIGNALS: + void configureRequested(quint32 serial); + +protected: + void xdg_surface_configure(uint32_t serial) override; + +private: + KWayland::Client::Surface *m_surface; +}; + +/** + * The XdgToplevel class represents an xdg_toplevel surface. Note that the XdgToplevel surface + * takes the ownership of the underlying XdgSurface object. + */ +class XdgToplevel : public QObject, public QtWayland::xdg_toplevel +{ + Q_OBJECT + +public: + enum class State { + Maximized = 1 << 0, + Fullscreen = 1 << 1, + Resizing = 1 << 2, + Activated = 1 << 3 + }; + Q_DECLARE_FLAGS(States, State) + + explicit XdgToplevel(XdgSurface *surface, QObject *parent = nullptr); + ~XdgToplevel() override; + + XdgSurface *xdgSurface() const; + +Q_SIGNALS: + void configureRequested(const QSize &size, KWin::Test::XdgToplevel::States states); + void closeRequested(); + +protected: + void xdg_toplevel_configure(int32_t width, int32_t height, wl_array *states) override; + void xdg_toplevel_close() override; + +private: + std::unique_ptr m_xdgSurface; +}; + +/** + * The XdgPositioner class represents an xdg_positioner object. + */ +class XdgPositioner : public QtWayland::xdg_positioner +{ +public: + explicit XdgPositioner(XdgShell *shell); + ~XdgPositioner() override; +}; + +/** + * The XdgPopup class represents an xdg_popup surface. Note that the XdgPopup surface takes + * the ownership of the underlying XdgSurface object. + */ +class XdgPopup : public QObject, public QtWayland::xdg_popup +{ + Q_OBJECT + +public: + XdgPopup(XdgSurface *surface, XdgSurface *parentSurface, XdgPositioner *positioner, QObject *parent = nullptr); + ~XdgPopup() override; + + XdgSurface *xdgSurface() const; + +Q_SIGNALS: + void configureRequested(const QRect &rect); + void doneReceived(); + +protected: + void xdg_popup_configure(int32_t x, int32_t y, int32_t width, int32_t height) override; + void xdg_popup_popup_done() override; + +private: + std::unique_ptr m_xdgSurface; +}; + +class XdgDecorationManagerV1 : public QtWayland::zxdg_decoration_manager_v1 +{ +public: + ~XdgDecorationManagerV1() override; +}; + +class XdgToplevelDecorationV1 : public QObject, public QtWayland::zxdg_toplevel_decoration_v1 +{ + Q_OBJECT + +public: + XdgToplevelDecorationV1(XdgDecorationManagerV1 *manager, XdgToplevel *toplevel, QObject *parent = nullptr); + ~XdgToplevelDecorationV1() override; + +Q_SIGNALS: + void configureRequested(QtWayland::zxdg_toplevel_decoration_v1::mode mode); + +protected: + void zxdg_toplevel_decoration_v1_configure(uint32_t mode) override; +}; + +class IdleInhibitManagerV1 : public QtWayland::zwp_idle_inhibit_manager_v1 +{ +public: + ~IdleInhibitManagerV1() override; +}; + +class IdleInhibitorV1 : public QtWayland::zwp_idle_inhibitor_v1 +{ +public: + IdleInhibitorV1(IdleInhibitManagerV1 *manager, KWayland::Client::Surface *surface); + ~IdleInhibitorV1() override; +}; + +class WaylandOutputConfigurationV2 : public QObject, public QtWayland::kde_output_configuration_v2 +{ + Q_OBJECT +public: + WaylandOutputConfigurationV2(struct ::kde_output_configuration_v2 *object); + +Q_SIGNALS: + void applied(); + void failed(); + +protected: + void kde_output_configuration_v2_applied() override; + void kde_output_configuration_v2_failed() override; +}; + +class WaylandOutputManagementV2 : public QObject, public QtWayland::kde_output_management_v2 +{ + Q_OBJECT +public: + WaylandOutputManagementV2(struct ::wl_registry *registry, int id, int version); + + WaylandOutputConfigurationV2 *createConfiguration(); +}; + +class WaylandOutputDeviceV2Mode : public QObject, public QtWayland::kde_output_device_mode_v2 +{ + Q_OBJECT + +public: + WaylandOutputDeviceV2Mode(struct ::kde_output_device_mode_v2 *object); + ~WaylandOutputDeviceV2Mode() override; + + int refreshRate() const; + QSize size() const; + bool preferred() const; + + bool operator==(const WaylandOutputDeviceV2Mode &other); + + static WaylandOutputDeviceV2Mode *get(struct ::kde_output_device_mode_v2 *object); + +Q_SIGNALS: + void removed(); + +protected: + void kde_output_device_mode_v2_size(int32_t width, int32_t height) override; + void kde_output_device_mode_v2_refresh(int32_t refresh) override; + void kde_output_device_mode_v2_preferred() override; + void kde_output_device_mode_v2_removed() override; + +private: + int m_refreshRate = 60000; + QSize m_size; + bool m_preferred = false; +}; + +class WaylandOutputDeviceV2 : public QObject, public QtWayland::kde_output_device_v2 +{ + Q_OBJECT + +public: + WaylandOutputDeviceV2(int id); + ~WaylandOutputDeviceV2() override; + + QByteArray edid() const; + bool enabled() const; + int id() const; + QString name() const; + QString model() const; + QString manufacturer() const; + qreal scale() const; + QPoint globalPosition() const; + QSize pixelSize() const; + int refreshRate() const; + uint32_t vrrPolicy() const; + uint32_t overscan() const; + uint32_t capabilities() const; + uint32_t rgbRange() const; + + QString modeId() const; + +Q_SIGNALS: + void enabledChanged(); + void done(); + +protected: + void kde_output_device_v2_geometry(int32_t x, + int32_t y, + int32_t physical_width, + int32_t physical_height, + int32_t subpixel, + const QString &make, + const QString &model, + int32_t transform) override; + void kde_output_device_v2_current_mode(struct ::kde_output_device_mode_v2 *mode) override; + void kde_output_device_v2_mode(struct ::kde_output_device_mode_v2 *mode) override; + void kde_output_device_v2_done() override; + void kde_output_device_v2_scale(wl_fixed_t factor) override; + void kde_output_device_v2_edid(const QString &raw) override; + void kde_output_device_v2_enabled(int32_t enabled) override; + void kde_output_device_v2_uuid(const QString &uuid) override; + void kde_output_device_v2_serial_number(const QString &serialNumber) override; + void kde_output_device_v2_eisa_id(const QString &eisaId) override; + void kde_output_device_v2_capabilities(uint32_t flags) override; + void kde_output_device_v2_overscan(uint32_t overscan) override; + void kde_output_device_v2_vrr_policy(uint32_t vrr_policy) override; + void kde_output_device_v2_rgb_range(uint32_t rgb_range) override; + +private: + QString modeName(const WaylandOutputDeviceV2Mode *m) const; + WaylandOutputDeviceV2Mode *deviceModeFromId(const int modeId) const; + + WaylandOutputDeviceV2Mode *m_mode; + QList m_modes; + + int m_id; + QPoint m_pos; + QSize m_physicalSize; + int32_t m_subpixel; + QString m_manufacturer; + QString m_model; + int32_t m_transform; + qreal m_factor; + QByteArray m_edid; + int32_t m_enabled; + QString m_uuid; + QString m_serialNumber; + QString m_eisaId; + uint32_t m_flags; + uint32_t m_overscan; + uint32_t m_vrr_policy; + uint32_t m_rgbRange; +}; + +class MockInputMethod : public QObject, QtWayland::zwp_input_method_v1 +{ + Q_OBJECT +public: + MockInputMethod(struct wl_registry *registry, int id, int version); + ~MockInputMethod(); + + KWayland::Client::Surface *inputPanelSurface() const + { + return m_inputSurface.get(); + } + auto *context() const + { + return m_context; + } + +Q_SIGNALS: + void activate(); + +protected: + void zwp_input_method_v1_activate(struct ::zwp_input_method_context_v1 *context) override; + void zwp_input_method_v1_deactivate(struct ::zwp_input_method_context_v1 *context) override; + +private: + std::unique_ptr m_inputSurface; + QtWayland::zwp_input_panel_surface_v1 *m_inputMethodSurface = nullptr; + struct ::zwp_input_method_context_v1 *m_context = nullptr; +}; + +enum class AdditionalWaylandInterface { + Seat = 1 << 0, + Decoration = 1 << 1, + PlasmaShell = 1 << 2, + WindowManagement = 1 << 3, + PointerConstraints = 1 << 4, + IdleInhibitV1 = 1 << 5, + AppMenu = 1 << 6, + ShadowManager = 1 << 7, + XdgDecorationV1 = 1 << 8, + OutputManagementV2 = 1 << 9, + TextInputManagerV2 = 1 << 10, + InputMethodV1 = 1 << 11, + LayerShellV1 = 1 << 12, + TextInputManagerV3 = 1 << 13, + OutputDeviceV2 = 1 << 14, +}; +Q_DECLARE_FLAGS(AdditionalWaylandInterfaces, AdditionalWaylandInterface) + +class VirtualInputDevice : public InputDevice +{ + Q_OBJECT + +public: + explicit VirtualInputDevice(QObject *parent = nullptr); + + void setPointer(bool set); + void setKeyboard(bool set); + void setTouch(bool set); + void setName(const QString &name); + + QString sysName() const override; + QString name() const override; + + bool isEnabled() const override; + void setEnabled(bool enabled) override; + + LEDs leds() const override; + void setLeds(LEDs leds) override; + + bool isKeyboard() const override; + bool isAlphaNumericKeyboard() const override; + bool isPointer() const override; + bool isTouchpad() const override; + bool isTouch() const override; + bool isTabletTool() const override; + bool isTabletPad() const override; + bool isTabletModeSwitch() const override; + bool isLidSwitch() const override; + +private: + QString m_name; + bool m_pointer = false; + bool m_keyboard = false; + bool m_touch = false; +}; + +void keyboardKeyPressed(quint32 key, quint32 time); +void keyboardKeyReleased(quint32 key, quint32 time); +void pointerAxisHorizontal(qreal delta, + quint32 time, + qint32 discreteDelta = 0, + InputRedirection::PointerAxisSource source = InputRedirection::PointerAxisSourceUnknown); +void pointerAxisVertical(qreal delta, + quint32 time, + qint32 discreteDelta = 0, + InputRedirection::PointerAxisSource source = InputRedirection::PointerAxisSourceUnknown); +void pointerButtonPressed(quint32 button, quint32 time); +void pointerButtonReleased(quint32 button, quint32 time); +void pointerMotion(const QPointF &position, quint32 time); +void touchCancel(); +void touchDown(qint32 id, const QPointF &pos, quint32 time); +void touchMotion(qint32 id, const QPointF &pos, quint32 time); +void touchUp(qint32 id, quint32 time); + +/** + * Creates a Wayland Connection in a dedicated thread and creates various + * client side objects which can be used to create windows. + * @returns @c true if created successfully, @c false if there was an error + * @see destroyWaylandConnection + */ +bool setupWaylandConnection(AdditionalWaylandInterfaces flags = AdditionalWaylandInterfaces()); + +/** + * Destroys the Wayland Connection created with @link{setupWaylandConnection}. + * This can be called from cleanup in order to ensure that no Wayland Connection + * leaks into the next test method. + * @see setupWaylandConnection + */ +void destroyWaylandConnection(); + +KWayland::Client::ConnectionThread *waylandConnection(); +KWayland::Client::Compositor *waylandCompositor(); +KWayland::Client::SubCompositor *waylandSubCompositor(); +KWayland::Client::ShadowManager *waylandShadowManager(); +KWayland::Client::ShmPool *waylandShmPool(); +KWayland::Client::Seat *waylandSeat(); +KWayland::Client::ServerSideDecorationManager *waylandServerSideDecoration(); +KWayland::Client::PlasmaShell *waylandPlasmaShell(); +KWayland::Client::PlasmaWindowManagement *waylandWindowManagement(); +KWayland::Client::PointerConstraints *waylandPointerConstraints(); +KWayland::Client::AppMenuManager *waylandAppMenuManager(); +WaylandOutputManagementV2 *waylandOutputManagementV2(); +KWayland::Client::TextInputManager *waylandTextInputManager(); +QVector waylandOutputs(); +KWayland::Client::Output *waylandOutput(const QString &name); +QVector waylandOutputDevicesV2(); + +bool waitForWaylandSurface(Window *window); + +bool waitForWaylandPointer(); +bool waitForWaylandTouch(); +bool waitForWaylandKeyboard(); + +void flushWaylandConnection(); + +std::unique_ptr createSurface(); +KWayland::Client::SubSurface *createSubSurface(KWayland::Client::Surface *surface, + KWayland::Client::Surface *parentSurface, QObject *parent = nullptr); + +LayerSurfaceV1 *createLayerSurfaceV1(KWayland::Client::Surface *surface, + const QString &scope, + KWayland::Client::Output *output = nullptr, + LayerShellV1::layer layer = LayerShellV1::layer_top); + +TextInputManagerV3 *waylandTextInputManagerV3(); + +enum class CreationSetup { + CreateOnly, + CreateAndConfigure, /// commit and wait for the configure event, making this surface ready to commit buffers +}; + +QtWayland::zwp_input_panel_surface_v1 *createInputPanelSurfaceV1(KWayland::Client::Surface *surface, + KWayland::Client::Output *output); + +XdgToplevel *createXdgToplevelSurface(KWayland::Client::Surface *surface, QObject *parent = nullptr); +XdgToplevel *createXdgToplevelSurface(KWayland::Client::Surface *surface, + CreationSetup configureMode, + QObject *parent = nullptr); + +XdgPositioner *createXdgPositioner(); + +XdgPopup *createXdgPopupSurface(KWayland::Client::Surface *surface, XdgSurface *parentSurface, + XdgPositioner *positioner, + CreationSetup configureMode = CreationSetup::CreateAndConfigure, + QObject *parent = nullptr); + +XdgToplevelDecorationV1 *createXdgToplevelDecorationV1(XdgToplevel *toplevel, QObject *parent = nullptr); +IdleInhibitorV1 *createIdleInhibitorV1(KWayland::Client::Surface *surface); + +/** + * Creates a shared memory buffer of @p size in @p color and attaches it to the @p surface. + * The @p surface gets damaged and committed, thus it's rendered. + */ +void render(KWayland::Client::Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format = QImage::Format_ARGB32_Premultiplied); + +/** + * Creates a shared memory buffer using the supplied image @p img and attaches it to the @p surface + */ +void render(KWayland::Client::Surface *surface, const QImage &img); + +/** + * Waits till a new Window is shown and returns the created Window. + * If no Window gets shown during @p timeout @c null is returned. + */ +Window *waitForWaylandWindowShown(int timeout = 5000); + +/** + * Combination of @link{render} and @link{waitForWaylandWindowShown}. + */ +Window *renderAndWaitForShown(KWayland::Client::Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format = QImage::Format_ARGB32, int timeout = 5000); + +/** + * Waits for the @p window to be destroyed. + */ +bool waitForWindowDestroyed(Window *window); + +/** + * Locks the screen and waits till the screen is locked. + * @returns @c true if the screen could be locked, @c false otherwise + */ +bool lockScreen(); + +/** + * Unlocks the screen and waits till the screen is unlocked. + * @returns @c true if the screen could be unlocked, @c false otherwise + */ +bool unlockScreen(); + +MockInputMethod *inputMethod(); +KWayland::Client::Surface *inputPanelSurface(); + +} + +} + +Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::Test::AdditionalWaylandInterfaces) +Q_DECLARE_METATYPE(KWin::Test::XdgToplevel::States) +Q_DECLARE_METATYPE(QtWayland::zxdg_toplevel_decoration_v1::mode) + +#define WAYLANDTEST_MAIN_HELPER(TestObject, DPI, OperationMode) \ + int main(int argc, char *argv[]) \ + { \ + setenv("QT_QPA_PLATFORM", "wayland-org.kde.kwin.qpa", true); \ + setenv("QT_QPA_PLATFORM_PLUGIN_PATH", QFileInfo(QString::fromLocal8Bit(argv[0])).absolutePath().toLocal8Bit().constData(), true); \ + setenv("KWIN_FORCE_OWN_QPA", "1", true); \ + qunsetenv("KDE_FULL_SESSION"); \ + qunsetenv("KDE_SESSION_VERSION"); \ + qunsetenv("XDG_SESSION_DESKTOP"); \ + qunsetenv("XDG_CURRENT_DESKTOP"); \ + DPI; \ + KWin::WaylandTestApplication app(OperationMode, argc, argv); \ + app.setAttribute(Qt::AA_Use96Dpi, true); \ + TestObject tc; \ + return QTest::qExec(&tc, argc, argv); \ + } + +#ifdef NO_XWAYLAND +#define WAYLANDTEST_MAIN(TestObject) WAYLANDTEST_MAIN_HELPER(TestObject, QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps), KWin::Application::OperationModeWaylandOnly) +#else +#define WAYLANDTEST_MAIN(TestObject) WAYLANDTEST_MAIN_HELPER(TestObject, QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps), KWin::Application::OperationModeXwayland) +#endif + +#endif diff --git a/autotests/integration/kwinbindings_test.cpp b/autotests/integration/kwinbindings_test.cpp new file mode 100644 index 0000000..58c9a0f --- /dev/null +++ b/autotests/integration/kwinbindings_test.cpp @@ -0,0 +1,249 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2017 Martin Flöser + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/platform.h" +#include "cursor.h" +#include "input.h" +#include "scripting/scripting.h" +#include "useractions.h" +#include "virtualdesktops.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include + +#include +#include +#include + +using namespace KWin; +using namespace KWayland::Client; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_kwinbindings-0"); + +class KWinBindingsTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testSwitchWindow(); + void testSwitchWindowScript(); + void testWindowToDesktop_data(); + void testWindowToDesktop(); +}; + +void KWinBindingsTest::initTestCase() +{ + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); +} + +void KWinBindingsTest::init() +{ + QVERIFY(Test::setupWaylandConnection()); + workspace()->setActiveOutput(QPoint(640, 512)); + KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void KWinBindingsTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void KWinBindingsTest::testSwitchWindow() +{ + // first create windows + std::unique_ptr surface1(Test::createSurface()); + std::unique_ptr shellSurface1(Test::createXdgToplevelSurface(surface1.get())); + auto c1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::blue); + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + auto c2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::blue); + std::unique_ptr surface3(Test::createSurface()); + std::unique_ptr shellSurface3(Test::createXdgToplevelSurface(surface3.get())); + auto c3 = Test::renderAndWaitForShown(surface3.get(), QSize(100, 50), Qt::blue); + std::unique_ptr surface4(Test::createSurface()); + std::unique_ptr shellSurface4(Test::createXdgToplevelSurface(surface4.get())); + auto c4 = Test::renderAndWaitForShown(surface4.get(), QSize(100, 50), Qt::blue); + + QVERIFY(c4->isActive()); + QVERIFY(c4 != c3); + QVERIFY(c3 != c2); + QVERIFY(c2 != c1); + + // let's position all windows + c1->move(QPoint(0, 0)); + c2->move(QPoint(200, 0)); + c3->move(QPoint(200, 200)); + c4->move(QPoint(0, 200)); + + // now let's trigger the shortcuts + + // invoke global shortcut through dbus + auto invokeShortcut = [](const QString &shortcut) { + auto msg = QDBusMessage::createMethodCall( + QStringLiteral("org.kde.kglobalaccel"), + QStringLiteral("/component/kwin"), + QStringLiteral("org.kde.kglobalaccel.Component"), + QStringLiteral("invokeShortcut")); + msg.setArguments(QList{shortcut}); + QDBusConnection::sessionBus().asyncCall(msg); + }; + invokeShortcut(QStringLiteral("Switch Window Up")); + QTRY_COMPARE(workspace()->activeWindow(), c1); + invokeShortcut(QStringLiteral("Switch Window Right")); + QTRY_COMPARE(workspace()->activeWindow(), c2); + invokeShortcut(QStringLiteral("Switch Window Down")); + QTRY_COMPARE(workspace()->activeWindow(), c3); + invokeShortcut(QStringLiteral("Switch Window Left")); + QTRY_COMPARE(workspace()->activeWindow(), c4); + // test opposite direction + invokeShortcut(QStringLiteral("Switch Window Left")); + QTRY_COMPARE(workspace()->activeWindow(), c3); + invokeShortcut(QStringLiteral("Switch Window Down")); + QTRY_COMPARE(workspace()->activeWindow(), c2); + invokeShortcut(QStringLiteral("Switch Window Right")); + QTRY_COMPARE(workspace()->activeWindow(), c1); + invokeShortcut(QStringLiteral("Switch Window Up")); + QTRY_COMPARE(workspace()->activeWindow(), c4); +} + +void KWinBindingsTest::testSwitchWindowScript() +{ + QVERIFY(Scripting::self()); + + // first create windows + std::unique_ptr surface1(Test::createSurface()); + std::unique_ptr shellSurface1(Test::createXdgToplevelSurface(surface1.get())); + auto c1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::blue); + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + auto c2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::blue); + std::unique_ptr surface3(Test::createSurface()); + std::unique_ptr shellSurface3(Test::createXdgToplevelSurface(surface3.get())); + auto c3 = Test::renderAndWaitForShown(surface3.get(), QSize(100, 50), Qt::blue); + std::unique_ptr surface4(Test::createSurface()); + std::unique_ptr shellSurface4(Test::createXdgToplevelSurface(surface4.get())); + auto c4 = Test::renderAndWaitForShown(surface4.get(), QSize(100, 50), Qt::blue); + + QVERIFY(c4->isActive()); + QVERIFY(c4 != c3); + QVERIFY(c3 != c2); + QVERIFY(c2 != c1); + + // let's position all windows + c1->move(QPoint(0, 0)); + c2->move(QPoint(200, 0)); + c3->move(QPoint(200, 200)); + c4->move(QPoint(0, 200)); + + auto runScript = [](const QString &slot) { + QTemporaryFile tmpFile; + QVERIFY(tmpFile.open()); + QTextStream out(&tmpFile); + out << "workspace." << slot << "()"; + out.flush(); + + const int id = Scripting::self()->loadScript(tmpFile.fileName()); + QVERIFY(id != -1); + QVERIFY(Scripting::self()->isScriptLoaded(tmpFile.fileName())); + auto s = Scripting::self()->findScript(tmpFile.fileName()); + QVERIFY(s); + QSignalSpy runningChangedSpy(s, &AbstractScript::runningChanged); + s->run(); + QTRY_COMPARE(runningChangedSpy.count(), 1); + }; + + runScript(QStringLiteral("slotSwitchWindowUp")); + QTRY_COMPARE(workspace()->activeWindow(), c1); + runScript(QStringLiteral("slotSwitchWindowRight")); + QTRY_COMPARE(workspace()->activeWindow(), c2); + runScript(QStringLiteral("slotSwitchWindowDown")); + QTRY_COMPARE(workspace()->activeWindow(), c3); + runScript(QStringLiteral("slotSwitchWindowLeft")); + QTRY_COMPARE(workspace()->activeWindow(), c4); +} + +void KWinBindingsTest::testWindowToDesktop_data() +{ + QTest::addColumn("desktop"); + + QTest::newRow("2") << 2; + QTest::newRow("3") << 3; + QTest::newRow("4") << 4; + QTest::newRow("5") << 5; + QTest::newRow("6") << 6; + QTest::newRow("7") << 7; + QTest::newRow("8") << 8; + QTest::newRow("9") << 9; + QTest::newRow("10") << 10; + QTest::newRow("11") << 11; + QTest::newRow("12") << 12; + QTest::newRow("13") << 13; + QTest::newRow("14") << 14; + QTest::newRow("15") << 15; + QTest::newRow("16") << 16; + QTest::newRow("17") << 17; + QTest::newRow("18") << 18; + QTest::newRow("19") << 19; + QTest::newRow("20") << 20; +} + +void KWinBindingsTest::testWindowToDesktop() +{ + // first go to desktop one + VirtualDesktopManager::self()->setCurrent(VirtualDesktopManager::self()->desktops().first()); + + // now create a window + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QSignalSpy desktopChangedSpy(window, &Window::desktopChanged); + QCOMPARE(workspace()->activeWindow(), window); + + QFETCH(int, desktop); + VirtualDesktopManager::self()->setCount(desktop); + + // now trigger the shortcut + auto invokeShortcut = [](int desktop) { + auto msg = QDBusMessage::createMethodCall( + QStringLiteral("org.kde.kglobalaccel"), + QStringLiteral("/component/kwin"), + QStringLiteral("org.kde.kglobalaccel.Component"), + QStringLiteral("invokeShortcut")); + msg.setArguments(QList{QStringLiteral("Window to Desktop %1").arg(desktop)}); + QDBusConnection::sessionBus().asyncCall(msg); + }; + invokeShortcut(desktop); + QVERIFY(desktopChangedSpy.wait()); + QCOMPARE(window->desktop(), desktop); + // back to desktop 1 + invokeShortcut(1); + QVERIFY(desktopChangedSpy.wait()); + QCOMPARE(window->desktop(), 1); + // invoke with one desktop too many + invokeShortcut(desktop + 1); + // that should fail + QVERIFY(!desktopChangedSpy.wait(100)); +} + +WAYLANDTEST_MAIN(KWinBindingsTest) +#include "kwinbindings_test.moc" diff --git a/autotests/integration/layershellv1window_test.cpp b/autotests/integration/layershellv1window_test.cpp new file mode 100644 index 0000000..eff5b01 --- /dev/null +++ b/autotests/integration/layershellv1window_test.cpp @@ -0,0 +1,560 @@ +/* + SPDX-FileCopyrightText: 2020 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "main.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include +#include + +Q_DECLARE_METATYPE(QMargins) +Q_DECLARE_METATYPE(KWin::Layer) + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_layershellv1window-0"); + +class LayerShellV1WindowTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testOutput_data(); + void testOutput(); + void testAnchor_data(); + void testAnchor(); + void testMargins_data(); + void testMargins(); + void testLayer_data(); + void testLayer(); + void testPlacementArea_data(); + void testPlacementArea(); + void testFill_data(); + void testFill(); + void testStack(); + void testFocus(); + void testActivate_data(); + void testActivate(); + void testUnmap(); +}; + +void LayerShellV1WindowTest::initTestCase() +{ + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); +} + +void LayerShellV1WindowTest::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::LayerShellV1)); + + workspace()->setActiveOutput(QPoint(640, 512)); + Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void LayerShellV1WindowTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void LayerShellV1WindowTest::testOutput_data() +{ + QTest::addColumn("screenId"); + + QTest::addRow("first output") << 0; + QTest::addRow("second output") << 1; +} + +void LayerShellV1WindowTest::testOutput() +{ + // Fetch the wl_output object. + QFETCH(int, screenId); + KWayland::Client::Output *output = Test::waylandOutputs().value(screenId); + QVERIFY(output); + + // Create a layer shell surface. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createLayerSurfaceV1(surface.get(), QStringLiteral("test"), output)); + + // Set the initial state of the layer surface. + shellSurface->set_size(280, 124); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // Wait for the compositor to position the surface. + QSignalSpy configureRequestedSpy(shellSurface.get(), &Test::LayerSurfaceV1::configureRequested); + QVERIFY(configureRequestedSpy.wait()); + const QSize requestedSize = configureRequestedSpy.last().at(1).toSize(); + + // Map the layer surface. + shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt()); + Window *window = Test::renderAndWaitForShown(surface.get(), requestedSize, Qt::red); + QVERIFY(window); + + // Verify that the window is on the requested screen. + QVERIFY(output->geometry().contains(window->frameGeometry().toRect())); + + // Destroy the window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void LayerShellV1WindowTest::testAnchor_data() +{ + QTest::addColumn("anchor"); + QTest::addColumn("expectedGeometry"); + + QTest::addRow("left") << int(Test::LayerSurfaceV1::anchor_left) + << QRectF(0, 450, 280, 124); + + QTest::addRow("top left") << (Test::LayerSurfaceV1::anchor_top | Test::LayerSurfaceV1::anchor_left) + << QRectF(0, 0, 280, 124); + + QTest::addRow("top") << int(Test::LayerSurfaceV1::anchor_top) + << QRectF(500, 0, 280, 124); + + QTest::addRow("top right") << (Test::LayerSurfaceV1::anchor_top | Test::LayerSurfaceV1::anchor_right) + << QRectF(1000, 0, 280, 124); + + QTest::addRow("right") << int(Test::LayerSurfaceV1::anchor_right) + << QRectF(1000, 450, 280, 124); + + QTest::addRow("bottom right") << (Test::LayerSurfaceV1::anchor_bottom | Test::LayerSurfaceV1::anchor_right) + << QRectF(1000, 900, 280, 124); + + QTest::addRow("bottom") << int(Test::LayerSurfaceV1::anchor_bottom) + << QRectF(500, 900, 280, 124); + + QTest::addRow("bottom left") << (Test::LayerSurfaceV1::anchor_bottom | Test::LayerSurfaceV1::anchor_left) + << QRectF(0, 900, 280, 124); +} + +void LayerShellV1WindowTest::testAnchor() +{ + // Create a layer shell surface. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createLayerSurfaceV1(surface.get(), QStringLiteral("test"))); + + // Set the initial state of the layer surface. + QFETCH(int, anchor); + shellSurface->set_anchor(anchor); + shellSurface->set_size(280, 124); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // Wait for the compositor to position the surface. + QSignalSpy configureRequestedSpy(shellSurface.get(), &Test::LayerSurfaceV1::configureRequested); + QVERIFY(configureRequestedSpy.wait()); + const QSize requestedSize = configureRequestedSpy.last().at(1).toSize(); + QCOMPARE(requestedSize, QSize(280, 124)); + + // Map the layer surface. + shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt()); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(280, 124), Qt::red); + QVERIFY(window); + + // Verify that the window is placed at expected location. + QTEST(window->frameGeometry(), "expectedGeometry"); + + // Destroy the window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void LayerShellV1WindowTest::testMargins_data() +{ + QTest::addColumn("anchor"); + QTest::addColumn("margins"); + QTest::addColumn("expectedGeometry"); + + QTest::addRow("left") << int(Test::LayerSurfaceV1::anchor_left) + << QMargins(100, 0, 0, 0) + << QRectF(100, 450, 280, 124); + + QTest::addRow("top left") << (Test::LayerSurfaceV1::anchor_top | Test::LayerSurfaceV1::anchor_left) + << QMargins(100, 200, 0, 0) + << QRectF(100, 200, 280, 124); + + QTest::addRow("top") << int(Test::LayerSurfaceV1::anchor_top) + << QMargins(0, 200, 0, 0) + << QRectF(500, 200, 280, 124); + + QTest::addRow("top right") << (Test::LayerSurfaceV1::anchor_top | Test::LayerSurfaceV1::anchor_right) + << QMargins(0, 200, 300, 0) + << QRectF(700, 200, 280, 124); + + QTest::addRow("right") << int(Test::LayerSurfaceV1::anchor_right) + << QMargins(0, 0, 300, 0) + << QRectF(700, 450, 280, 124); + + QTest::addRow("bottom right") << (Test::LayerSurfaceV1::anchor_bottom | Test::LayerSurfaceV1::anchor_right) + << QMargins(0, 0, 300, 400) + << QRectF(700, 500, 280, 124); + + QTest::addRow("bottom") << int(Test::LayerSurfaceV1::anchor_bottom) + << QMargins(0, 0, 0, 400) + << QRectF(500, 500, 280, 124); + + QTest::addRow("bottom left") << (Test::LayerSurfaceV1::anchor_bottom | Test::LayerSurfaceV1::anchor_left) + << QMargins(100, 0, 0, 400) + << QRectF(100, 500, 280, 124); +} + +void LayerShellV1WindowTest::testMargins() +{ + // Create a layer shell surface. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createLayerSurfaceV1(surface.get(), QStringLiteral("test"))); + + // Set the initial state of the layer surface. + QFETCH(QMargins, margins); + QFETCH(int, anchor); + shellSurface->set_anchor(anchor); + shellSurface->set_margin(margins.top(), margins.right(), margins.bottom(), margins.left()); + shellSurface->set_size(280, 124); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // Wait for the compositor to position the surface. + QSignalSpy configureRequestedSpy(shellSurface.get(), &Test::LayerSurfaceV1::configureRequested); + QVERIFY(configureRequestedSpy.wait()); + const QSize requestedSize = configureRequestedSpy.last().at(1).toSize(); + + // Map the layer surface. + shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt()); + Window *window = Test::renderAndWaitForShown(surface.get(), requestedSize, Qt::red); + QVERIFY(window); + + // Verify that the window is placed at expected location. + QTEST(window->frameGeometry(), "expectedGeometry"); + + // Destroy the window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void LayerShellV1WindowTest::testLayer_data() +{ + QTest::addColumn("protocolLayer"); + QTest::addColumn("compositorLayer"); + + QTest::addRow("overlay") << int(Test::LayerShellV1::layer_overlay) << UnmanagedLayer; + QTest::addRow("top") << int(Test::LayerShellV1::layer_top) << AboveLayer; + QTest::addRow("bottom") << int(Test::LayerShellV1::layer_bottom) << BelowLayer; + QTest::addRow("background") << int(Test::LayerShellV1::layer_background) << DesktopLayer; +} + +void LayerShellV1WindowTest::testLayer() +{ + // Create a layer shell surface. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createLayerSurfaceV1(surface.get(), QStringLiteral("test"))); + + // Set the initial state of the layer surface. + QFETCH(int, protocolLayer); + shellSurface->set_layer(protocolLayer); + shellSurface->set_size(280, 124); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // Wait for the compositor to position the surface. + QSignalSpy configureRequestedSpy(shellSurface.get(), &Test::LayerSurfaceV1::configureRequested); + QVERIFY(configureRequestedSpy.wait()); + const QSize requestedSize = configureRequestedSpy.last().at(1).toSize(); + + // Map the layer surface. + shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt()); + Window *window = Test::renderAndWaitForShown(surface.get(), requestedSize, Qt::red); + QVERIFY(window); + + // Verify that the window is placed at expected location. + QTEST(window->layer(), "compositorLayer"); + + // Destroy the window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void LayerShellV1WindowTest::testPlacementArea_data() +{ + QTest::addColumn("anchor"); + QTest::addColumn("exclusiveZone"); + QTest::addColumn("placementArea"); + + QTest::addRow("left") << int(Test::LayerSurfaceV1::anchor_left) << 300 << QRectF(300, 0, 980, 1024); + QTest::addRow("top") << int(Test::LayerSurfaceV1::anchor_top) << 300 << QRectF(0, 300, 1280, 724); + QTest::addRow("right") << int(Test::LayerSurfaceV1::anchor_right) << 300 << QRectF(0, 0, 980, 1024); + QTest::addRow("bottom") << int(Test::LayerSurfaceV1::anchor_bottom) << 300 << QRectF(0, 0, 1280, 724); +} + +void LayerShellV1WindowTest::testPlacementArea() +{ + // Create a layer shell surface. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createLayerSurfaceV1(surface.get(), QStringLiteral("test"))); + + // Set the initial state of the layer surface. + QFETCH(int, anchor); + QFETCH(int, exclusiveZone); + shellSurface->set_anchor(anchor); + shellSurface->set_exclusive_zone(exclusiveZone); + shellSurface->set_size(280, 124); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // Wait for the compositor to position the surface. + QSignalSpy configureRequestedSpy(shellSurface.get(), &Test::LayerSurfaceV1::configureRequested); + QVERIFY(configureRequestedSpy.wait()); + const QSize requestedSize = configureRequestedSpy.last().at(1).toSize(); + + // Map the layer surface. + shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt()); + Window *window = Test::renderAndWaitForShown(surface.get(), requestedSize, Qt::red); + QVERIFY(window); + + // Verify that the work area has been adjusted. + QTEST(workspace()->clientArea(PlacementArea, window), "placementArea"); + + // Destroy the window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void LayerShellV1WindowTest::testFill_data() +{ + QTest::addColumn("anchor"); + QTest::addColumn("desiredSize"); + QTest::addColumn("expectedGeometry"); + + QTest::addRow("horizontal") << (Test::LayerSurfaceV1::anchor_left | Test::LayerSurfaceV1::anchor_right) + << QSize(0, 124) + << QRectF(0, 450, 1280, 124); + + QTest::addRow("vertical") << (Test::LayerSurfaceV1::anchor_top | Test::LayerSurfaceV1::anchor_bottom) + << QSize(280, 0) + << QRectF(500, 0, 280, 1024); + + QTest::addRow("all") << (Test::LayerSurfaceV1::anchor_left | Test::LayerSurfaceV1::anchor_top | Test::LayerSurfaceV1::anchor_right | Test::LayerSurfaceV1::anchor_bottom) + << QSize(0, 0) + << QRectF(0, 0, 1280, 1024); +} + +void LayerShellV1WindowTest::testFill() +{ + // Create a layer shell surface. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createLayerSurfaceV1(surface.get(), QStringLiteral("test"))); + + // Set the initial state of the layer surface. + QFETCH(int, anchor); + QFETCH(QSize, desiredSize); + shellSurface->set_anchor(anchor); + shellSurface->set_size(desiredSize.width(), desiredSize.height()); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // Wait for the compositor to position the surface. + QSignalSpy configureRequestedSpy(shellSurface.get(), &Test::LayerSurfaceV1::configureRequested); + QVERIFY(configureRequestedSpy.wait()); + const QSize requestedSize = configureRequestedSpy.last().at(1).toSize(); + + // Map the layer surface. + shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt()); + Window *window = Test::renderAndWaitForShown(surface.get(), requestedSize, Qt::red); + QVERIFY(window); + + // Verify that the window is placed at expected location. + QTEST(window->frameGeometry(), "expectedGeometry"); + + // Destroy the window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void LayerShellV1WindowTest::testStack() +{ + // Create a layer shell surface. + std::unique_ptr surface1(Test::createSurface()); + std::unique_ptr shellSurface1(Test::createLayerSurfaceV1(surface1.get(), QStringLiteral("test"))); + + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createLayerSurfaceV1(surface2.get(), QStringLiteral("test"))); + + // Set the initial state of the layer surface. + shellSurface1->set_anchor(Test::LayerSurfaceV1::anchor_left); + shellSurface1->set_size(80, 124); + shellSurface1->set_exclusive_zone(80); + surface1->commit(KWayland::Client::Surface::CommitFlag::None); + + shellSurface2->set_anchor(Test::LayerSurfaceV1::anchor_left); + shellSurface2->set_size(200, 124); + shellSurface2->set_exclusive_zone(200); + surface2->commit(KWayland::Client::Surface::CommitFlag::None); + + // Wait for the compositor to position the surfaces. + QSignalSpy configureRequestedSpy1(shellSurface1.get(), &Test::LayerSurfaceV1::configureRequested); + QSignalSpy configureRequestedSpy2(shellSurface2.get(), &Test::LayerSurfaceV1::configureRequested); + QVERIFY(configureRequestedSpy2.wait()); + const QSize requestedSize1 = configureRequestedSpy1.last().at(1).toSize(); + const QSize requestedSize2 = configureRequestedSpy2.last().at(1).toSize(); + + // Map the layer surface. + shellSurface1->ack_configure(configureRequestedSpy1.last().at(0).toUInt()); + Window *window1 = Test::renderAndWaitForShown(surface1.get(), requestedSize1, Qt::red); + QVERIFY(window1); + + shellSurface2->ack_configure(configureRequestedSpy2.last().at(0).toUInt()); + Window *window2 = Test::renderAndWaitForShown(surface2.get(), requestedSize2, Qt::red); + QVERIFY(window2); + + // Check that the second layer surface is placed next to the first. + QCOMPARE(window1->frameGeometry(), QRect(0, 450, 80, 124)); + QCOMPARE(window2->frameGeometry(), QRect(80, 450, 200, 124)); + + // Check that the work area has been adjusted accordingly. + QCOMPARE(workspace()->clientArea(PlacementArea, window1), QRect(280, 0, 1000, 1024)); + QCOMPARE(workspace()->clientArea(PlacementArea, window2), QRect(280, 0, 1000, 1024)); + + // Destroy the window. + shellSurface1.reset(); + QVERIFY(Test::waitForWindowDestroyed(window1)); + shellSurface2.reset(); + QVERIFY(Test::waitForWindowDestroyed(window2)); +} + +void LayerShellV1WindowTest::testFocus() +{ + // Create a layer shell surface. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createLayerSurfaceV1(surface.get(), QStringLiteral("test"))); + + // Set the initial state of the layer surface. + shellSurface->set_keyboard_interactivity(1); + shellSurface->set_size(280, 124); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // Wait for the compositor to position the surface. + QSignalSpy configureRequestedSpy(shellSurface.get(), &Test::LayerSurfaceV1::configureRequested); + QVERIFY(configureRequestedSpy.wait()); + const QSize requestedSize = configureRequestedSpy.last().at(1).toSize(); + + // Map the layer surface. + shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt()); + Window *window = Test::renderAndWaitForShown(surface.get(), requestedSize, Qt::red); + QVERIFY(window); + + // The layer surface must be focused when it's mapped. + QVERIFY(window->isActive()); + + // Destroy the window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void LayerShellV1WindowTest::testActivate_data() +{ + QTest::addColumn("layer"); + QTest::addColumn("active"); + + QTest::addRow("overlay") << int(Test::LayerShellV1::layer_overlay) << true; + QTest::addRow("top") << int(Test::LayerShellV1::layer_top) << true; + QTest::addRow("bottom") << int(Test::LayerShellV1::layer_bottom) << false; + QTest::addRow("background") << int(Test::LayerShellV1::layer_background) << false; +} + +void LayerShellV1WindowTest::testActivate() +{ + // Create a layer shell surface. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createLayerSurfaceV1(surface.get(), QStringLiteral("test"))); + + // Set the initial state of the layer surface. + QFETCH(int, layer); + shellSurface->set_layer(layer); + shellSurface->set_size(280, 124); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // Wait for the compositor to position the surface. + QSignalSpy configureRequestedSpy(shellSurface.get(), &Test::LayerSurfaceV1::configureRequested); + QVERIFY(configureRequestedSpy.wait()); + const QSize requestedSize = configureRequestedSpy.last().at(1).toSize(); + + // Map the layer surface. + shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt()); + Window *window = Test::renderAndWaitForShown(surface.get(), requestedSize, Qt::red); + QVERIFY(window); + QVERIFY(!window->isActive()); + + // Try to activate the layer surface. + shellSurface->set_keyboard_interactivity(1); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + QSignalSpy activeChangedSpy(window, &Window::activeChanged); + QTEST(activeChangedSpy.wait(1000), "active"); + + // Destroy the window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void LayerShellV1WindowTest::testUnmap() +{ + // Create a layer shell surface. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createLayerSurfaceV1(surface.get(), QStringLiteral("test"))); + + // Set the initial state of the layer surface. + shellSurface->set_size(280, 124); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // Wait for the compositor to position the surface. + QSignalSpy configureRequestedSpy(shellSurface.get(), &Test::LayerSurfaceV1::configureRequested); + QVERIFY(configureRequestedSpy.wait()); + + // Map the layer surface. + shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt()); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(280, 124), Qt::red); + QVERIFY(window); + + // Unmap the layer surface. + surface->attachBuffer(KWayland::Client::Buffer::Ptr()); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(Test::waitForWindowDestroyed(window)); + + // Notify the compositor that we want to map the layer surface. + shellSurface->set_size(280, 124); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // Wait for the configure event. + QVERIFY(configureRequestedSpy.wait()); + + // Map the layer surface back. + shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt()); + window = Test::renderAndWaitForShown(surface.get(), QSize(280, 124), Qt::red); + QVERIFY(window); + + // Destroy the window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +} // namespace KWin + +WAYLANDTEST_MAIN(KWin::LayerShellV1WindowTest) +#include "layershellv1window_test.moc" diff --git a/autotests/integration/lockscreen.cpp b/autotests/integration/lockscreen.cpp new file mode 100644 index 0000000..d03ebcc --- /dev/null +++ b/autotests/integration/lockscreen.cpp @@ -0,0 +1,756 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "composite.h" +#include "core/output.h" +#include "core/platform.h" +#include "core/renderbackend.h" +#include "cursor.h" +#include "screenedge.h" +#include "wayland/keyboard_interface.h" +#include "wayland/seat_interface.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// screenlocker +#include + +#include + +#include + +Q_DECLARE_METATYPE(Qt::Orientation) + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_lock_screen-0"); + +class LockScreenTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testStackingOrder(); + void testPointer(); + void testPointerButton(); + void testPointerAxis(); + void testKeyboard(); + void testScreenEdge(); + void testEffects(); + void testEffectsKeyboard(); + void testEffectsKeyboardAutorepeat(); + void testMoveWindow(); + void testPointerShortcut(); + void testAxisShortcut_data(); + void testAxisShortcut(); + void testKeyboardShortcut(); + void testTouch(); + +private: + void unlock(); + std::pair> showWindow(); + KWayland::Client::ConnectionThread *m_connection = nullptr; + KWayland::Client::Compositor *m_compositor = nullptr; + KWayland::Client::Seat *m_seat = nullptr; + KWayland::Client::ShmPool *m_shm = nullptr; +}; + +class HelperEffect : public Effect +{ + Q_OBJECT +public: + HelperEffect() + { + } + ~HelperEffect() override + { + } + + void windowInputMouseEvent(QEvent *) override + { + Q_EMIT inputEvent(); + } + void grabbedKeyboardEvent(QKeyEvent *e) override + { + Q_EMIT keyEvent(e->text()); + } + +Q_SIGNALS: + void inputEvent(); + void keyEvent(const QString &); +}; + +#define LOCK \ + do { \ + QVERIFY(!waylandServer()->isScreenLocked()); \ + QSignalSpy lockStateChangedSpy(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged); \ + ScreenLocker::KSldApp::self()->lock(ScreenLocker::EstablishLock::Immediate); \ + QTRY_COMPARE(ScreenLocker::KSldApp::self()->lockState(), ScreenLocker::KSldApp::Locked); \ + QVERIFY(waylandServer()->isScreenLocked()); \ + } while (false) + +#define UNLOCK \ + do { \ + QSignalSpy lockStateChangedSpy(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged); \ + unlock(); \ + if (lockStateChangedSpy.count() != 1) { \ + QVERIFY(lockStateChangedSpy.wait()); \ + } \ + QCOMPARE(lockStateChangedSpy.count(), 1); \ + QVERIFY(!waylandServer()->isScreenLocked()); \ + } while (false) + +#define MOTION(target) Test::pointerMotion(target, timestamp++) + +#define PRESS Test::pointerButtonPressed(BTN_LEFT, timestamp++) + +#define RELEASE Test::pointerButtonReleased(BTN_LEFT, timestamp++) + +#define KEYPRESS(key) Test::keyboardKeyPressed(key, timestamp++) + +#define KEYRELEASE(key) Test::keyboardKeyReleased(key, timestamp++) + +void LockScreenTest::unlock() +{ + using namespace ScreenLocker; + const auto children = KSldApp::self()->children(); + for (auto it = children.begin(); it != children.end(); ++it) { + if (qstrcmp((*it)->metaObject()->className(), "LogindIntegration") != 0) { + continue; + } + QMetaObject::invokeMethod(*it, "requestUnlock"); + break; + } +} + +std::pair> LockScreenTest::showWindow() +{ + using namespace KWayland::Client; +#define VERIFY(statement) \ + if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__)) \ + return {nullptr, nullptr}; +#define COMPARE(actual, expected) \ + if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)) \ + return {nullptr, nullptr}; + + std::unique_ptr surface = Test::createSurface(); + VERIFY(surface.get()); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + VERIFY(shellSurface); + // let's render + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + + VERIFY(window); + COMPARE(workspace()->activeWindow(), window); + +#undef VERIFY +#undef COMPARE + + return {window, std::move(surface)}; +} + +void LockScreenTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType("ElectricBorder"); + + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2")); + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); + setenv("QT_QPA_PLATFORM", "wayland", true); + + QCOMPARE(Compositor::self()->backend()->compositingType(), KWin::OpenGLCompositing); +} + +void LockScreenTest::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat)); + QVERIFY(Test::waitForWaylandPointer()); + m_connection = Test::waylandConnection(); + m_compositor = Test::waylandCompositor(); + m_shm = Test::waylandShmPool(); + m_seat = Test::waylandSeat(); + + workspace()->setActiveOutput(QPoint(640, 512)); + Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void LockScreenTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void LockScreenTest::testStackingOrder() +{ + // This test verifies that the lockscreen greeter is placed above other windows. + + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + + LOCK; + QVERIFY(windowAddedSpy.wait()); + + Window *window = windowAddedSpy.first().first().value(); + QVERIFY(window); + QVERIFY(window->isLockScreen()); + QCOMPARE(window->layer(), UnmanagedLayer); + + UNLOCK; +} + +void LockScreenTest::testPointer() +{ + using namespace KWayland::Client; + + std::unique_ptr pointer(m_seat->createPointer()); + QVERIFY(pointer != nullptr); + QSignalSpy enteredSpy(pointer.get(), &Pointer::entered); + QSignalSpy leftSpy(pointer.get(), &Pointer::left); + + auto [window, surface] = showWindow(); + QVERIFY(window); + + // first move cursor into the center of the window + quint32 timestamp = 1; + MOTION(window->frameGeometry().center()); + QVERIFY(enteredSpy.wait()); + + LOCK; + + QVERIFY(leftSpy.wait()); + QCOMPARE(leftSpy.count(), 1); + + // simulate moving out in and out again + MOTION(window->frameGeometry().center()); + MOTION(window->frameGeometry().bottomRight() + QPoint(100, 100)); + MOTION(window->frameGeometry().bottomRight() + QPoint(100, 100)); + QVERIFY(!leftSpy.wait()); + QCOMPARE(leftSpy.count(), 1); + QCOMPARE(enteredSpy.count(), 1); + + // go back on the window + MOTION(window->frameGeometry().center()); + // and unlock + UNLOCK; + + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.count(), 2); + // move on the window + MOTION(window->frameGeometry().center() + QPoint(100, 100)); + QVERIFY(leftSpy.wait()); + MOTION(window->frameGeometry().center()); + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.count(), 3); +} + +void LockScreenTest::testPointerButton() +{ + using namespace KWayland::Client; + + std::unique_ptr pointer(m_seat->createPointer()); + QVERIFY(pointer != nullptr); + QSignalSpy enteredSpy(pointer.get(), &Pointer::entered); + QSignalSpy buttonChangedSpy(pointer.get(), &Pointer::buttonStateChanged); + + auto [window, surface] = showWindow(); + QVERIFY(window); + + // first move cursor into the center of the window + quint32 timestamp = 1; + MOTION(window->frameGeometry().center()); + QVERIFY(enteredSpy.wait()); + // and simulate a click + PRESS; + QVERIFY(buttonChangedSpy.wait()); + RELEASE; + QVERIFY(buttonChangedSpy.wait()); + + LOCK; + + // and simulate a click + PRESS; + QVERIFY(!buttonChangedSpy.wait()); + RELEASE; + QVERIFY(!buttonChangedSpy.wait()); + + UNLOCK; + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.count(), 2); + + // and click again + PRESS; + QVERIFY(buttonChangedSpy.wait()); + RELEASE; + QVERIFY(buttonChangedSpy.wait()); +} + +void LockScreenTest::testPointerAxis() +{ + using namespace KWayland::Client; + + std::unique_ptr pointer(m_seat->createPointer()); + QVERIFY(pointer != nullptr); + QSignalSpy axisChangedSpy(pointer.get(), &Pointer::axisChanged); + QSignalSpy enteredSpy(pointer.get(), &Pointer::entered); + + auto [window, surface] = showWindow(); + QVERIFY(window); + + // first move cursor into the center of the window + quint32 timestamp = 1; + MOTION(window->frameGeometry().center()); + QVERIFY(enteredSpy.wait()); + // and simulate axis + Test::pointerAxisHorizontal(5.0, timestamp++); + QVERIFY(axisChangedSpy.wait()); + + LOCK; + + // and simulate axis + Test::pointerAxisHorizontal(5.0, timestamp++); + QVERIFY(!axisChangedSpy.wait(100)); + Test::pointerAxisVertical(5.0, timestamp++); + QVERIFY(!axisChangedSpy.wait(100)); + + // and unlock + UNLOCK; + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.count(), 2); + + // and move axis again + Test::pointerAxisHorizontal(5.0, timestamp++); + QVERIFY(axisChangedSpy.wait()); + Test::pointerAxisVertical(5.0, timestamp++); + QVERIFY(axisChangedSpy.wait()); +} + +void LockScreenTest::testKeyboard() +{ + using namespace KWayland::Client; + + std::unique_ptr keyboard(m_seat->createKeyboard()); + QVERIFY(keyboard != nullptr); + QSignalSpy enteredSpy(keyboard.get(), &Keyboard::entered); + QSignalSpy leftSpy(keyboard.get(), &Keyboard::left); + QSignalSpy keyChangedSpy(keyboard.get(), &Keyboard::keyChanged); + + auto [window, surface] = showWindow(); + QVERIFY(window); + QVERIFY(enteredSpy.wait()); + QTRY_COMPARE(enteredSpy.count(), 1); + + quint32 timestamp = 1; + KEYPRESS(KEY_A); + QVERIFY(keyChangedSpy.wait()); + QCOMPARE(keyChangedSpy.count(), 1); + QCOMPARE(keyChangedSpy.at(0).at(0).value(), quint32(KEY_A)); + QCOMPARE(keyChangedSpy.at(0).at(1).value(), Keyboard::KeyState::Pressed); + QCOMPARE(keyChangedSpy.at(0).at(2).value(), quint32(1)); + KEYRELEASE(KEY_A); + QVERIFY(keyChangedSpy.wait()); + QCOMPARE(keyChangedSpy.count(), 2); + QCOMPARE(keyChangedSpy.at(1).at(0).value(), quint32(KEY_A)); + QCOMPARE(keyChangedSpy.at(1).at(1).value(), Keyboard::KeyState::Released); + QCOMPARE(keyChangedSpy.at(1).at(2).value(), quint32(2)); + + LOCK; + QVERIFY(leftSpy.wait()); + KEYPRESS(KEY_B); + KEYRELEASE(KEY_B); + QCOMPARE(leftSpy.count(), 1); + QCOMPARE(keyChangedSpy.count(), 2); + + UNLOCK; + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.count(), 2); + KEYPRESS(KEY_C); + QVERIFY(keyChangedSpy.wait()); + QCOMPARE(keyChangedSpy.count(), 3); + KEYRELEASE(KEY_C); + QVERIFY(keyChangedSpy.wait()); + QCOMPARE(keyChangedSpy.count(), 4); + QCOMPARE(enteredSpy.count(), 2); + QCOMPARE(keyChangedSpy.at(2).at(0).value(), quint32(KEY_C)); + QCOMPARE(keyChangedSpy.at(3).at(0).value(), quint32(KEY_C)); + QCOMPARE(keyChangedSpy.at(2).at(2).value(), quint32(5)); + QCOMPARE(keyChangedSpy.at(3).at(2).value(), quint32(6)); + QCOMPARE(keyChangedSpy.at(2).at(1).value(), Keyboard::KeyState::Pressed); + QCOMPARE(keyChangedSpy.at(3).at(1).value(), Keyboard::KeyState::Released); +} + +void LockScreenTest::testScreenEdge() +{ + QSignalSpy screenEdgeSpy(workspace()->screenEdges(), &ScreenEdges::approaching); + QCOMPARE(screenEdgeSpy.count(), 0); + + quint32 timestamp = 1; + MOTION(QPoint(5, 5)); + QCOMPARE(screenEdgeSpy.count(), 1); + + LOCK; + + MOTION(QPoint(4, 4)); + QCOMPARE(screenEdgeSpy.count(), 1); + + // and unlock + UNLOCK; + + MOTION(QPoint(5, 5)); + QCOMPARE(screenEdgeSpy.count(), 2); +} + +void LockScreenTest::testEffects() +{ + std::unique_ptr effect(new HelperEffect); + QSignalSpy inputSpy(effect.get(), &HelperEffect::inputEvent); + effects->startMouseInterception(effect.get(), Qt::ArrowCursor); + + quint32 timestamp = 1; + QCOMPARE(inputSpy.count(), 0); + MOTION(QPoint(5, 5)); + QCOMPARE(inputSpy.count(), 1); + // simlate click + PRESS; + QCOMPARE(inputSpy.count(), 2); + RELEASE; + QCOMPARE(inputSpy.count(), 3); + + LOCK; + + MOTION(QPoint(6, 6)); + QCOMPARE(inputSpy.count(), 3); + // simlate click + PRESS; + QCOMPARE(inputSpy.count(), 3); + RELEASE; + QCOMPARE(inputSpy.count(), 3); + + UNLOCK; + + MOTION(QPoint(5, 5)); + QCOMPARE(inputSpy.count(), 4); + // simlate click + PRESS; + QCOMPARE(inputSpy.count(), 5); + RELEASE; + QCOMPARE(inputSpy.count(), 6); + + effects->stopMouseInterception(effect.get()); +} + +void LockScreenTest::testEffectsKeyboard() +{ + std::unique_ptr effect(new HelperEffect); + QSignalSpy inputSpy(effect.get(), &HelperEffect::keyEvent); + effects->grabKeyboard(effect.get()); + + quint32 timestamp = 1; + KEYPRESS(KEY_A); + QCOMPARE(inputSpy.count(), 1); + QCOMPARE(inputSpy.first().first().toString(), QStringLiteral("a")); + KEYRELEASE(KEY_A); + QCOMPARE(inputSpy.count(), 2); + QCOMPARE(inputSpy.first().first().toString(), QStringLiteral("a")); + QCOMPARE(inputSpy.at(1).first().toString(), QStringLiteral("a")); + + LOCK; + KEYPRESS(KEY_B); + QCOMPARE(inputSpy.count(), 2); + KEYRELEASE(KEY_B); + QCOMPARE(inputSpy.count(), 2); + + UNLOCK; + KEYPRESS(KEY_C); + QCOMPARE(inputSpy.count(), 3); + QCOMPARE(inputSpy.first().first().toString(), QStringLiteral("a")); + QCOMPARE(inputSpy.at(1).first().toString(), QStringLiteral("a")); + QCOMPARE(inputSpy.at(2).first().toString(), QStringLiteral("c")); + KEYRELEASE(KEY_C); + QCOMPARE(inputSpy.count(), 4); + QCOMPARE(inputSpy.first().first().toString(), QStringLiteral("a")); + QCOMPARE(inputSpy.at(1).first().toString(), QStringLiteral("a")); + QCOMPARE(inputSpy.at(2).first().toString(), QStringLiteral("c")); + QCOMPARE(inputSpy.at(3).first().toString(), QStringLiteral("c")); + + effects->ungrabKeyboard(); +} + +void LockScreenTest::testEffectsKeyboardAutorepeat() +{ + // this test is just like testEffectsKeyboard, but tests auto repeat key events + // while the key is pressed the Effect should get auto repeated events + // but the lock screen should filter them out + std::unique_ptr effect(new HelperEffect); + QSignalSpy inputSpy(effect.get(), &HelperEffect::keyEvent); + effects->grabKeyboard(effect.get()); + + // we need to configure the key repeat first. It is only enabled on libinput + waylandServer()->seat()->keyboard()->setRepeatInfo(25, 300); + + quint32 timestamp = 1; + KEYPRESS(KEY_A); + QCOMPARE(inputSpy.count(), 1); + QCOMPARE(inputSpy.first().first().toString(), QStringLiteral("a")); + QVERIFY(inputSpy.wait()); + QVERIFY(inputSpy.count() > 1); + // and still more events + QVERIFY(inputSpy.wait()); + QCOMPARE(inputSpy.at(1).first().toString(), QStringLiteral("a")); + + // now release + inputSpy.clear(); + KEYRELEASE(KEY_A); + QCOMPARE(inputSpy.count(), 1); + + // while locked key repeat should not pass any events to the Effect + LOCK; + KEYPRESS(KEY_B); + QVERIFY(!inputSpy.wait(200)); + KEYRELEASE(KEY_B); + QVERIFY(!inputSpy.wait(200)); + + UNLOCK; + // don't test again, that's covered by testEffectsKeyboard + + effects->ungrabKeyboard(); +} + +void LockScreenTest::testMoveWindow() +{ + using namespace KWayland::Client; + auto [window, surface] = showWindow(); + QVERIFY(window); + QSignalSpy clientStepUserMovedResizedSpy(window, &Window::clientStepUserMovedResized); + quint32 timestamp = 1; + + workspace()->slotWindowMove(); + QCOMPARE(workspace()->moveResizeWindow(), window); + QVERIFY(window->isInteractiveMove()); + Test::keyboardKeyPressed(KEY_RIGHT, timestamp++); + Test::keyboardKeyReleased(KEY_RIGHT, timestamp++); + QEXPECT_FAIL("", "First event is ignored", Continue); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + + // TODO adjust once the expected fail is fixed + Test::keyboardKeyPressed(KEY_RIGHT, timestamp++); + Test::keyboardKeyReleased(KEY_RIGHT, timestamp++); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + + // while locking our window should continue to be in move resize + LOCK; + QCOMPARE(workspace()->moveResizeWindow(), window); + QVERIFY(window->isInteractiveMove()); + Test::keyboardKeyPressed(KEY_RIGHT, timestamp++); + Test::keyboardKeyReleased(KEY_RIGHT, timestamp++); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + + UNLOCK; + QCOMPARE(workspace()->moveResizeWindow(), window); + QVERIFY(window->isInteractiveMove()); + Test::keyboardKeyPressed(KEY_RIGHT, timestamp++); + Test::keyboardKeyReleased(KEY_RIGHT, timestamp++); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 2); + Test::keyboardKeyPressed(KEY_ESC, timestamp++); + Test::keyboardKeyReleased(KEY_ESC, timestamp++); + QVERIFY(!window->isInteractiveMove()); +} + +void LockScreenTest::testPointerShortcut() +{ + using namespace KWayland::Client; + std::unique_ptr action(new QAction(nullptr)); + QSignalSpy actionSpy(action.get(), &QAction::triggered); + input()->registerPointerShortcut(Qt::MetaModifier, Qt::LeftButton, action.get()); + + // try to trigger the shortcut + quint32 timestamp = 1; +#define PERFORM(expectedCount) \ + do { \ + Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++); \ + PRESS; \ + QCoreApplication::instance()->processEvents(); \ + QCOMPARE(actionSpy.count(), expectedCount); \ + RELEASE; \ + Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++); \ + QCoreApplication::instance()->processEvents(); \ + QCOMPARE(actionSpy.count(), expectedCount); \ + } while (false) + + PERFORM(1); + + // now the same thing with a locked screen + LOCK; + PERFORM(1); + + // and as unlocked + UNLOCK; + PERFORM(2); +#undef PERFORM +} + +void LockScreenTest::testAxisShortcut_data() +{ + QTest::addColumn("direction"); + QTest::addColumn("sign"); + + QTest::newRow("up") << Qt::Vertical << 1; + QTest::newRow("down") << Qt::Vertical << -1; + QTest::newRow("left") << Qt::Horizontal << 1; + QTest::newRow("right") << Qt::Horizontal << -1; +} + +void LockScreenTest::testAxisShortcut() +{ + using namespace KWayland::Client; + std::unique_ptr action(new QAction(nullptr)); + QSignalSpy actionSpy(action.get(), &QAction::triggered); + QFETCH(Qt::Orientation, direction); + QFETCH(int, sign); + PointerAxisDirection axisDirection = PointerAxisUp; + if (direction == Qt::Vertical) { + axisDirection = sign > 0 ? PointerAxisUp : PointerAxisDown; + } else { + axisDirection = sign > 0 ? PointerAxisLeft : PointerAxisRight; + } + input()->registerAxisShortcut(Qt::MetaModifier, axisDirection, action.get()); + + // try to trigger the shortcut + quint32 timestamp = 1; +#define PERFORM(expectedCount) \ + do { \ + Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++); \ + if (direction == Qt::Vertical) \ + Test::pointerAxisVertical(sign * 5.0, timestamp++); \ + else \ + Test::pointerAxisHorizontal(sign * 5.0, timestamp++); \ + QCoreApplication::instance()->processEvents(); \ + QCOMPARE(actionSpy.count(), expectedCount); \ + Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++); \ + QCoreApplication::instance()->processEvents(); \ + QCOMPARE(actionSpy.count(), expectedCount); \ + } while (false) + + PERFORM(1); + + // now the same thing with a locked screen + LOCK; + PERFORM(1); + + // and as unlocked + UNLOCK; + PERFORM(2); +#undef PERFORM +} + +void LockScreenTest::testKeyboardShortcut() +{ + using namespace KWayland::Client; + std::unique_ptr action(new QAction(nullptr)); + QSignalSpy actionSpy(action.get(), &QAction::triggered); + action->setProperty("componentName", QStringLiteral(KWIN_NAME)); + action->setObjectName("LockScreenTest::testKeyboardShortcut"); + KGlobalAccel::self()->setDefaultShortcut(action.get(), QList{Qt::CTRL | Qt::META | Qt::ALT | Qt::Key_Space}); + KGlobalAccel::self()->setShortcut(action.get(), QList{Qt::CTRL | Qt::META | Qt::ALT | Qt::Key_Space}, + KGlobalAccel::NoAutoloading); + + // try to trigger the shortcut + quint32 timestamp = 1; + KEYPRESS(KEY_LEFTCTRL); + KEYPRESS(KEY_LEFTMETA); + KEYPRESS(KEY_LEFTALT); + KEYPRESS(KEY_SPACE); + QVERIFY(actionSpy.wait()); + QCOMPARE(actionSpy.count(), 1); + KEYRELEASE(KEY_SPACE); + QVERIFY(!actionSpy.wait()); + QCOMPARE(actionSpy.count(), 1); + + LOCK; + KEYPRESS(KEY_SPACE); + QVERIFY(!actionSpy.wait()); + QCOMPARE(actionSpy.count(), 1); + KEYRELEASE(KEY_SPACE); + QVERIFY(!actionSpy.wait()); + QCOMPARE(actionSpy.count(), 1); + + UNLOCK; + KEYPRESS(KEY_SPACE); + QVERIFY(actionSpy.wait()); + QCOMPARE(actionSpy.count(), 2); + KEYRELEASE(KEY_SPACE); + QVERIFY(!actionSpy.wait()); + QCOMPARE(actionSpy.count(), 2); + KEYRELEASE(KEY_LEFTCTRL); + KEYRELEASE(KEY_LEFTMETA); + KEYRELEASE(KEY_LEFTALT); +} + +void LockScreenTest::testTouch() +{ + using namespace KWayland::Client; + auto touch = m_seat->createTouch(m_seat); + QVERIFY(touch); + QVERIFY(touch->isValid()); + auto [window, surface] = showWindow(); + QVERIFY(window); + QSignalSpy sequenceStartedSpy(touch, &Touch::sequenceStarted); + QSignalSpy cancelSpy(touch, &Touch::sequenceCanceled); + QSignalSpy pointRemovedSpy(touch, &Touch::pointRemoved); + + quint32 timestamp = 1; + Test::touchDown(1, QPointF(25, 25), timestamp++); + QVERIFY(sequenceStartedSpy.wait()); + QCOMPARE(sequenceStartedSpy.count(), 1); + + LOCK; + QVERIFY(cancelSpy.wait()); + + Test::touchUp(1, timestamp++); + QVERIFY(!pointRemovedSpy.wait(100)); + Test::touchDown(1, QPointF(25, 25), timestamp++); + Test::touchMotion(1, QPointF(26, 26), timestamp++); + Test::touchUp(1, timestamp++); + + UNLOCK; + Test::touchDown(1, QPointF(25, 25), timestamp++); + QVERIFY(sequenceStartedSpy.wait()); + QCOMPARE(sequenceStartedSpy.count(), 2); + Test::touchUp(1, timestamp++); + QVERIFY(pointRemovedSpy.wait()); + QCOMPARE(pointRemovedSpy.count(), 1); +} + +} + +WAYLANDTEST_MAIN(KWin::LockScreenTest) +#include "lockscreen.moc" diff --git a/autotests/integration/maximize_test.cpp b/autotests/integration/maximize_test.cpp new file mode 100644 index 0000000..e28f7b6 --- /dev/null +++ b/autotests/integration/maximize_test.cpp @@ -0,0 +1,330 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "decorations/decorationbridge.h" +#include "decorations/settings.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include +#include +#include +#include + +#include +#include +#include + +using namespace KWin; +using namespace KWayland::Client; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_maximized-0"); + +class TestMaximized : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testMaximizedPassedToDeco(); + void testInitiallyMaximizedBorderless(); + void testBorderlessMaximizedWindow(); + void testMaximizedGainFocusAndBeActivated(); +}; + +void TestMaximized::initTestCase() +{ + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); +} + +void TestMaximized::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::XdgDecorationV1 | Test::AdditionalWaylandInterface::PlasmaShell)); + + workspace()->setActiveOutput(QPoint(640, 512)); + KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void TestMaximized::cleanup() +{ + Test::destroyWaylandConnection(); + + // adjust config + auto group = kwinApp()->config()->group("Windows"); + group.writeEntry("BorderlessMaximizedWindows", false); + group.sync(); + Workspace::self()->slotReconfigure(); + QCOMPARE(options->borderlessMaximizedWindows(), false); +} + +void TestMaximized::testMaximizedPassedToDeco() +{ + // this test verifies that when a XdgShellClient gets maximized the Decoration receives the signal + + // Create the test window. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + std::unique_ptr xdgDecoration(Test::createXdgToplevelDecorationV1(shellSurface.get())); + + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(window->isDecorated()); + + auto decoration = window->decoration(); + QVERIFY(decoration); + QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore); + + // Wait for configure event that signals the window is active now. + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); + + // When there are no borders, there is no change to them when maximizing. + // TODO: we should test both cases with fixed fake decoration for autotests. + const bool hasBorders = Workspace::self()->decorationBridge()->settings()->borderSize() != KDecoration2::BorderSize::None; + + // now maximize + QSignalSpy bordersChangedSpy(decoration, &KDecoration2::Decoration::bordersChanged); + QSignalSpy maximizedChangedSpy(decoration->client().toStrongRef().get(), &KDecoration2::DecoratedClient::maximizedChanged); + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + + workspace()->slotWindowMaximize(); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024 - decoration->borderTop())); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).toSize(), Qt::red); + QVERIFY(frameGeometryChangedSpy.wait()); + + // If no borders, there is only the initial geometry shape change, but none through border resizing. + QCOMPARE(frameGeometryChangedSpy.count(), hasBorders ? 2 : 1); + QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(maximizedChangedSpy.count(), 1); + QCOMPARE(maximizedChangedSpy.last().first().toBool(), true); + QCOMPARE(bordersChangedSpy.count(), hasBorders ? 1 : 0); + QCOMPARE(decoration->borderLeft(), 0); + QCOMPARE(decoration->borderBottom(), 0); + QCOMPARE(decoration->borderRight(), 0); + QVERIFY(decoration->borderTop() != 0); + + // now unmaximize again + workspace()->slotWindowMaximize(); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 4); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(100, 50)); + + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(100, 50), Qt::red); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(frameGeometryChangedSpy.count(), hasBorders ? 4 : 2); + QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(maximizedChangedSpy.count(), 2); + QCOMPARE(maximizedChangedSpy.last().first().toBool(), false); + QCOMPARE(bordersChangedSpy.count(), hasBorders ? 2 : 0); + QVERIFY(decoration->borderTop() != 0); + QVERIFY(decoration->borderLeft() != !hasBorders); + QVERIFY(decoration->borderRight() != !hasBorders); + QVERIFY(decoration->borderBottom() != !hasBorders); + + // Destroy the test window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void TestMaximized::testInitiallyMaximizedBorderless() +{ + // This test verifies that a window created as maximized, will be maximized and without Border with BorderlessMaximizedWindows + + // adjust config + auto group = kwinApp()->config()->group("Windows"); + group.writeEntry("BorderlessMaximizedWindows", true); + group.sync(); + Workspace::self()->slotReconfigure(); + QCOMPARE(options->borderlessMaximizedWindows(), true); + + // Create the test window. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + std::unique_ptr decoration(Test::createXdgToplevelDecorationV1(shellSurface.get())); + + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + shellSurface->set_maximized(); + QSignalSpy decorationConfigureRequestedSpy(decoration.get(), &Test::XdgToplevelDecorationV1::configureRequested); + decoration->set_mode(Test::XdgToplevelDecorationV1::mode_server_side); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // Wait for the initial configure event. + Test::XdgToplevel::States states; + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); + + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::blue); + QVERIFY(window); + QVERIFY(!window->isDecorated()); + QVERIFY(window->isActive()); + QVERIFY(window->isMaximizable()); + QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(window->requestedMaximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(decorationConfigureRequestedSpy.last().at(0).value(), + Test::XdgToplevelDecorationV1::mode_server_side); + + // Destroy the window. + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} +void TestMaximized::testBorderlessMaximizedWindow() +{ + // This test verifies that a maximized window looses it's server-side + // decoration when the borderless maximized option is on. + + // Enable the borderless maximized windows option. + auto group = kwinApp()->config()->group("Windows"); + group.writeEntry("BorderlessMaximizedWindows", true); + group.sync(); + Workspace::self()->slotReconfigure(); + QCOMPARE(options->borderlessMaximizedWindows(), true); + + // Create the test window. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + std::unique_ptr decoration(Test::createXdgToplevelDecorationV1(shellSurface.get())); + + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + QSignalSpy decorationConfigureRequestedSpy(decoration.get(), &Test::XdgToplevelDecorationV1::configureRequested); + decoration->set_mode(Test::XdgToplevelDecorationV1::mode_server_side); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // Wait for the initial configure event. + Test::XdgToplevel::States states; + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(0, 0)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Map the window. + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(window->isActive()); + QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(window->isDecorated(), true); + + // We should receive a configure event when the window becomes active. + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Maximize the window. + const QRectF maximizeRestoreGeometry = window->frameGeometry(); + workspace()->slotWindowMaximize(); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); + + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(1280, 1024), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(window->requestedMaximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(window->isDecorated(), false); + + // Restore the window. + workspace()->slotWindowMaximize(); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 4); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(100, 50)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(100, 50), Qt::red); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry(), maximizeRestoreGeometry); + QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(window->isDecorated(), true); + + // Destroy the window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void TestMaximized::testMaximizedGainFocusAndBeActivated() +{ + // This test verifies that a window will be raised and gain focus when it's maximized + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr xdgShellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr xdgShellSurface2(Test::createXdgToplevelSurface(surface2.get())); + auto window2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::blue); + + QVERIFY(!window->isActive()); + QVERIFY(window2->isActive()); + QCOMPARE(workspace()->stackingOrder(), (QList{window, window2})); + + workspace()->performWindowOperation(window, Options::MaximizeOp); + + QVERIFY(window->isActive()); + QVERIFY(!window2->isActive()); + QCOMPARE(workspace()->stackingOrder(), (QList{window2, window})); + + xdgShellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); + xdgShellSurface2.reset(); + QVERIFY(Test::waitForWindowDestroyed(window2)); +} + +WAYLANDTEST_MAIN(TestMaximized) +#include "maximize_test.moc" diff --git a/autotests/integration/modifier_only_shortcut_test.cpp b/autotests/integration/modifier_only_shortcut_test.cpp new file mode 100644 index 0000000..69e4aa7 --- /dev/null +++ b/autotests/integration/modifier_only_shortcut_test.cpp @@ -0,0 +1,374 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include + +#include "kwin_wayland_test.h" + +#include "core/platform.h" +#include "cursor.h" +#include "input.h" +#include "keyboard_input.h" +#include "wayland_server.h" +#include "workspace.h" + +#include + +#include + +#include + +using namespace KWin; +using namespace KWayland::Client; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_modifier_only_shortcut-0"); +static const QString s_serviceName = QStringLiteral("org.kde.KWin.Test.ModifierOnlyShortcut"); +static const QString s_path = QStringLiteral("/Test"); + +class ModifierOnlyShortcutTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testTrigger_data(); + void testTrigger(); + void testCapsLock(); + void testGlobalShortcutsDisabled_data(); + void testGlobalShortcutsDisabled(); +}; + +class Target : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.KWin.Test.ModifierOnlyShortcut") + +public: + Target(); + ~Target() override; + +public Q_SLOTS: + Q_SCRIPTABLE void shortcut(); + +Q_SIGNALS: + void shortcutTriggered(); +}; + +Target::Target() + : QObject() +{ + QDBusConnection::sessionBus().registerService(s_serviceName); + QDBusConnection::sessionBus().registerObject(s_path, s_serviceName, this, QDBusConnection::ExportScriptableSlots); +} + +Target::~Target() +{ + QDBusConnection::sessionBus().unregisterObject(s_path); + QDBusConnection::sessionBus().unregisterService(s_serviceName); +} + +void Target::shortcut() +{ + Q_EMIT shortcutTriggered(); +} + +void ModifierOnlyShortcutTest::initTestCase() +{ + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); + qputenv("KWIN_XKB_DEFAULT_KEYMAP", "1"); + qputenv("XKB_DEFAULT_RULES", "evdev"); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); +} + +void ModifierOnlyShortcutTest::init() +{ + workspace()->setActiveOutput(QPoint(640, 512)); + KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void ModifierOnlyShortcutTest::cleanup() +{ +} + +void ModifierOnlyShortcutTest::testTrigger_data() +{ + QTest::addColumn("metaConfig"); + QTest::addColumn("altConfig"); + QTest::addColumn("controlConfig"); + QTest::addColumn("shiftConfig"); + QTest::addColumn("modifier"); + QTest::addColumn>("nonTriggeringMods"); + + const QStringList trigger = QStringList{s_serviceName, s_path, s_serviceName, QStringLiteral("shortcut")}; + const QStringList e = QStringList(); + + QTest::newRow("leftMeta") << trigger << e << e << e << KEY_LEFTMETA << QList{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT}; + QTest::newRow("rightMeta") << trigger << e << e << e << KEY_RIGHTMETA << QList{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT}; + QTest::newRow("leftAlt") << e << trigger << e << e << KEY_LEFTALT << QList{KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT}; + QTest::newRow("rightAlt") << e << trigger << e << e << KEY_RIGHTALT << QList{KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT}; + QTest::newRow("leftControl") << e << e << trigger << e << KEY_LEFTCTRL << QList{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTSHIFT, KEY_RIGHTSHIFT}; + QTest::newRow("rightControl") << e << e << trigger << e << KEY_RIGHTCTRL << QList{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTSHIFT, KEY_RIGHTSHIFT}; + QTest::newRow("leftShift") << e << e << e << trigger << KEY_LEFTSHIFT << QList{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTMETA, KEY_RIGHTMETA}; + QTest::newRow("rightShift") << e << e << e << trigger << KEY_RIGHTSHIFT << QList{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTMETA, KEY_RIGHTMETA}; +} + +void ModifierOnlyShortcutTest::testTrigger() +{ + // this test verifies that modifier only shortcut triggers correctly + Target target; + QSignalSpy triggeredSpy(&target, &Target::shortcutTriggered); + + KConfigGroup group = kwinApp()->config()->group("ModifierOnlyShortcuts"); + QFETCH(QStringList, metaConfig); + QFETCH(QStringList, altConfig); + QFETCH(QStringList, shiftConfig); + QFETCH(QStringList, controlConfig); + group.writeEntry("Meta", metaConfig); + group.writeEntry("Alt", altConfig); + group.writeEntry("Shift", shiftConfig); + group.writeEntry("Control", controlConfig); + group.sync(); + workspace()->slotReconfigure(); + + // configured shortcut should trigger + quint32 timestamp = 1; + QFETCH(int, modifier); + Test::keyboardKeyPressed(modifier, timestamp++); + Test::keyboardKeyReleased(modifier, timestamp++); + QCOMPARE(triggeredSpy.count(), 1); + + // the other shortcuts should not trigger + QFETCH(QList, nonTriggeringMods); + for (auto it = nonTriggeringMods.constBegin(), end = nonTriggeringMods.constEnd(); it != end; it++) { + Test::keyboardKeyPressed(*it, timestamp++); + Test::keyboardKeyReleased(*it, timestamp++); + QCOMPARE(triggeredSpy.count(), 1); + } + + // try configured again + Test::keyboardKeyPressed(modifier, timestamp++); + Test::keyboardKeyReleased(modifier, timestamp++); + QCOMPARE(triggeredSpy.count(), 2); + + // click another key while modifier is held + Test::keyboardKeyPressed(modifier, timestamp++); + Test::keyboardKeyPressed(KEY_A, timestamp++); + Test::keyboardKeyReleased(KEY_A, timestamp++); + Test::keyboardKeyReleased(modifier, timestamp++); + QCOMPARE(triggeredSpy.count(), 2); + + // release other key after modifier release + Test::keyboardKeyPressed(modifier, timestamp++); + Test::keyboardKeyPressed(KEY_A, timestamp++); + Test::keyboardKeyReleased(modifier, timestamp++); + Test::keyboardKeyReleased(KEY_A, timestamp++); + QCOMPARE(triggeredSpy.count(), 2); + + // press key before pressing modifier + Test::keyboardKeyPressed(KEY_A, timestamp++); + Test::keyboardKeyPressed(modifier, timestamp++); + Test::keyboardKeyReleased(modifier, timestamp++); + Test::keyboardKeyReleased(KEY_A, timestamp++); + QCOMPARE(triggeredSpy.count(), 2); + + // mouse button pressed before clicking modifier + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + QCOMPARE(input()->qtButtonStates(), Qt::LeftButton); + Test::keyboardKeyPressed(modifier, timestamp++); + Test::keyboardKeyReleased(modifier, timestamp++); + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + QCOMPARE(input()->qtButtonStates(), Qt::NoButton); + QCOMPARE(triggeredSpy.count(), 2); + + // mouse button press before mod press, release before mod release + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + QCOMPARE(input()->qtButtonStates(), Qt::LeftButton); + Test::keyboardKeyPressed(modifier, timestamp++); + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + Test::keyboardKeyReleased(modifier, timestamp++); + QCOMPARE(input()->qtButtonStates(), Qt::NoButton); + QCOMPARE(triggeredSpy.count(), 2); + + // mouse button click while mod is pressed + Test::keyboardKeyPressed(modifier, timestamp++); + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + QCOMPARE(input()->qtButtonStates(), Qt::LeftButton); + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + Test::keyboardKeyReleased(modifier, timestamp++); + QCOMPARE(input()->qtButtonStates(), Qt::NoButton); + QCOMPARE(triggeredSpy.count(), 2); + + // scroll while mod is pressed + Test::keyboardKeyPressed(modifier, timestamp++); + Test::pointerAxisVertical(5.0, timestamp++); + Test::keyboardKeyReleased(modifier, timestamp++); + QCOMPARE(triggeredSpy.count(), 2); + + // same for horizontal + Test::keyboardKeyPressed(modifier, timestamp++); + Test::pointerAxisHorizontal(5.0, timestamp++); + Test::keyboardKeyReleased(modifier, timestamp++); + QCOMPARE(triggeredSpy.count(), 2); + +#if KWIN_BUILD_SCREENLOCKER + // now try to lock the screen while modifier key is pressed + Test::keyboardKeyPressed(modifier, timestamp++); + QVERIFY(Test::lockScreen()); + Test::keyboardKeyReleased(modifier, timestamp++); + QCOMPARE(triggeredSpy.count(), 2); + + // now trigger while screen is locked, should also not work + Test::keyboardKeyPressed(modifier, timestamp++); + Test::keyboardKeyReleased(modifier, timestamp++); + QCOMPARE(triggeredSpy.count(), 2); + + QVERIFY(Test::unlockScreen()); +#endif +} + +void ModifierOnlyShortcutTest::testCapsLock() +{ + // this test verifies that Capslock does not trigger the shift shortcut + // but other shortcuts still trigger even when Capslock is on + Target target; + QSignalSpy triggeredSpy(&target, &Target::shortcutTriggered); + + KConfigGroup group = kwinApp()->config()->group("ModifierOnlyShortcuts"); + group.writeEntry("Meta", QStringList()); + group.writeEntry("Alt", QStringList()); + group.writeEntry("Shift", QStringList{s_serviceName, s_path, s_serviceName, QStringLiteral("shortcut")}); + group.writeEntry("Control", QStringList()); + group.sync(); + workspace()->slotReconfigure(); + + // first test that the normal shortcut triggers + quint32 timestamp = 1; + const int modifier = KEY_LEFTSHIFT; + Test::keyboardKeyPressed(modifier, timestamp++); + Test::keyboardKeyReleased(modifier, timestamp++); + QCOMPARE(triggeredSpy.count(), 1); + + // now capslock + Test::keyboardKeyPressed(KEY_CAPSLOCK, timestamp++); + Test::keyboardKeyReleased(KEY_CAPSLOCK, timestamp++); + QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier); + QCOMPARE(triggeredSpy.count(), 1); + + // currently caps lock is on + // shift still triggers + Test::keyboardKeyPressed(modifier, timestamp++); + Test::keyboardKeyReleased(modifier, timestamp++); + QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier); + QCOMPARE(triggeredSpy.count(), 2); + + // meta should also trigger + group.writeEntry("Meta", QStringList{s_serviceName, s_path, s_serviceName, QStringLiteral("shortcut")}); + group.writeEntry("Alt", QStringList()); + group.writeEntry("Shift", QStringList{}); + group.writeEntry("Control", QStringList()); + group.sync(); + workspace()->slotReconfigure(); + Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++); + QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier | Qt::MetaModifier); + QCOMPARE(input()->keyboard()->xkb()->modifiersRelevantForGlobalShortcuts(), Qt::MetaModifier); + Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++); + QCOMPARE(triggeredSpy.count(), 3); + + // set back to shift to ensure we don't trigger with capslock + group.writeEntry("Meta", QStringList()); + group.writeEntry("Alt", QStringList()); + group.writeEntry("Shift", QStringList{s_serviceName, s_path, s_serviceName, QStringLiteral("shortcut")}); + group.writeEntry("Control", QStringList()); + group.sync(); + workspace()->slotReconfigure(); + + // release caps lock + Test::keyboardKeyPressed(KEY_CAPSLOCK, timestamp++); + Test::keyboardKeyReleased(KEY_CAPSLOCK, timestamp++); + QCOMPARE(input()->keyboardModifiers(), Qt::NoModifier); + QCOMPARE(triggeredSpy.count(), 3); +} + +void ModifierOnlyShortcutTest::testGlobalShortcutsDisabled_data() +{ + QTest::addColumn("metaConfig"); + QTest::addColumn("altConfig"); + QTest::addColumn("controlConfig"); + QTest::addColumn("shiftConfig"); + QTest::addColumn("modifier"); + + const QStringList trigger = QStringList{s_serviceName, s_path, s_serviceName, QStringLiteral("shortcut")}; + const QStringList e = QStringList(); + + QTest::newRow("leftMeta") << trigger << e << e << e << KEY_LEFTMETA; + QTest::newRow("rightMeta") << trigger << e << e << e << KEY_RIGHTMETA; + QTest::newRow("leftAlt") << e << trigger << e << e << KEY_LEFTALT; + QTest::newRow("rightAlt") << e << trigger << e << e << KEY_RIGHTALT; + QTest::newRow("leftControl") << e << e << trigger << e << KEY_LEFTCTRL; + QTest::newRow("rightControl") << e << e << trigger << e << KEY_RIGHTCTRL; + QTest::newRow("leftShift") << e << e << e << trigger << KEY_LEFTSHIFT; + QTest::newRow("rightShift") << e << e << e << trigger << KEY_RIGHTSHIFT; +} + +void ModifierOnlyShortcutTest::testGlobalShortcutsDisabled() +{ + // this test verifies that when global shortcuts are disabled inside KWin (e.g. through a window rule) + // the modifier only shortcuts do not trigger. + // see BUG: 370146 + Target target; + QSignalSpy triggeredSpy(&target, &Target::shortcutTriggered); + + KConfigGroup group = kwinApp()->config()->group("ModifierOnlyShortcuts"); + QFETCH(QStringList, metaConfig); + QFETCH(QStringList, altConfig); + QFETCH(QStringList, shiftConfig); + QFETCH(QStringList, controlConfig); + group.writeEntry("Meta", metaConfig); + group.writeEntry("Alt", altConfig); + group.writeEntry("Shift", shiftConfig); + group.writeEntry("Control", controlConfig); + group.sync(); + workspace()->slotReconfigure(); + + // trigger once to verify the shortcut works + quint32 timestamp = 1; + QFETCH(int, modifier); + QVERIFY(!workspace()->globalShortcutsDisabled()); + Test::keyboardKeyPressed(modifier, timestamp++); + Test::keyboardKeyReleased(modifier, timestamp++); + QCOMPARE(triggeredSpy.count(), 1); + triggeredSpy.clear(); + + // now disable global shortcuts + workspace()->disableGlobalShortcutsForClient(true); + QVERIFY(workspace()->globalShortcutsDisabled()); + // Should not get triggered + Test::keyboardKeyPressed(modifier, timestamp++); + Test::keyboardKeyReleased(modifier, timestamp++); + QCOMPARE(triggeredSpy.count(), 0); + triggeredSpy.clear(); + + // enable again + workspace()->disableGlobalShortcutsForClient(false); + QVERIFY(!workspace()->globalShortcutsDisabled()); + // should get triggered again + Test::keyboardKeyPressed(modifier, timestamp++); + Test::keyboardKeyReleased(modifier, timestamp++); + QCOMPARE(triggeredSpy.count(), 1); +} + +WAYLANDTEST_MAIN(ModifierOnlyShortcutTest) +#include "modifier_only_shortcut_test.moc" diff --git a/autotests/integration/move_resize_window_test.cpp b/autotests/integration/move_resize_window_test.cpp new file mode 100644 index 0000000..8902469 --- /dev/null +++ b/autotests/integration/move_resize_window_test.cpp @@ -0,0 +1,1102 @@ + +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2015 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "atoms.h" +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "deleted.h" +#include "effects.h" +#include "placement.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" +#include "x11window.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +Q_DECLARE_METATYPE(KWin::QuickTileMode) +Q_DECLARE_METATYPE(KWin::MaximizeMode) + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_quick_tiling-0"); + +class MoveResizeWindowTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testMove(); + void testResize(); + void testPackTo_data(); + void testPackTo(); + void testPackAgainstClient_data(); + void testPackAgainstClient(); + void testGrowShrink_data(); + void testGrowShrink(); + void testPointerMoveEnd_data(); + void testPointerMoveEnd(); + void testClientSideMove(); + void testPlasmaShellSurfaceMovable_data(); + void testPlasmaShellSurfaceMovable(); + void testNetMove(); + void testAdjustClientGeometryOfAutohidingX11Panel_data(); + void testAdjustClientGeometryOfAutohidingX11Panel(); + void testAdjustClientGeometryOfAutohidingWaylandPanel_data(); + void testAdjustClientGeometryOfAutohidingWaylandPanel(); + void testResizeForVirtualKeyboard_data(); + void testResizeForVirtualKeyboard(); + void testResizeForVirtualKeyboardWithMaximize(); + void testResizeForVirtualKeyboardWithFullScreen(); + void testDestroyMoveClient(); + void testDestroyResizeClient(); + +private: + KWayland::Client::ConnectionThread *m_connection = nullptr; + KWayland::Client::Compositor *m_compositor = nullptr; +}; + +void MoveResizeWindowTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType("MaximizeMode"); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 1); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); +} + +void MoveResizeWindowTest::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::PlasmaShell | Test::AdditionalWaylandInterface::Seat)); + QVERIFY(Test::waitForWaylandPointer()); + m_connection = Test::waylandConnection(); + m_compositor = Test::waylandCompositor(); + + workspace()->setActiveOutput(QPoint(640, 512)); +} + +void MoveResizeWindowTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void MoveResizeWindowTest::testMove() +{ + using namespace KWayland::Client; + + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + // let's render + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + + QVERIFY(window); + QCOMPARE(workspace()->activeWindow(), window); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50)); + QSignalSpy startMoveResizedSpy(window, &Window::clientStartUserMovedResized); + QSignalSpy moveResizedChangedSpy(window, &Window::moveResizedChanged); + QSignalSpy clientStepUserMovedResizedSpy(window, &Window::clientStepUserMovedResized); + QSignalSpy clientFinishUserMovedResizedSpy(window, &Window::clientFinishUserMovedResized); + + // effects signal handlers + QSignalSpy windowStartUserMovedResizedSpy(effects, &EffectsHandler::windowStartUserMovedResized); + QSignalSpy windowStepUserMovedResizedSpy(effects, &EffectsHandler::windowStepUserMovedResized); + QSignalSpy windowFinishUserMovedResizedSpy(effects, &EffectsHandler::windowFinishUserMovedResized); + + // begin move + QVERIFY(workspace()->moveResizeWindow() == nullptr); + QCOMPARE(window->isInteractiveMove(), false); + workspace()->slotWindowMove(); + QCOMPARE(workspace()->moveResizeWindow(), window); + QCOMPARE(startMoveResizedSpy.count(), 1); + QCOMPARE(moveResizedChangedSpy.count(), 1); + QCOMPARE(windowStartUserMovedResizedSpy.count(), 1); + QCOMPARE(window->isInteractiveMove(), true); + QCOMPARE(window->geometryRestore(), QRect()); + + // send some key events, not going through input redirection + const QPoint cursorPos = Cursors::self()->mouse()->pos(); + window->keyPressEvent(Qt::Key_Right); + window->updateInteractiveMoveResize(Cursors::self()->mouse()->pos()); + QCOMPARE(Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); + QEXPECT_FAIL("", "First event is ignored", Continue); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + clientStepUserMovedResizedSpy.clear(); + windowStepUserMovedResizedSpy.clear(); + + window->keyPressEvent(Qt::Key_Right); + window->updateInteractiveMoveResize(Cursors::self()->mouse()->pos()); + QCOMPARE(Cursors::self()->mouse()->pos(), cursorPos + QPoint(16, 0)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + QCOMPARE(windowStepUserMovedResizedSpy.count(), 1); + + window->keyPressEvent(Qt::Key_Down | Qt::ALT); + window->updateInteractiveMoveResize(Cursors::self()->mouse()->pos()); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 2); + QCOMPARE(windowStepUserMovedResizedSpy.count(), 2); + QCOMPARE(window->frameGeometry(), QRect(16, 32, 100, 50)); + QCOMPARE(Cursors::self()->mouse()->pos(), cursorPos + QPoint(16, 32)); + + // let's end + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0); + window->keyPressEvent(Qt::Key_Enter); + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); + QCOMPARE(moveResizedChangedSpy.count(), 2); + QCOMPARE(windowFinishUserMovedResizedSpy.count(), 1); + QCOMPARE(window->frameGeometry(), QRect(16, 32, 100, 50)); + QCOMPARE(window->isInteractiveMove(), false); + QVERIFY(workspace()->moveResizeWindow() == nullptr); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void MoveResizeWindowTest::testResize() +{ + // a test case which manually resizes a window + using namespace KWayland::Client; + + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + QVERIFY(shellSurface != nullptr); + + // Wait for the initial configure event. + Test::XdgToplevel::States states; + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + QCOMPARE(toplevelConfigureRequestedSpy.count(), 1); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Resizing)); + + // Let's render. + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + + // We have to receive a configure event when the client becomes active. + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); + QCOMPARE(toplevelConfigureRequestedSpy.count(), 2); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Resizing)); + + QVERIFY(window); + QCOMPARE(workspace()->activeWindow(), window); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50)); + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + QSignalSpy startMoveResizedSpy(window, &Window::clientStartUserMovedResized); + QSignalSpy moveResizedChangedSpy(window, &Window::moveResizedChanged); + QSignalSpy clientStepUserMovedResizedSpy(window, &Window::clientStepUserMovedResized); + QSignalSpy clientFinishUserMovedResizedSpy(window, &Window::clientFinishUserMovedResized); + + // begin resize + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QCOMPARE(window->isInteractiveMove(), false); + QCOMPARE(window->isInteractiveResize(), false); + workspace()->slotWindowResize(); + QCOMPARE(workspace()->moveResizeWindow(), window); + QCOMPARE(startMoveResizedSpy.count(), 1); + QCOMPARE(moveResizedChangedSpy.count(), 1); + QCOMPARE(window->isInteractiveResize(), true); + QCOMPARE(window->geometryRestore(), QRect()); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); + QCOMPARE(toplevelConfigureRequestedSpy.count(), 3); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing)); + + // Trigger a change. + const QPoint cursorPos = Cursors::self()->mouse()->pos(); + window->keyPressEvent(Qt::Key_Right); + window->updateInteractiveMoveResize(Cursors::self()->mouse()->pos()); + QCOMPARE(Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); + + // The client should receive a configure event with the new size. + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 4); + QCOMPARE(toplevelConfigureRequestedSpy.count(), 4); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing)); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(108, 50)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + + // Now render new size. + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(108, 50), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 108, 50)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + + // Go down. + window->keyPressEvent(Qt::Key_Down); + window->updateInteractiveMoveResize(Cursors::self()->mouse()->pos()); + QCOMPARE(Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 8)); + + // The client should receive another configure event. + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 5); + QCOMPARE(toplevelConfigureRequestedSpy.count(), 5); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing)); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(108, 58)); + + // Now render new size. + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(108, 58), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 108, 58)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 2); + + // Let's finalize the resize operation. + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0); + window->keyPressEvent(Qt::Key_Enter); + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); + QCOMPARE(moveResizedChangedSpy.count(), 2); + QCOMPARE(window->isInteractiveResize(), false); + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 6); + QCOMPARE(toplevelConfigureRequestedSpy.count(), 6); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Resizing)); + + // Destroy the client. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void MoveResizeWindowTest::testPackTo_data() +{ + QTest::addColumn("methodCall"); + QTest::addColumn("expectedGeometry"); + + QTest::newRow("left") << QStringLiteral("slotWindowMoveLeft") << QRectF(0, 487, 100, 50); + QTest::newRow("up") << QStringLiteral("slotWindowMoveUp") << QRectF(590, 0, 100, 50); + QTest::newRow("right") << QStringLiteral("slotWindowMoveRight") << QRectF(1180, 487, 100, 50); + QTest::newRow("down") << QStringLiteral("slotWindowMoveDown") << QRectF(590, 974, 100, 50); +} + +void MoveResizeWindowTest::testPackTo() +{ + using namespace KWayland::Client; + + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + // let's render + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + + QVERIFY(window); + QCOMPARE(workspace()->activeWindow(), window); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50)); + + // let's place it centered + workspace()->placement()->placeCentered(window, QRect(0, 0, 1280, 1024)); + QCOMPARE(window->frameGeometry(), QRect(590, 487, 100, 50)); + + QFETCH(QString, methodCall); + QMetaObject::invokeMethod(workspace(), methodCall.toLocal8Bit().constData()); + QTEST(window->frameGeometry(), "expectedGeometry"); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void MoveResizeWindowTest::testPackAgainstClient_data() +{ + QTest::addColumn("methodCall"); + QTest::addColumn("expectedGeometry"); + + QTest::newRow("left") << QStringLiteral("slotWindowMoveLeft") << QRectF(10, 487, 100, 50); + QTest::newRow("up") << QStringLiteral("slotWindowMoveUp") << QRectF(590, 10, 100, 50); + QTest::newRow("right") << QStringLiteral("slotWindowMoveRight") << QRectF(1170, 487, 100, 50); + QTest::newRow("down") << QStringLiteral("slotWindowMoveDown") << QRectF(590, 964, 100, 50); +} + +void MoveResizeWindowTest::testPackAgainstClient() +{ + using namespace KWayland::Client; + + std::unique_ptr surface1(Test::createSurface()); + QVERIFY(surface1 != nullptr); + std::unique_ptr surface2(Test::createSurface()); + QVERIFY(surface2 != nullptr); + std::unique_ptr surface3(Test::createSurface()); + QVERIFY(surface3 != nullptr); + std::unique_ptr surface4(Test::createSurface()); + QVERIFY(surface4 != nullptr); + + std::unique_ptr shellSurface1(Test::createXdgToplevelSurface(surface1.get())); + QVERIFY(shellSurface1 != nullptr); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + QVERIFY(shellSurface2 != nullptr); + std::unique_ptr shellSurface3(Test::createXdgToplevelSurface(surface3.get())); + QVERIFY(shellSurface3 != nullptr); + std::unique_ptr shellSurface4(Test::createXdgToplevelSurface(surface4.get())); + QVERIFY(shellSurface4 != nullptr); + auto renderWindow = [](KWayland::Client::Surface *surface, const QString &methodCall, const QRect &expectedGeometry) { + // let's render + auto window = Test::renderAndWaitForShown(surface, QSize(10, 10), Qt::blue); + + QVERIFY(window); + QCOMPARE(workspace()->activeWindow(), window); + QCOMPARE(window->frameGeometry().size(), QSize(10, 10)); + // let's place it centered + workspace()->placement()->placeCentered(window, QRect(0, 0, 1280, 1024)); + QCOMPARE(window->frameGeometry(), QRect(635, 507, 10, 10)); + QMetaObject::invokeMethod(workspace(), methodCall.toLocal8Bit().constData()); + QCOMPARE(window->frameGeometry(), expectedGeometry); + }; + renderWindow(surface1.get(), QStringLiteral("slotWindowMoveLeft"), QRect(0, 507, 10, 10)); + renderWindow(surface2.get(), QStringLiteral("slotWindowMoveUp"), QRect(635, 0, 10, 10)); + renderWindow(surface3.get(), QStringLiteral("slotWindowMoveRight"), QRect(1270, 507, 10, 10)); + renderWindow(surface4.get(), QStringLiteral("slotWindowMoveDown"), QRect(635, 1014, 10, 10)); + + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + + QVERIFY(window); + QCOMPARE(workspace()->activeWindow(), window); + // let's place it centered + workspace()->placement()->placeCentered(window, QRect(0, 0, 1280, 1024)); + QCOMPARE(window->frameGeometry(), QRect(590, 487, 100, 50)); + + QFETCH(QString, methodCall); + QMetaObject::invokeMethod(workspace(), methodCall.toLocal8Bit().constData()); + QTEST(window->frameGeometry(), "expectedGeometry"); +} + +void MoveResizeWindowTest::testGrowShrink_data() +{ + QTest::addColumn("methodCall"); + QTest::addColumn("expectedGeometry"); + + QTest::newRow("grow vertical") << QStringLiteral("slotWindowExpandVertical") << QRectF(590, 487, 100, 537); + QTest::newRow("grow horizontal") << QStringLiteral("slotWindowExpandHorizontal") << QRectF(590, 487, 690, 50); + QTest::newRow("shrink vertical") << QStringLiteral("slotWindowShrinkVertical") << QRectF(590, 487, 100, 23); + QTest::newRow("shrink horizontal") << QStringLiteral("slotWindowShrinkHorizontal") << QRectF(590, 487, 40, 50); +} + +void MoveResizeWindowTest::testGrowShrink() +{ + using namespace KWayland::Client; + + // block geometry helper + std::unique_ptr surface1(Test::createSurface()); + QVERIFY(surface1 != nullptr); + std::unique_ptr shellSurface1(Test::createXdgToplevelSurface(surface1.get())); + QVERIFY(shellSurface1 != nullptr); + Test::render(surface1.get(), QSize(650, 514), Qt::blue); + QVERIFY(Test::waitForWaylandWindowShown()); + workspace()->slotWindowMoveRight(); + workspace()->slotWindowMoveDown(); + + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + // let's render + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(toplevelConfigureRequestedSpy.wait()); + + QVERIFY(window); + QCOMPARE(workspace()->activeWindow(), window); + + // let's place it centered + workspace()->placement()->placeCentered(window, QRect(0, 0, 1280, 1024)); + QCOMPARE(window->frameGeometry(), QRect(590, 487, 100, 50)); + + QFETCH(QString, methodCall); + QMetaObject::invokeMethod(workspace(), methodCall.toLocal8Bit().constData()); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + Test::render(surface.get(), toplevelConfigureRequestedSpy.last().first().toSize(), Qt::red); + + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + m_connection->flush(); + QVERIFY(frameGeometryChangedSpy.wait()); + QTEST(window->frameGeometry(), "expectedGeometry"); +} + +void MoveResizeWindowTest::testPointerMoveEnd_data() +{ + QTest::addColumn("additionalButton"); + + QTest::newRow("BTN_RIGHT") << BTN_RIGHT; + QTest::newRow("BTN_MIDDLE") << BTN_MIDDLE; + QTest::newRow("BTN_SIDE") << BTN_SIDE; + QTest::newRow("BTN_EXTRA") << BTN_EXTRA; + QTest::newRow("BTN_FORWARD") << BTN_FORWARD; + QTest::newRow("BTN_BACK") << BTN_BACK; + QTest::newRow("BTN_TASK") << BTN_TASK; + for (int i = BTN_TASK + 1; i < BTN_JOYSTICK; i++) { + QTest::newRow(QByteArray::number(i, 16).constData()) << i; + } +} + +void MoveResizeWindowTest::testPointerMoveEnd() +{ + // this test verifies that moving a window through pointer only ends if all buttons are released + using namespace KWayland::Client; + + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + // let's render + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + + QVERIFY(window); + QCOMPARE(window, workspace()->activeWindow()); + QVERIFY(!window->isInteractiveMove()); + + // let's trigger the left button + quint32 timestamp = 1; + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + QVERIFY(!window->isInteractiveMove()); + workspace()->slotWindowMove(); + QVERIFY(window->isInteractiveMove()); + + // let's press another button + QFETCH(int, additionalButton); + Test::pointerButtonPressed(additionalButton, timestamp++); + QVERIFY(window->isInteractiveMove()); + + // release the left button, should still have the window moving + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + QVERIFY(window->isInteractiveMove()); + + // but releasing the other button should now end moving + Test::pointerButtonReleased(additionalButton, timestamp++); + QVERIFY(!window->isInteractiveMove()); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} +void MoveResizeWindowTest::testClientSideMove() +{ + using namespace KWayland::Client; + Cursors::self()->mouse()->setPos(640, 512); + std::unique_ptr pointer(Test::waylandSeat()->createPointer()); + QSignalSpy pointerEnteredSpy(pointer.get(), &Pointer::entered); + QSignalSpy pointerLeftSpy(pointer.get(), &Pointer::left); + QSignalSpy buttonSpy(pointer.get(), &Pointer::buttonStateChanged); + + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // move pointer into center of geometry + const QRectF startGeometry = window->frameGeometry(); + Cursors::self()->mouse()->setPos(startGeometry.center()); + QVERIFY(pointerEnteredSpy.wait()); + QCOMPARE(pointerEnteredSpy.first().last().toPoint(), QPoint(50, 25)); + // simulate press + quint32 timestamp = 1; + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + QVERIFY(buttonSpy.wait()); + QSignalSpy moveStartSpy(window, &Window::clientStartUserMovedResized); + shellSurface->move(*Test::waylandSeat(), buttonSpy.first().first().value()); + QVERIFY(moveStartSpy.wait()); + QCOMPARE(window->isInteractiveMove(), true); + QVERIFY(pointerLeftSpy.wait()); + + // move a bit + QSignalSpy clientMoveStepSpy(window, &Window::clientStepUserMovedResized); + const QPointF startPoint = startGeometry.center(); + const int dragDistance = QApplication::startDragDistance(); + // Why? + Test::pointerMotion(startPoint + QPoint(dragDistance, dragDistance) + QPoint(6, 6), timestamp++); + QCOMPARE(clientMoveStepSpy.count(), 1); + + // and release again + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + QVERIFY(pointerEnteredSpy.wait()); + QCOMPARE(window->isInteractiveMove(), false); + QCOMPARE(window->frameGeometry(), startGeometry.translated(QPoint(dragDistance, dragDistance) + QPoint(6, 6))); + QCOMPARE(pointerEnteredSpy.last().last().toPoint(), QPoint(50, 25)); +} + +void MoveResizeWindowTest::testPlasmaShellSurfaceMovable_data() +{ + QTest::addColumn("role"); + QTest::addColumn("movable"); + QTest::addColumn("movableAcrossScreens"); + QTest::addColumn("resizable"); + + QTest::newRow("normal") << KWayland::Client::PlasmaShellSurface::Role::Normal << true << true << true; + QTest::newRow("desktop") << KWayland::Client::PlasmaShellSurface::Role::Desktop << false << false << false; + QTest::newRow("panel") << KWayland::Client::PlasmaShellSurface::Role::Panel << false << false << false; + QTest::newRow("osd") << KWayland::Client::PlasmaShellSurface::Role::OnScreenDisplay << false << false << false; +} + +void MoveResizeWindowTest::testPlasmaShellSurfaceMovable() +{ + // this test verifies that certain window types from PlasmaShellSurface are not moveable or resizable + using namespace KWayland::Client; + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + // and a PlasmaShellSurface + std::unique_ptr plasmaSurface(Test::waylandPlasmaShell()->createSurface(surface.get())); + QVERIFY(plasmaSurface != nullptr); + QFETCH(KWayland::Client::PlasmaShellSurface::Role, role); + plasmaSurface->setRole(role); + // let's render + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + + QVERIFY(window); + QTEST(window->isMovable(), "movable"); + QTEST(window->isMovableAcrossScreens(), "movableAcrossScreens"); + QTEST(window->isResizable(), "resizable"); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +struct XcbConnectionDeleter +{ + void operator()(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } +}; + +void MoveResizeWindowTest::testNetMove() +{ + // this test verifies that a move request for an X11 window through NET API works + // create an xcb window + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + 0, 0, 100, 100, + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, 0, 0); + xcb_icccm_size_hints_set_size(&hints, 1, 100, 100); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + // let's set a no-border + NETWinInfo winInfo(c.get(), windowId, rootWindow(), NET::WMWindowType, NET::Properties2()); + winInfo.setWindowType(NET::Override); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + const QRectF origGeo = window->frameGeometry(); + + // let's move the cursor outside the window + Cursors::self()->mouse()->setPos(workspace()->activeOutput()->geometry().center()); + QVERIFY(!origGeo.contains(Cursors::self()->mouse()->pos())); + + QSignalSpy moveStartSpy(window, &X11Window::clientStartUserMovedResized); + QSignalSpy moveEndSpy(window, &X11Window::clientFinishUserMovedResized); + QSignalSpy moveStepSpy(window, &X11Window::clientStepUserMovedResized); + QVERIFY(!workspace()->moveResizeWindow()); + + // use NETRootInfo to trigger a move request + NETRootInfo root(c.get(), NET::Properties()); + root.moveResizeRequest(windowId, origGeo.center().x(), origGeo.center().y(), NET::Move); + xcb_flush(c.get()); + + QVERIFY(moveStartSpy.wait()); + QCOMPARE(workspace()->moveResizeWindow(), window); + QVERIFY(window->isInteractiveMove()); + QCOMPARE(window->geometryRestore(), origGeo); + QCOMPARE(Cursors::self()->mouse()->pos(), origGeo.center()); + + // let's move a step + Cursors::self()->mouse()->setPos(Cursors::self()->mouse()->pos() + QPoint(10, 10)); + QCOMPARE(moveStepSpy.count(), 1); + QCOMPARE(moveStepSpy.first().last(), origGeo.translated(10, 10)); + + // let's cancel the move resize again through the net API + root.moveResizeRequest(windowId, window->frameGeometry().center().x(), window->frameGeometry().center().y(), NET::MoveResizeCancel); + xcb_flush(c.get()); + QVERIFY(moveEndSpy.wait()); + + // and destroy the window again + xcb_unmap_window(c.get(), windowId); + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + c.reset(); + + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + QVERIFY(windowClosedSpy.wait()); +} + +void MoveResizeWindowTest::testAdjustClientGeometryOfAutohidingX11Panel_data() +{ + QTest::addColumn("panelGeometry"); + QTest::addColumn("targetPoint"); + QTest::addColumn("expectedAdjustedPoint"); + QTest::addColumn("hideLocation"); + + QTest::newRow("top") << QRect(0, 0, 100, 20) << QPoint(50, 25) << QPoint(50, 20) << 0u; + QTest::newRow("bottom") << QRect(0, 1024 - 20, 100, 20) << QPoint(50, 1024 - 25 - 50) << QPoint(50, 1024 - 20 - 50) << 2u; + QTest::newRow("left") << QRect(0, 0, 20, 100) << QPoint(25, 50) << QPoint(20, 50) << 3u; + QTest::newRow("right") << QRect(1280 - 20, 0, 20, 100) << QPoint(1280 - 25 - 100, 50) << QPoint(1280 - 20 - 100, 50) << 1u; +} + +void MoveResizeWindowTest::testAdjustClientGeometryOfAutohidingX11Panel() +{ + // this test verifies that auto hiding panels are ignored when adjusting client geometry + // see BUG 365892 + + // first create our panel + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + + xcb_window_t windowId = xcb_generate_id(c.get()); + QFETCH(QRect, panelGeometry); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + panelGeometry.x(), panelGeometry.y(), panelGeometry.width(), panelGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, panelGeometry.x(), panelGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, panelGeometry.width(), panelGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + NETWinInfo winInfo(c.get(), windowId, rootWindow(), NET::WMWindowType, NET::Properties2()); + winInfo.setWindowType(NET::Dock); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *panel = windowCreatedSpy.first().first().value(); + QVERIFY(panel); + QCOMPARE(panel->window(), windowId); + QCOMPARE(panel->frameGeometry(), panelGeometry); + QVERIFY(panel->isDock()); + + // let's create a window + using namespace KWayland::Client; + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + auto testWindow = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + + QVERIFY(testWindow); + QVERIFY(testWindow->isMovable()); + // panel is not yet hidden, we should snap against it + QFETCH(QPoint, targetPoint); + QTEST(Workspace::self()->adjustWindowPosition(testWindow, targetPoint, false).toPoint(), "expectedAdjustedPoint"); + + // now let's hide the panel + QSignalSpy panelHiddenSpy(panel, &Window::windowHidden); + QFETCH(quint32, hideLocation); + xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->kde_screen_edge_show, XCB_ATOM_CARDINAL, 32, 1, &hideLocation); + xcb_flush(c.get()); + QVERIFY(panelHiddenSpy.wait()); + + // now try to snap again + QCOMPARE(Workspace::self()->adjustWindowPosition(testWindow, targetPoint, false), targetPoint); + + // and destroy the panel again + xcb_unmap_window(c.get(), windowId); + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + c.reset(); + + QSignalSpy panelClosedSpy(panel, &X11Window::windowClosed); + QVERIFY(panelClosedSpy.wait()); + + // snap once more + QCOMPARE(Workspace::self()->adjustWindowPosition(testWindow, targetPoint, false), targetPoint); + + // and close + QSignalSpy windowClosedSpy(testWindow, &Window::windowClosed); + shellSurface.reset(); + surface.reset(); + QVERIFY(windowClosedSpy.wait()); +} + +void MoveResizeWindowTest::testAdjustClientGeometryOfAutohidingWaylandPanel_data() +{ + QTest::addColumn("panelGeometry"); + QTest::addColumn("targetPoint"); + QTest::addColumn("expectedAdjustedPoint"); + + QTest::newRow("top") << QRect(0, 0, 100, 20) << QPoint(50, 25) << QPoint(50, 20); + QTest::newRow("bottom") << QRect(0, 1024 - 20, 100, 20) << QPoint(50, 1024 - 25 - 50) << QPoint(50, 1024 - 20 - 50); + QTest::newRow("left") << QRect(0, 0, 20, 100) << QPoint(25, 50) << QPoint(20, 50); + QTest::newRow("right") << QRect(1280 - 20, 0, 20, 100) << QPoint(1280 - 25 - 100, 50) << QPoint(1280 - 20 - 100, 50); +} + +void MoveResizeWindowTest::testAdjustClientGeometryOfAutohidingWaylandPanel() +{ + // this test verifies that auto hiding panels are ignored when adjusting client geometry + // see BUG 365892 + + // first create our panel + using namespace KWayland::Client; + std::unique_ptr panelSurface(Test::createSurface()); + QVERIFY(panelSurface != nullptr); + std::unique_ptr panelShellSurface(Test::createXdgToplevelSurface(panelSurface.get())); + QVERIFY(panelShellSurface != nullptr); + std::unique_ptr plasmaSurface(Test::waylandPlasmaShell()->createSurface(panelSurface.get())); + QVERIFY(plasmaSurface != nullptr); + plasmaSurface->setRole(PlasmaShellSurface::Role::Panel); + plasmaSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::AutoHide); + QFETCH(QRect, panelGeometry); + plasmaSurface->setPosition(panelGeometry.topLeft()); + // let's render + auto panel = Test::renderAndWaitForShown(panelSurface.get(), panelGeometry.size(), Qt::blue); + QVERIFY(panel); + QCOMPARE(panel->frameGeometry(), panelGeometry); + QVERIFY(panel->isDock()); + + // let's create a window + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + auto testWindow = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + + QVERIFY(testWindow); + QVERIFY(testWindow->isMovable()); + // panel is not yet hidden, we should snap against it + QFETCH(QPoint, targetPoint); + QTEST(Workspace::self()->adjustWindowPosition(testWindow, targetPoint, false).toPoint(), "expectedAdjustedPoint"); + + // now let's hide the panel + QSignalSpy panelHiddenSpy(panel, &Window::windowHidden); + plasmaSurface->requestHideAutoHidingPanel(); + QVERIFY(panelHiddenSpy.wait()); + + // now try to snap again + QCOMPARE(Workspace::self()->adjustWindowPosition(testWindow, targetPoint, false), targetPoint); + + // and destroy the panel again + QSignalSpy panelClosedSpy(panel, &Window::windowClosed); + plasmaSurface.reset(); + panelShellSurface.reset(); + panelSurface.reset(); + QVERIFY(panelClosedSpy.wait()); + + // snap once more + QCOMPARE(Workspace::self()->adjustWindowPosition(testWindow, targetPoint, false), targetPoint); + + // and close + QSignalSpy windowClosedSpy(testWindow, &Window::windowClosed); + shellSurface.reset(); + surface.reset(); + QVERIFY(windowClosedSpy.wait()); +} + +void MoveResizeWindowTest::testResizeForVirtualKeyboard_data() +{ + QTest::addColumn("windowRect"); + QTest::addColumn("keyboardRect"); + QTest::addColumn("resizedWindowRect"); + + QTest::newRow("standard") << QRect(100, 300, 500, 800) << QRect(0, 100, 1280, 500) << QRect(100, 0, 500, 100); + QTest::newRow("same size") << QRect(100, 300, 500, 500) << QRect(0, 600, 1280, 400) << QRect(100, 100, 500, 500); + QTest::newRow("smaller width") << QRect(100, 300, 500, 800) << QRect(300, 100, 100, 500) << QRect(100, 0, 500, 100); + QTest::newRow("no height change") << QRect(100, 300, 500, 500) << QRect(0, 900, 1280, 124) << QRect(100, 300, 500, 500); + QTest::newRow("no width change") << QRect(100, 300, 500, 500) << QRect(0, 400, 100, 500) << QRect(100, 300, 500, 500); +} + +void MoveResizeWindowTest::testResizeForVirtualKeyboard() +{ + using namespace KWayland::Client; + + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + + QFETCH(QRect, windowRect); + QFETCH(QRect, keyboardRect); + QFETCH(QRect, resizedWindowRect); + + // There are three things that may happen when the virtual keyboard geometry + // is set: We move the window to the top and resize it, we move the window + // but don't change its size (if the window is already small enough) or we + // do not change anything because the virtual keyboard does not overlap the + // window. We should verify that, for the first, we get both a position and + // a size change, for the second we only get a position change and for the + // last we get no changes. + bool sizeChange = windowRect.size() != resizedWindowRect.size(); + bool positionChange = windowRect.topLeft() != resizedWindowRect.topLeft(); + + // let's render + auto window = Test::renderAndWaitForShown(surface.get(), windowRect.size(), Qt::blue); + QVERIFY(window); + + // The client should receive a configure event upon becoming active. + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + surfaceConfigureRequestedSpy.clear(); + + window->move(windowRect.topLeft()); + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + + QCOMPARE(window->frameGeometry(), windowRect); + window->setVirtualKeyboardGeometry(keyboardRect); + + if (sizeChange) { + QVERIFY(surfaceConfigureRequestedSpy.wait()); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last()[0].toInt()); + } else { + QVERIFY(surfaceConfigureRequestedSpy.count() == 0); + } + // render at the new size + Test::render(surface.get(), toplevelConfigureRequestedSpy.last().first().toSize(), Qt::blue); + + if (positionChange || sizeChange) { + QVERIFY(frameGeometryChangedSpy.count() > 0 || frameGeometryChangedSpy.wait()); + frameGeometryChangedSpy.clear(); + } else { + QVERIFY(frameGeometryChangedSpy.count() == 0); + } + + QCOMPARE(window->frameGeometry(), resizedWindowRect); + window->setVirtualKeyboardGeometry(QRect()); + + if (sizeChange) { + QVERIFY(surfaceConfigureRequestedSpy.wait()); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last()[0].toInt()); + } + // render at the new size + Test::render(surface.get(), toplevelConfigureRequestedSpy.last().first().toSize(), Qt::blue); + + if (positionChange || sizeChange) { + QVERIFY(frameGeometryChangedSpy.count() > 0 || frameGeometryChangedSpy.wait()); + } else { + QVERIFY(frameGeometryChangedSpy.count() == 0); + } + + QCOMPARE(window->frameGeometry(), windowRect); +} + +void MoveResizeWindowTest::testResizeForVirtualKeyboardWithMaximize() +{ + using namespace KWayland::Client; + + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + + // let's render + auto window = Test::renderAndWaitForShown(surface.get(), QSize(500, 800), Qt::blue); + QVERIFY(window); + + // The client should receive a configure event upon becoming active. + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + + window->move(QPoint(100, 300)); + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + + QCOMPARE(window->frameGeometry(), QRect(100, 300, 500, 800)); + window->setVirtualKeyboardGeometry(QRect(0, 100, 1280, 500)); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last()[0].toInt()); + // render at the new size + Test::render(surface.get(), toplevelConfigureRequestedSpy.last().first().toSize(), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry(), QRect(100, 0, 500, 100)); + + window->setMaximize(true, true); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last()[0].toInt()); + Test::render(surface.get(), toplevelConfigureRequestedSpy.last().first().toSize(), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024)); + + window->setVirtualKeyboardGeometry(QRect()); + QVERIFY(!surfaceConfigureRequestedSpy.wait(10)); + + // render at the size of the configureRequested.. it won't have changed + Test::render(surface.get(), toplevelConfigureRequestedSpy.last().first().toSize(), Qt::blue); + QVERIFY(!frameGeometryChangedSpy.wait(10)); + + // Size will NOT be restored + QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024)); +} + +void MoveResizeWindowTest::testResizeForVirtualKeyboardWithFullScreen() +{ + using namespace KWayland::Client; + + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + + // let's render + auto window = Test::renderAndWaitForShown(surface.get(), QSize(500, 800), Qt::blue); + QVERIFY(window); + + // The client should receive a configure event upon becoming active. + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + + window->move(QPoint(100, 300)); + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + + QCOMPARE(window->frameGeometry(), QRect(100, 300, 500, 800)); + window->setVirtualKeyboardGeometry(QRect(0, 100, 1280, 500)); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last()[0].toInt()); + // render at the new size + Test::render(surface.get(), toplevelConfigureRequestedSpy.last().first().toSize(), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry(), QRect(100, 0, 500, 100)); + + window->setFullScreen(true, true); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last()[0].toInt()); + Test::render(surface.get(), toplevelConfigureRequestedSpy.last().first().toSize(), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024)); + + window->setVirtualKeyboardGeometry(QRect()); + QVERIFY(!surfaceConfigureRequestedSpy.wait(10)); + + // render at the size of the configureRequested.. it won't have changed + Test::render(surface.get(), toplevelConfigureRequestedSpy.last().first().toSize(), Qt::blue); + QVERIFY(!frameGeometryChangedSpy.wait(10)); + // Size will NOT be restored + QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024)); +} + +void MoveResizeWindowTest::testDestroyMoveClient() +{ + // This test verifies that active move operation gets finished when + // the associated client is destroyed. + + // Create the test client. + using namespace KWayland::Client; + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // Start moving the client. + QSignalSpy clientStartMoveResizedSpy(window, &Window::clientStartUserMovedResized); + QSignalSpy clientFinishUserMovedResizedSpy(window, &Window::clientFinishUserMovedResized); + + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QCOMPARE(window->isInteractiveMove(), false); + QCOMPARE(window->isInteractiveResize(), false); + workspace()->slotWindowMove(); + QCOMPARE(clientStartMoveResizedSpy.count(), 1); + QCOMPARE(workspace()->moveResizeWindow(), window); + QCOMPARE(window->isInteractiveMove(), true); + QCOMPARE(window->isInteractiveResize(), false); + + // Let's pretend that the client crashed. + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); + QCOMPARE(workspace()->moveResizeWindow(), nullptr); +} + +void MoveResizeWindowTest::testDestroyResizeClient() +{ + // This test verifies that active resize operation gets finished when + // the associated client is destroyed. + + // Create the test client. + using namespace KWayland::Client; + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // Start resizing the client. + QSignalSpy clientStartMoveResizedSpy(window, &Window::clientStartUserMovedResized); + QSignalSpy clientFinishUserMovedResizedSpy(window, &Window::clientFinishUserMovedResized); + + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QCOMPARE(window->isInteractiveMove(), false); + QCOMPARE(window->isInteractiveResize(), false); + workspace()->slotWindowResize(); + QCOMPARE(clientStartMoveResizedSpy.count(), 1); + QCOMPARE(workspace()->moveResizeWindow(), window); + QCOMPARE(window->isInteractiveMove(), false); + QCOMPARE(window->isInteractiveResize(), true); + + // Let's pretend that the client crashed. + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); + QCOMPARE(workspace()->moveResizeWindow(), nullptr); +} + +} + +WAYLANDTEST_MAIN(KWin::MoveResizeWindowTest) +#include "move_resize_window_test.moc" diff --git a/autotests/integration/nightcolor_test.cpp b/autotests/integration/nightcolor_test.cpp new file mode 100644 index 0000000..d80a25c --- /dev/null +++ b/autotests/integration/nightcolor_test.cpp @@ -0,0 +1,106 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2017 Roman Gilg + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/platform.h" +#include "plugins/nightcolor/constants.h" +#include "plugins/nightcolor/nightcolormanager.h" +#include "wayland_server.h" + +#include + +using namespace KWin; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_nightcolor-0"); + +class NightColorTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testConfigRead_data(); + void testConfigRead(); +}; + +void NightColorTest::initTestCase() +{ + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + + NightColorManager *manager = NightColorManager::self(); + QVERIFY(manager); +} + +void NightColorTest::init() +{ + QVERIFY(Test::setupWaylandConnection()); +} + +void NightColorTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void NightColorTest::testConfigRead_data() +{ + QTest::addColumn("active"); + QTest::addColumn("mode"); + + QTest::newRow("activeMode0") << true << 0; + QTest::newRow("activeMode1") << true << 1; + QTest::newRow("activeMode2") << true << 3; + QTest::newRow("notActiveMode2") << false << 2; + QTest::newRow("wrongData1") << false << 4; +} + +void NightColorTest::testConfigRead() +{ + QFETCH(bool, active); + QFETCH(int, mode); + + const bool activeDefault = true; + const int modeDefault = 0; + + KConfigGroup cfgGroup = kwinApp()->config()->group("NightColor"); + + cfgGroup.writeEntry("Active", activeDefault); + cfgGroup.writeEntry("Mode", modeDefault); + kwinApp()->config()->sync(); + NightColorManager *manager = NightColorManager::self(); + manager->reconfigure(); + + QCOMPARE(manager->isEnabled(), activeDefault); + QCOMPARE(manager->mode(), modeDefault); + + cfgGroup.writeEntry("Active", active); + cfgGroup.writeEntry("Mode", mode); + kwinApp()->config()->sync(); + + manager->reconfigure(); + + QCOMPARE(manager->isEnabled(), active); + if (mode > 3 || mode < 0) { + QCOMPARE(manager->mode(), 0); + } else { + QCOMPARE(manager->mode(), mode); + } +} + +WAYLANDTEST_MAIN(NightColorTest) +#include "nightcolor_test.moc" diff --git a/autotests/integration/no_global_shortcuts_test.cpp b/autotests/integration/no_global_shortcuts_test.cpp new file mode 100644 index 0000000..0a48ade --- /dev/null +++ b/autotests/integration/no_global_shortcuts_test.cpp @@ -0,0 +1,268 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2018 Martin Flöser + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/platform.h" +#include "cursor.h" +#include "input.h" +#include "keyboard_input.h" +#include "screenedge.h" +#include "wayland_server.h" +#include "workspace.h" + +#include +#include + +#include + +#include + +using namespace KWin; +using namespace KWayland::Client; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_no_global_shortcuts-0"); +static const QString s_serviceName = QStringLiteral("org.kde.KWin.Test.ModifierOnlyShortcut"); +static const QString s_path = QStringLiteral("/Test"); + +Q_DECLARE_METATYPE(KWin::ElectricBorder) + +/** + * This test verifies the NoGlobalShortcuts initialization flag + */ +class NoGlobalShortcutsTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testTrigger_data(); + void testTrigger(); + void testKGlobalAccel(); + void testPointerShortcut(); + void testAxisShortcut_data(); + void testAxisShortcut(); + void testScreenEdge(); +}; + +class Target : public QObject +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.KWin.Test.ModifierOnlyShortcut") + +public: + Target(); + ~Target() override; + +public Q_SLOTS: + Q_SCRIPTABLE void shortcut(); + +Q_SIGNALS: + void shortcutTriggered(); +}; + +Target::Target() + : QObject() +{ + QDBusConnection::sessionBus().registerService(s_serviceName); + QDBusConnection::sessionBus().registerObject(s_path, s_serviceName, this, QDBusConnection::ExportScriptableSlots); +} + +Target::~Target() +{ + QDBusConnection::sessionBus().unregisterObject(s_path); + QDBusConnection::sessionBus().unregisterService(s_serviceName); +} + +void Target::shortcut() +{ + Q_EMIT shortcutTriggered(); +} + +void NoGlobalShortcutsTest::initTestCase() +{ + qRegisterMetaType("ElectricBorder"); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName, KWin::WaylandServer::InitializationFlag::NoGlobalShortcuts)); + + kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); + qputenv("KWIN_XKB_DEFAULT_KEYMAP", "1"); + qputenv("XKB_DEFAULT_RULES", "evdev"); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); +} + +void NoGlobalShortcutsTest::init() +{ + workspace()->setActiveOutput(QPoint(640, 512)); + KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void NoGlobalShortcutsTest::cleanup() +{ +} + +void NoGlobalShortcutsTest::testTrigger_data() +{ + QTest::addColumn("metaConfig"); + QTest::addColumn("altConfig"); + QTest::addColumn("controlConfig"); + QTest::addColumn("shiftConfig"); + QTest::addColumn("modifier"); + QTest::addColumn>("nonTriggeringMods"); + + const QStringList trigger = QStringList{s_serviceName, s_path, s_serviceName, QStringLiteral("shortcut")}; + const QStringList e = QStringList(); + + QTest::newRow("leftMeta") << trigger << e << e << e << KEY_LEFTMETA << QList{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT}; + QTest::newRow("rightMeta") << trigger << e << e << e << KEY_RIGHTMETA << QList{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT}; + QTest::newRow("leftAlt") << e << trigger << e << e << KEY_LEFTALT << QList{KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT}; + QTest::newRow("rightAlt") << e << trigger << e << e << KEY_RIGHTALT << QList{KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTSHIFT, KEY_RIGHTSHIFT}; + QTest::newRow("leftControl") << e << e << trigger << e << KEY_LEFTCTRL << QList{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTSHIFT, KEY_RIGHTSHIFT}; + QTest::newRow("rightControl") << e << e << trigger << e << KEY_RIGHTCTRL << QList{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTMETA, KEY_RIGHTMETA, KEY_LEFTSHIFT, KEY_RIGHTSHIFT}; + QTest::newRow("leftShift") << e << e << e << trigger << KEY_LEFTSHIFT << QList{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTMETA, KEY_RIGHTMETA}; + QTest::newRow("rightShift") << e << e << e << trigger << KEY_RIGHTSHIFT << QList{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTMETA, KEY_RIGHTMETA}; +} + +void NoGlobalShortcutsTest::testTrigger() +{ + // test based on ModifierOnlyShortcutTest::testTrigger + Target target; + QSignalSpy triggeredSpy(&target, &Target::shortcutTriggered); + + KConfigGroup group = kwinApp()->config()->group("ModifierOnlyShortcuts"); + QFETCH(QStringList, metaConfig); + QFETCH(QStringList, altConfig); + QFETCH(QStringList, shiftConfig); + QFETCH(QStringList, controlConfig); + group.writeEntry("Meta", metaConfig); + group.writeEntry("Alt", altConfig); + group.writeEntry("Shift", shiftConfig); + group.writeEntry("Control", controlConfig); + group.sync(); + workspace()->slotReconfigure(); + + // configured shortcut should trigger + quint32 timestamp = 1; + QFETCH(int, modifier); + Test::keyboardKeyPressed(modifier, timestamp++); + Test::keyboardKeyReleased(modifier, timestamp++); + QCOMPARE(triggeredSpy.count(), 0); + + // the other shortcuts should not trigger + QFETCH(QList, nonTriggeringMods); + for (auto it = nonTriggeringMods.constBegin(), end = nonTriggeringMods.constEnd(); it != end; it++) { + Test::keyboardKeyPressed(*it, timestamp++); + Test::keyboardKeyReleased(*it, timestamp++); + QCOMPARE(triggeredSpy.count(), 0); + } +} + +void NoGlobalShortcutsTest::testKGlobalAccel() +{ + std::unique_ptr action(new QAction(nullptr)); + action->setProperty("componentName", QStringLiteral(KWIN_NAME)); + action->setObjectName(QStringLiteral("globalshortcuts-test-meta-shift-w")); + QSignalSpy triggeredSpy(action.get(), &QAction::triggered); + KGlobalAccel::self()->setShortcut(action.get(), QList{Qt::META | Qt::SHIFT | Qt::Key_W}, KGlobalAccel::NoAutoloading); + input()->registerShortcut(Qt::META | Qt::SHIFT | Qt::Key_W, action.get()); + + // press meta+shift+w + quint32 timestamp = 0; + Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++); + QCOMPARE(input()->keyboardModifiers(), Qt::MetaModifier); + Test::keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); + QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier | Qt::MetaModifier); + Test::keyboardKeyPressed(KEY_W, timestamp++); + Test::keyboardKeyReleased(KEY_W, timestamp++); + + // release meta+shift + Test::keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++); + + QVERIFY(!triggeredSpy.wait()); + QCOMPARE(triggeredSpy.count(), 0); +} + +void NoGlobalShortcutsTest::testPointerShortcut() +{ + // based on LockScreenTest::testPointerShortcut + std::unique_ptr action(new QAction(nullptr)); + QSignalSpy actionSpy(action.get(), &QAction::triggered); + input()->registerPointerShortcut(Qt::MetaModifier, Qt::LeftButton, action.get()); + + // try to trigger the shortcut + quint32 timestamp = 1; + Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++); + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + QCoreApplication::instance()->processEvents(); + QCOMPARE(actionSpy.count(), 0); + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++); + QCoreApplication::instance()->processEvents(); + QCOMPARE(actionSpy.count(), 0); +} + +void NoGlobalShortcutsTest::testAxisShortcut_data() +{ + QTest::addColumn("direction"); + QTest::addColumn("sign"); + + QTest::newRow("up") << Qt::Vertical << 1; + QTest::newRow("down") << Qt::Vertical << -1; + QTest::newRow("left") << Qt::Horizontal << 1; + QTest::newRow("right") << Qt::Horizontal << -1; +} + +void NoGlobalShortcutsTest::testAxisShortcut() +{ + // based on LockScreenTest::testAxisShortcut + std::unique_ptr action(new QAction(nullptr)); + QSignalSpy actionSpy(action.get(), &QAction::triggered); + QFETCH(Qt::Orientation, direction); + QFETCH(int, sign); + PointerAxisDirection axisDirection = PointerAxisUp; + if (direction == Qt::Vertical) { + axisDirection = sign > 0 ? PointerAxisUp : PointerAxisDown; + } else { + axisDirection = sign > 0 ? PointerAxisLeft : PointerAxisRight; + } + input()->registerAxisShortcut(Qt::MetaModifier, axisDirection, action.get()); + + // try to trigger the shortcut + quint32 timestamp = 1; + Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++); + if (direction == Qt::Vertical) { + Test::pointerAxisVertical(sign * 5.0, timestamp++); + } else { + Test::pointerAxisHorizontal(sign * 5.0, timestamp++); + } + QCoreApplication::instance()->processEvents(); + QCOMPARE(actionSpy.count(), 0); + Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++); + QCoreApplication::instance()->processEvents(); + QCOMPARE(actionSpy.count(), 0); +} + +void NoGlobalShortcutsTest::testScreenEdge() +{ + // based on LockScreenTest::testScreenEdge + QSignalSpy screenEdgeSpy(workspace()->screenEdges(), &ScreenEdges::approaching); + QCOMPARE(screenEdgeSpy.count(), 0); + + quint32 timestamp = 1; + Test::pointerMotion({5, 5}, timestamp++); + QCOMPARE(screenEdgeSpy.count(), 0); +} + +WAYLANDTEST_MAIN(NoGlobalShortcutsTest) +#include "no_global_shortcuts_test.moc" diff --git a/autotests/integration/outputchanges_test.cpp b/autotests/integration/outputchanges_test.cpp new file mode 100644 index 0000000..0c88c92 --- /dev/null +++ b/autotests/integration/outputchanges_test.cpp @@ -0,0 +1,454 @@ +/* + SPDX-FileCopyrightText: 2021 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/outputconfiguration.h" +#include "core/platform.h" +#include "cursor.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_output_changes-0"); + +class OutputChangesTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testWindowSticksToOutputAfterOutputIsDisabled(); + void testWindowSticksToOutputAfterAnotherOutputIsDisabled(); + void testWindowSticksToOutputAfterOutputIsMoved(); + void testWindowSticksToOutputAfterOutputsAreSwappedLeftToRight(); + void testWindowSticksToOutputAfterOutputsAreSwappedRightToLeft(); + + void testWindowRestoredAfterEnablingOutput(); + void testMaximizedWindowRestoredAfterEnablingOutput(); + void testFullScreenWindowRestoredAfterEnablingOutput(); + + void testWindowNotRestoredAfterMovingWindowAndEnablingOutput(); +}; + +void OutputChangesTest::initTestCase() +{ + qRegisterMetaType(); + + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); +} + +void OutputChangesTest::init() +{ + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + QVERIFY(Test::setupWaylandConnection()); + + workspace()->setActiveOutput(QPoint(640, 512)); + Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void OutputChangesTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void OutputChangesTest::testWindowSticksToOutputAfterOutputIsDisabled() +{ + auto outputs = kwinApp()->platform()->outputs(); + + // Create a window. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // Move the window to some predefined position so the test is more robust. + window->move(QPoint(42, 67)); + QCOMPARE(window->frameGeometry(), QRect(42, 67, 100, 50)); + + // Disable the output where the window is on. + OutputConfiguration config; + { + auto changeSet = config.changeSet(outputs[0]); + changeSet->enabled = false; + } + workspace()->applyOutputConfiguration(config); + + // The window will be sent to the second output, which is at (1280, 0). + QCOMPARE(window->frameGeometry(), QRect(1280 + 42, 0 + 67, 100, 50)); +} + +void OutputChangesTest::testWindowSticksToOutputAfterAnotherOutputIsDisabled() +{ + auto outputs = kwinApp()->platform()->outputs(); + + // Create a window. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // Move the window to the second output. + window->move(QPoint(1280 + 42, 67)); + QCOMPARE(window->frameGeometry(), QRect(1280 + 42, 67, 100, 50)); + + // Disable the first output. + OutputConfiguration config; + { + auto changeSet = config.changeSet(outputs[0]); + changeSet->enabled = false; + } + { + auto changeSet = config.changeSet(outputs[1]); + changeSet->pos = QPoint(0, 0); + } + workspace()->applyOutputConfiguration(config); + + // The position of the window relative to its output should remain the same. + QCOMPARE(window->frameGeometry(), QRect(42, 67, 100, 50)); +} + +void OutputChangesTest::testWindowSticksToOutputAfterOutputIsMoved() +{ + auto outputs = kwinApp()->platform()->outputs(); + + // Create a window. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // Move the window to some predefined position so the test is more robust. + window->move(QPoint(42, 67)); + QCOMPARE(window->frameGeometry(), QRect(42, 67, 100, 50)); + + // Disable the first output. + OutputConfiguration config; + { + auto changeSet = config.changeSet(outputs[0]); + changeSet->pos = QPoint(-10, 20); + } + workspace()->applyOutputConfiguration(config); + + // The position of the window relative to its output should remain the same. + QCOMPARE(window->frameGeometry(), QRect(-10 + 42, 20 + 67, 100, 50)); +} + +void OutputChangesTest::testWindowSticksToOutputAfterOutputsAreSwappedLeftToRight() +{ + // This test verifies that a window placed on the left monitor sticks + // to that monitor even after the monitors are swapped horizontally. + + const auto outputs = kwinApp()->platform()->outputs(); + + // Create a window. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // Move the window to the left output. + window->move(QPointF(0, 0)); + QCOMPARE(window->output(), outputs[0]); + QCOMPARE(window->frameGeometry(), QRectF(0, 0, 100, 50)); + + // Swap outputs. + OutputConfiguration config; + { + auto changeSet1 = config.changeSet(outputs[0]); + changeSet1->pos = QPoint(1280, 0); + auto changeSet2 = config.changeSet(outputs[1]); + changeSet2->pos = QPoint(0, 0); + } + workspace()->applyOutputConfiguration(config); + + // The window should be still on its original output. + QCOMPARE(window->output(), outputs[0]); + QCOMPARE(window->frameGeometry(), QRectF(1280, 0, 100, 50)); +} + +void OutputChangesTest::testWindowSticksToOutputAfterOutputsAreSwappedRightToLeft() +{ + // This test verifies that a window placed on the right monitor sticks + // to that monitor even after the monitors are swapped horizontally. + + const auto outputs = kwinApp()->platform()->outputs(); + + // Create a window. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // Move the window to the right output. + window->move(QPointF(1280, 0)); + QCOMPARE(window->output(), outputs[1]); + QCOMPARE(window->frameGeometry(), QRectF(1280, 0, 100, 50)); + + // Swap outputs. + OutputConfiguration config; + { + auto changeSet1 = config.changeSet(outputs[0]); + changeSet1->pos = QPoint(1280, 0); + auto changeSet2 = config.changeSet(outputs[1]); + changeSet2->pos = QPoint(0, 0); + } + workspace()->applyOutputConfiguration(config); + + // The window should be still on its original output. + QCOMPARE(window->output(), outputs[1]); + QCOMPARE(window->frameGeometry(), QRectF(0, 0, 100, 50)); +} + +void OutputChangesTest::testWindowRestoredAfterEnablingOutput() +{ + // This test verifies that a window will be moved back to its original output when it's hotplugged. + + const auto outputs = kwinApp()->platform()->outputs(); + + // Create a window. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // Move the window to the right output. + window->move(QPointF(1280 + 50, 100)); + QCOMPARE(window->output(), outputs[1]); + QCOMPARE(window->frameGeometry(), QRectF(1280 + 50, 100, 100, 50)); + + // Disable the right output. + OutputConfiguration config1; + { + auto changeSet = config1.changeSet(outputs[1]); + changeSet->enabled = false; + } + workspace()->applyOutputConfiguration(config1); + + // The window will be moved to the left monitor. + QCOMPARE(window->output(), outputs[0]); + QCOMPARE(window->frameGeometry(), QRectF(50, 100, 100, 50)); + + // Enable the right monitor. + OutputConfiguration config2; + { + auto changeSet = config2.changeSet(outputs[1]); + changeSet->enabled = true; + } + workspace()->applyOutputConfiguration(config2); + + // The window will be moved back to the right monitor. + QCOMPARE(window->output(), outputs[1]); + QCOMPARE(window->frameGeometry(), QRectF(1280 + 50, 100, 100, 50)); +} + +void OutputChangesTest::testWindowNotRestoredAfterMovingWindowAndEnablingOutput() +{ + // This test verifies that a window won't be moved to its original output when it's + // hotplugged because the window was moved manually by the user. + + const auto outputs = kwinApp()->platform()->outputs(); + + // Create a window. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // Move the window to the right output. + window->move(QPointF(1280 + 50, 100)); + QCOMPARE(window->output(), outputs[1]); + QCOMPARE(window->frameGeometry(), QRectF(1280 + 50, 100, 100, 50)); + + // Disable the right output. + OutputConfiguration config1; + { + auto changeSet = config1.changeSet(outputs[1]); + changeSet->enabled = false; + } + workspace()->applyOutputConfiguration(config1); + + // The window will be moved to the left monitor. + QCOMPARE(window->output(), outputs[0]); + QCOMPARE(window->frameGeometry(), QRectF(50, 100, 100, 50)); + + // Pretend that the user moved the window. + workspace()->slotWindowMove(); + QVERIFY(window->isInteractiveMove()); + window->keyPressEvent(Qt::Key_Right); + window->keyPressEvent(Qt::Key_Enter); + QCOMPARE(window->frameGeometry(), QRectF(58, 100, 100, 50)); + + // Enable the right monitor. + OutputConfiguration config2; + { + auto changeSet = config2.changeSet(outputs[1]); + changeSet->enabled = true; + } + workspace()->applyOutputConfiguration(config2); + + // The window is still on the left monitor because user manually moved it. + QCOMPARE(window->output(), outputs[0]); + QCOMPARE(window->frameGeometry(), QRectF(58, 100, 100, 50)); +} + +void OutputChangesTest::testMaximizedWindowRestoredAfterEnablingOutput() +{ + // This test verifies that a maximized window will be moved to its original + // output when it's re-enabled. + + const auto outputs = kwinApp()->platform()->outputs(); + + // Create a window. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // kwin will send a configure event with the actived state. + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + + // Move the window to the right monitor and make it maximized. + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + window->move(QPointF(1280 + 50, 100)); + window->maximize(MaximizeFull); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value(), QSize(1280, 1024)); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(1280, 1024), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry(), QRectF(1280, 0, 1280, 1024)); + QCOMPARE(window->moveResizeGeometry(), QRectF(1280, 0, 1280, 1024)); + QCOMPARE(window->output(), outputs[1]); + QCOMPARE(window->maximizeMode(), MaximizeFull); + QCOMPARE(window->requestedMaximizeMode(), MaximizeFull); + QCOMPARE(window->geometryRestore(), QRectF(1280 + 50, 100, 100, 50)); + + // Disable the right output. + OutputConfiguration config1; + { + auto changeSet = config1.changeSet(outputs[1]); + changeSet->enabled = false; + } + workspace()->applyOutputConfiguration(config1); + + // The window will be moved to the left monitor, the geometry restore will be updated too. + QCOMPARE(window->frameGeometry(), QRectF(0, 0, 1280, 1024)); + QCOMPARE(window->moveResizeGeometry(), QRectF(0, 0, 1280, 1024)); + QCOMPARE(window->output(), outputs[0]); + QCOMPARE(window->maximizeMode(), MaximizeFull); + QCOMPARE(window->requestedMaximizeMode(), MaximizeFull); + QCOMPARE(window->geometryRestore(), QRectF(50, 100, 100, 50)); + + // Enable the right monitor. + OutputConfiguration config2; + { + auto changeSet = config2.changeSet(outputs[1]); + changeSet->enabled = true; + } + workspace()->applyOutputConfiguration(config2); + + // The window will be moved back to the right monitor, the geometry restore will be updated too. + QCOMPARE(window->frameGeometry(), QRectF(1280, 0, 1280, 1024)); + QCOMPARE(window->moveResizeGeometry(), QRectF(1280, 0, 1280, 1024)); + QCOMPARE(window->output(), outputs[1]); + QCOMPARE(window->maximizeMode(), MaximizeFull); + QCOMPARE(window->requestedMaximizeMode(), MaximizeFull); + QCOMPARE(window->geometryRestore(), QRectF(1280 + 50, 100, 100, 50)); +} + +void OutputChangesTest::testFullScreenWindowRestoredAfterEnablingOutput() +{ + // This test verifies that a fullscreen window will be moved to its original + // output when it's re-enabled. + + const auto outputs = kwinApp()->platform()->outputs(); + + // Create a window. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // kwin will send a configure event with the actived state. + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + + // Move the window to the right monitor and make it fullscreen. + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + window->move(QPointF(1280 + 50, 100)); + window->setFullScreen(true); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value(), QSize(1280, 1024)); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(1280, 1024), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry(), QRectF(1280, 0, 1280, 1024)); + QCOMPARE(window->moveResizeGeometry(), QRectF(1280, 0, 1280, 1024)); + QCOMPARE(window->output(), outputs[1]); + QCOMPARE(window->isFullScreen(), true); + QCOMPARE(window->isRequestedFullScreen(), true); + QCOMPARE(window->fullscreenGeometryRestore(), QRectF(1280 + 50, 100, 100, 50)); + + // Disable the right output. + OutputConfiguration config1; + { + auto changeSet = config1.changeSet(outputs[1]); + changeSet->enabled = false; + } + workspace()->applyOutputConfiguration(config1); + + // The window will be moved to the left monitor, the geometry restore will be updated too. + QCOMPARE(window->frameGeometry(), QRectF(0, 0, 1280, 1024)); + QCOMPARE(window->moveResizeGeometry(), QRectF(0, 0, 1280, 1024)); + QCOMPARE(window->output(), outputs[0]); + QCOMPARE(window->isFullScreen(), true); + QCOMPARE(window->isRequestedFullScreen(), true); + QCOMPARE(window->fullscreenGeometryRestore(), QRectF(50, 100, 100, 50)); + + // Enable the right monitor. + OutputConfiguration config2; + { + auto changeSet = config2.changeSet(outputs[1]); + changeSet->enabled = true; + } + workspace()->applyOutputConfiguration(config2); + + // The window will be moved back to the right monitor, the geometry restore will be updated too. + QCOMPARE(window->frameGeometry(), QRectF(1280, 0, 1280, 1024)); + QCOMPARE(window->moveResizeGeometry(), QRectF(1280, 0, 1280, 1024)); + QCOMPARE(window->output(), outputs[1]); + QCOMPARE(window->isFullScreen(), true); + QCOMPARE(window->isRequestedFullScreen(), true); + QCOMPARE(window->fullscreenGeometryRestore(), QRectF(1280 + 50, 100, 100, 50)); +} + +} // namespace KWin + +WAYLANDTEST_MAIN(KWin::OutputChangesTest) +#include "outputchanges_test.moc" diff --git a/autotests/integration/placement_test.cpp b/autotests/integration/placement_test.cpp new file mode 100644 index 0000000..7dce5cc --- /dev/null +++ b/autotests/integration/placement_test.cpp @@ -0,0 +1,384 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2019 David Edmundson + SPDX-FileCopyrightText: 2019 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "placement.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include +#include +#include +#include + +using namespace KWin; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_placement-0"); + +struct PlaceWindowResult +{ + QSizeF initiallyConfiguredSize; + Test::XdgToplevel::States initiallyConfiguredStates; + QRectF finalGeometry; +}; + +class TestPlacement : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void init(); + void cleanup(); + void initTestCase(); + + void testPlaceSmart(); + void testPlaceZeroCornered(); + void testPlaceMaximized(); + void testPlaceMaximizedLeavesFullscreen(); + void testPlaceCentered(); + void testPlaceUnderMouse(); + void testPlaceCascaded(); + void testPlaceRandom(); + void testFullscreen(); + +private: + void setPlacementPolicy(PlacementPolicy policy); + /* + * Create a window and return relevant results for testing + * defaultSize is the buffer size to use if the compositor returns an empty size in the first configure + * event. + */ + std::pair> createAndPlaceWindow(const QSize &defaultSize); +}; + +void TestPlacement::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::PlasmaShell)); + + workspace()->setActiveOutput(QPoint(640, 512)); + KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void TestPlacement::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void TestPlacement::initTestCase() +{ + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); +} + +void TestPlacement::setPlacementPolicy(PlacementPolicy policy) +{ + auto group = kwinApp()->config()->group("Windows"); + group.writeEntry("Placement", Placement::policyToString(policy)); + group.sync(); + Workspace::self()->slotReconfigure(); +} + +std::pair> TestPlacement::createAndPlaceWindow(const QSize &defaultSize) +{ + PlaceWindowResult rc; + + // create a new window + std::unique_ptr surface = Test::createSurface(); + auto shellSurface = Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly, surface.get()); + + QSignalSpy toplevelConfigureRequestedSpy(shellSurface, &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + surfaceConfigureRequestedSpy.wait(); + + rc.initiallyConfiguredSize = toplevelConfigureRequestedSpy[0][0].toSize(); + rc.initiallyConfiguredStates = toplevelConfigureRequestedSpy[0][1].value(); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy[0][0].toUInt()); + + QSizeF size = rc.initiallyConfiguredSize; + + if (size.isEmpty()) { + size = defaultSize; + } + + auto window = Test::renderAndWaitForShown(surface.get(), size.toSize(), Qt::red); + + rc.finalGeometry = window->frameGeometry(); + return {rc, std::move(surface)}; +} + +void TestPlacement::testPlaceSmart() +{ + setPlacementPolicy(PlacementSmart); + + std::vector> surfaces; + QRegion usedArea; + + for (int i = 0; i < 4; i++) { + auto [windowPlacement, surface] = createAndPlaceWindow(QSize(600, 500)); + // smart placement shouldn't define a size on windows + QCOMPARE(windowPlacement.initiallyConfiguredSize, QSize(0, 0)); + QCOMPARE(windowPlacement.finalGeometry.size(), QSize(600, 500)); + + // exact placement isn't a defined concept that should be tested + // but the goal of smart placement is to make sure windows don't overlap until they need to + // 4 windows of 600, 500 should fit without overlap + QVERIFY(!usedArea.intersects(windowPlacement.finalGeometry.toRect())); + usedArea += windowPlacement.finalGeometry.toRect(); + surfaces.push_back(std::move(surface)); + } +} + +void TestPlacement::testPlaceZeroCornered() +{ + setPlacementPolicy(PlacementZeroCornered); + + std::vector> surfaces; + for (int i = 0; i < 4; i++) { + auto [windowPlacement, surface] = createAndPlaceWindow(QSize(600, 500)); + // smart placement shouldn't define a size on windows + QCOMPARE(windowPlacement.initiallyConfiguredSize, QSize(0, 0)); + // size should match our buffer + QCOMPARE(windowPlacement.finalGeometry.size(), QSize(600, 500)); + // and it should be in the corner + QCOMPARE(windowPlacement.finalGeometry.topLeft(), QPoint(0, 0)); + surfaces.push_back(std::move(surface)); + } +} + +void TestPlacement::testPlaceMaximized() +{ + setPlacementPolicy(PlacementMaximizing); + + // add a top panel + std::unique_ptr panelSurface(Test::createSurface()); + std::unique_ptr panelShellSurface(Test::createXdgToplevelSurface(panelSurface.get())); + std::unique_ptr plasmaSurface(Test::waylandPlasmaShell()->createSurface(panelSurface.get())); + plasmaSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::Panel); + plasmaSurface->setPosition(QPoint(0, 0)); + Test::renderAndWaitForShown(panelSurface.get(), QSize(1280, 20), Qt::blue); + + std::vector> surfaces; + + // all windows should be initially maximized with an initial configure size sent + for (int i = 0; i < 4; i++) { + auto [windowPlacement, surface] = createAndPlaceWindow(QSize(600, 500)); + QVERIFY(windowPlacement.initiallyConfiguredStates & Test::XdgToplevel::State::Maximized); + QCOMPARE(windowPlacement.initiallyConfiguredSize, QSize(1280, 1024 - 20)); + QCOMPARE(windowPlacement.finalGeometry, QRect(0, 20, 1280, 1024 - 20)); // under the panel + surfaces.push_back(std::move(surface)); + } +} + +void TestPlacement::testPlaceMaximizedLeavesFullscreen() +{ + setPlacementPolicy(PlacementMaximizing); + + // add a top panel + std::unique_ptr panelSurface(Test::createSurface()); + std::unique_ptr panelShellSurface(Test::createXdgToplevelSurface(panelSurface.get())); + std::unique_ptr plasmaSurface(Test::waylandPlasmaShell()->createSurface(panelSurface.get())); + plasmaSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::Panel); + plasmaSurface->setPosition(QPoint(0, 0)); + Test::renderAndWaitForShown(panelSurface.get(), QSize(1280, 20), Qt::blue); + + std::vector> surfaces; + + // all windows should be initially fullscreen with an initial configure size sent, despite the policy + for (int i = 0; i < 4; i++) { + std::unique_ptr surface = Test::createSurface(); + auto shellSurface = Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly, surface.get()); + shellSurface->set_fullscreen(nullptr); + QSignalSpy toplevelConfigureRequestedSpy(shellSurface, &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + + auto initiallyConfiguredSize = toplevelConfigureRequestedSpy[0][0].toSize(); + auto initiallyConfiguredStates = toplevelConfigureRequestedSpy[0][1].value(); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy[0][0].toUInt()); + + auto window = Test::renderAndWaitForShown(surface.get(), initiallyConfiguredSize, Qt::red); + + QVERIFY(initiallyConfiguredStates & Test::XdgToplevel::State::Fullscreen); + QCOMPARE(initiallyConfiguredSize, QSize(1280, 1024)); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024)); + + surfaces.push_back(std::move(surface)); + } +} + +void TestPlacement::testPlaceCentered() +{ + // This test verifies that Centered placement policy works. + + KConfigGroup group = kwinApp()->config()->group("Windows"); + group.writeEntry("Placement", Placement::policyToString(PlacementCentered)); + group.sync(); + workspace()->slotReconfigure(); + + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::red); + QVERIFY(window); + QCOMPARE(window->frameGeometry(), QRect(590, 487, 100, 50)); + + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void TestPlacement::testPlaceUnderMouse() +{ + // This test verifies that Under Mouse placement policy works. + + KConfigGroup group = kwinApp()->config()->group("Windows"); + group.writeEntry("Placement", Placement::policyToString(PlacementUnderMouse)); + group.sync(); + workspace()->slotReconfigure(); + + KWin::Cursors::self()->mouse()->setPos(QPoint(200, 300)); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), QPoint(200, 300)); + + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::red); + QVERIFY(window); + QCOMPARE(window->frameGeometry(), QRect(150, 275, 100, 50)); + + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void TestPlacement::testPlaceCascaded() +{ + // This test verifies that Cascaded placement policy works. + + KConfigGroup group = kwinApp()->config()->group("Windows"); + group.writeEntry("Placement", Placement::policyToString(PlacementCascade)); + group.sync(); + workspace()->slotReconfigure(); + + std::unique_ptr surface1(Test::createSurface()); + std::unique_ptr shellSurface1(Test::createXdgToplevelSurface(surface1.get())); + Window *window1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::red); + QVERIFY(window1); + QCOMPARE(window1->pos(), QPoint(0, 0)); + QCOMPARE(window1->size(), QSize(100, 50)); + + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + Window *window2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::blue); + QVERIFY(window2); + QCOMPARE(window2->pos(), window1->pos() + workspace()->cascadeOffset(window2)); + QCOMPARE(window2->size(), QSize(100, 50)); + + std::unique_ptr surface3(Test::createSurface()); + std::unique_ptr shellSurface3(Test::createXdgToplevelSurface(surface3.get())); + Window *window3 = Test::renderAndWaitForShown(surface3.get(), QSize(100, 50), Qt::green); + QVERIFY(window3); + QCOMPARE(window3->pos(), window2->pos() + workspace()->cascadeOffset(window3)); + QCOMPARE(window3->size(), QSize(100, 50)); + + shellSurface3.reset(); + QVERIFY(Test::waitForWindowDestroyed(window3)); + shellSurface2.reset(); + QVERIFY(Test::waitForWindowDestroyed(window2)); + shellSurface1.reset(); + QVERIFY(Test::waitForWindowDestroyed(window1)); +} + +void TestPlacement::testPlaceRandom() +{ + // This test verifies that Random placement policy works. + + KConfigGroup group = kwinApp()->config()->group("Windows"); + group.writeEntry("Placement", Placement::policyToString(PlacementRandom)); + group.sync(); + workspace()->slotReconfigure(); + + std::unique_ptr surface1(Test::createSurface()); + std::unique_ptr shellSurface1(Test::createXdgToplevelSurface(surface1.get())); + Window *window1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::red); + QVERIFY(window1); + QCOMPARE(window1->size(), QSize(100, 50)); + + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + Window *window2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::blue); + QVERIFY(window2); + QVERIFY(window2->pos() != window1->pos()); + QCOMPARE(window2->size(), QSize(100, 50)); + + std::unique_ptr surface3(Test::createSurface()); + std::unique_ptr shellSurface3(Test::createXdgToplevelSurface(surface3.get())); + Window *window3 = Test::renderAndWaitForShown(surface3.get(), QSize(100, 50), Qt::green); + QVERIFY(window3); + QVERIFY(window3->pos() != window1->pos()); + QVERIFY(window3->pos() != window2->pos()); + QCOMPARE(window3->size(), QSize(100, 50)); + + shellSurface3.reset(); + QVERIFY(Test::waitForWindowDestroyed(window3)); + shellSurface2.reset(); + QVERIFY(Test::waitForWindowDestroyed(window2)); + shellSurface1.reset(); + QVERIFY(Test::waitForWindowDestroyed(window1)); +} + +void TestPlacement::testFullscreen() +{ + const QList outputs = workspace()->outputs(); + + setPlacementPolicy(PlacementSmart); + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::red); + QVERIFY(window); + window->sendToOutput(outputs[0]); + + // Wait for the configure event with the activated state. + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + + window->setFullScreen(true); + + QSignalSpy geometryChangedSpy(window, &Window::frameGeometryChanged); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).toSize(), Qt::red); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry(), outputs[0]->geometry()); + + // this doesn't require a round trip, so should be immediate + window->sendToOutput(outputs[1]); + QCOMPARE(window->frameGeometry(), outputs[1]->geometry()); + QCOMPARE(geometryChangedSpy.count(), 2); +} + +WAYLANDTEST_MAIN(TestPlacement) +#include "placement_test.moc" diff --git a/autotests/integration/plasma_surface_test.cpp b/autotests/integration/plasma_surface_test.cpp new file mode 100644 index 0000000..f195af8 --- /dev/null +++ b/autotests/integration/plasma_surface_test.cpp @@ -0,0 +1,394 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "virtualdesktops.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace KWin; + +Q_DECLARE_METATYPE(KWin::Layer) + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_plasma_surface-0"); + +class PlasmaSurfaceTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testRoleOnAllDesktops_data(); + void testRoleOnAllDesktops(); + void testAcceptsFocus_data(); + void testAcceptsFocus(); + + void testPanelWindowsCanCover_data(); + void testPanelWindowsCanCover(); + void testOSDPlacement(); + void testOSDPlacementManualPosition(); + void testPanelTypeHasStrut_data(); + void testPanelTypeHasStrut(); + void testPanelActivate_data(); + void testPanelActivate(); + +private: + KWayland::Client::Compositor *m_compositor = nullptr; + KWayland::Client::PlasmaShell *m_plasmaShell = nullptr; +}; + +void PlasmaSurfaceTest::initTestCase() +{ + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); +} + +void PlasmaSurfaceTest::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::PlasmaShell)); + m_compositor = Test::waylandCompositor(); + m_plasmaShell = Test::waylandPlasmaShell(); + + KWin::Cursors::self()->mouse()->setPos(640, 512); +} + +void PlasmaSurfaceTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void PlasmaSurfaceTest::testRoleOnAllDesktops_data() +{ + QTest::addColumn("role"); + QTest::addColumn("expectedOnAllDesktops"); + + QTest::newRow("Desktop") << KWayland::Client::PlasmaShellSurface::Role::Desktop << true; + QTest::newRow("Panel") << KWayland::Client::PlasmaShellSurface::Role::Panel << true; + QTest::newRow("OSD") << KWayland::Client::PlasmaShellSurface::Role::OnScreenDisplay << true; + QTest::newRow("Normal") << KWayland::Client::PlasmaShellSurface::Role::Normal << false; + QTest::newRow("Notification") << KWayland::Client::PlasmaShellSurface::Role::Notification << true; + QTest::newRow("ToolTip") << KWayland::Client::PlasmaShellSurface::Role::ToolTip << true; + QTest::newRow("CriticalNotification") << KWayland::Client::PlasmaShellSurface::Role::CriticalNotification << true; + QTest::newRow("AppletPopup") << KWayland::Client::PlasmaShellSurface::Role::AppletPopup << true; +} + +void PlasmaSurfaceTest::testRoleOnAllDesktops() +{ + // this test verifies that a XdgShellClient is set on all desktops when the role changes + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + std::unique_ptr plasmaSurface(m_plasmaShell->createSurface(surface.get())); + QVERIFY(plasmaSurface != nullptr); + + // now render to map the window + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QCOMPARE(workspace()->activeWindow(), window); + + // currently the role is not yet set, so the window should not be on all desktops + QCOMPARE(window->isOnAllDesktops(), false); + + // now let's try to change that + QSignalSpy onAllDesktopsSpy(window, &Window::desktopChanged); + QFETCH(KWayland::Client::PlasmaShellSurface::Role, role); + plasmaSurface->setRole(role); + QFETCH(bool, expectedOnAllDesktops); + QCOMPARE(onAllDesktopsSpy.wait(), expectedOnAllDesktops); + QCOMPARE(window->isOnAllDesktops(), expectedOnAllDesktops); + + // let's create a second window where we init a little bit different + // first creating the PlasmaSurface then the Shell Surface + std::unique_ptr surface2(Test::createSurface()); + QVERIFY(surface2 != nullptr); + std::unique_ptr plasmaSurface2(m_plasmaShell->createSurface(surface2.get())); + QVERIFY(plasmaSurface2 != nullptr); + plasmaSurface2->setRole(role); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + QVERIFY(shellSurface2 != nullptr); + auto c2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::blue); + QVERIFY(c2); + QVERIFY(window != c2); + + QCOMPARE(c2->isOnAllDesktops(), expectedOnAllDesktops); +} + +void PlasmaSurfaceTest::testAcceptsFocus_data() +{ + QTest::addColumn("role"); + QTest::addColumn("wantsInput"); + QTest::addColumn("active"); + + QTest::newRow("Desktop") << KWayland::Client::PlasmaShellSurface::Role::Desktop << true << true; + QTest::newRow("Panel") << KWayland::Client::PlasmaShellSurface::Role::Panel << true << false; + QTest::newRow("OSD") << KWayland::Client::PlasmaShellSurface::Role::OnScreenDisplay << false << false; + QTest::newRow("Normal") << KWayland::Client::PlasmaShellSurface::Role::Normal << true << true; + QTest::newRow("Notification") << KWayland::Client::PlasmaShellSurface::Role::Notification << false << false; + QTest::newRow("ToolTip") << KWayland::Client::PlasmaShellSurface::Role::ToolTip << false << false; + QTest::newRow("CriticalNotification") << KWayland::Client::PlasmaShellSurface::Role::CriticalNotification << false << false; + QTest::newRow("AppletPopup") << KWayland::Client::PlasmaShellSurface::Role::AppletPopup << true << true; +} + +void PlasmaSurfaceTest::testAcceptsFocus() +{ + // this test verifies that some surface roles don't get focus + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + std::unique_ptr plasmaSurface(m_plasmaShell->createSurface(surface.get())); + QVERIFY(plasmaSurface != nullptr); + QFETCH(KWayland::Client::PlasmaShellSurface::Role, role); + plasmaSurface->setRole(role); + + // now render to map the window + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + + QVERIFY(window); + QTEST(window->wantsInput(), "wantsInput"); + QTEST(window->isActive(), "active"); +} + +void PlasmaSurfaceTest::testOSDPlacement() +{ + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + std::unique_ptr plasmaSurface(m_plasmaShell->createSurface(surface.get())); + QVERIFY(plasmaSurface != nullptr); + plasmaSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::OnScreenDisplay); + + // now render and map the window + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + + QVERIFY(window); + QCOMPARE(window->windowType(), NET::OnScreenDisplay); + QVERIFY(window->isOnScreenDisplay()); + QCOMPARE(window->frameGeometry(), QRect(1280 / 2 - 100 / 2, 2 * 1024 / 3 - 50 / 2, 100, 50)); + + // change the screen size + const QVector geometries{QRect(0, 0, 1280, 1024), QRect(1280, 0, 1280, 1024)}; + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", + Qt::DirectConnection, + Q_ARG(int, 2), + Q_ARG(QVector, geometries)); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), geometries[0]); + QCOMPARE(outputs[1]->geometry(), geometries[1]); + + QCOMPARE(window->frameGeometry(), QRect(1280 / 2 - 100 / 2, 2 * 1024 / 3 - 50 / 2, 100, 50)); + + // change size of window + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + Test::render(surface.get(), QSize(200, 100), Qt::red); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry(), QRect(1280 / 2 - 200 / 2, 2 * 1024 / 3 - 100 / 2, 200, 100)); +} + +void PlasmaSurfaceTest::testOSDPlacementManualPosition() +{ + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr plasmaSurface(m_plasmaShell->createSurface(surface.get())); + QVERIFY(plasmaSurface != nullptr); + plasmaSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::OnScreenDisplay); + + plasmaSurface->setPosition(QPoint(50, 70)); + + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + + // now render and map the window + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + + QVERIFY(window); + QVERIFY(!window->isPlaceable()); + QCOMPARE(window->windowType(), NET::OnScreenDisplay); + QVERIFY(window->isOnScreenDisplay()); + QCOMPARE(window->frameGeometry(), QRect(50, 70, 100, 50)); +} + +void PlasmaSurfaceTest::testPanelTypeHasStrut_data() +{ + QTest::addColumn("panelBehavior"); + QTest::addColumn("expectedStrut"); + QTest::addColumn("expectedMaxArea"); + QTest::addColumn("expectedLayer"); + + QTest::newRow("always visible - xdgWmBase") << KWayland::Client::PlasmaShellSurface::PanelBehavior::AlwaysVisible << true << QRectF(0, 50, 1280, 974) << KWin::DockLayer; + QTest::newRow("autohide - xdgWmBase") << KWayland::Client::PlasmaShellSurface::PanelBehavior::AutoHide << false << QRectF(0, 0, 1280, 1024) << KWin::AboveLayer; + QTest::newRow("windows can cover - xdgWmBase") << KWayland::Client::PlasmaShellSurface::PanelBehavior::WindowsCanCover << false << QRectF(0, 0, 1280, 1024) << KWin::NormalLayer; + QTest::newRow("windows go below - xdgWmBase") << KWayland::Client::PlasmaShellSurface::PanelBehavior::WindowsGoBelow << false << QRectF(0, 0, 1280, 1024) << KWin::AboveLayer; +} + +void PlasmaSurfaceTest::testPanelTypeHasStrut() +{ + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + std::unique_ptr plasmaSurface(m_plasmaShell->createSurface(surface.get())); + QVERIFY(plasmaSurface != nullptr); + plasmaSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::Panel); + plasmaSurface->setPosition(QPoint(0, 0)); + QFETCH(KWayland::Client::PlasmaShellSurface::PanelBehavior, panelBehavior); + plasmaSurface->setPanelBehavior(panelBehavior); + + // now render and map the window + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + + // the panel is on the first output and the current desktop + Output *output = workspace()->outputs().constFirst(); + VirtualDesktop *desktop = VirtualDesktopManager::self()->currentDesktop(); + + QVERIFY(window); + QCOMPARE(window->windowType(), NET::Dock); + QVERIFY(window->isDock()); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50)); + QTEST(window->hasStrut(), "expectedStrut"); + QTEST(workspace()->clientArea(MaximizeArea, output, desktop), "expectedMaxArea"); + QTEST(window->layer(), "expectedLayer"); +} + +void PlasmaSurfaceTest::testPanelWindowsCanCover_data() +{ + QTest::addColumn("panelGeometry"); + QTest::addColumn("windowGeometry"); + QTest::addColumn("triggerPoint"); + + QTest::newRow("top-full-edge") << QRect(0, 0, 1280, 30) << QRect(0, 0, 200, 300) << QPoint(100, 0); + QTest::newRow("top-left-edge") << QRect(0, 0, 1000, 30) << QRect(0, 0, 200, 300) << QPoint(100, 0); + QTest::newRow("top-right-edge") << QRect(280, 0, 1000, 30) << QRect(1000, 0, 200, 300) << QPoint(1000, 0); + QTest::newRow("bottom-full-edge") << QRect(0, 994, 1280, 30) << QRect(0, 724, 200, 300) << QPoint(100, 1023); + QTest::newRow("bottom-left-edge") << QRect(0, 994, 1000, 30) << QRect(0, 724, 200, 300) << QPoint(100, 1023); + QTest::newRow("bottom-right-edge") << QRect(280, 994, 1000, 30) << QRect(1000, 724, 200, 300) << QPoint(1000, 1023); + QTest::newRow("left-full-edge") << QRect(0, 0, 30, 1024) << QRect(0, 0, 200, 300) << QPoint(0, 100); + QTest::newRow("left-top-edge") << QRect(0, 0, 30, 800) << QRect(0, 0, 200, 300) << QPoint(0, 100); + QTest::newRow("left-bottom-edge") << QRect(0, 200, 30, 824) << QRect(0, 0, 200, 300) << QPoint(0, 250); + QTest::newRow("right-full-edge") << QRect(1250, 0, 30, 1024) << QRect(1080, 0, 200, 300) << QPoint(1279, 100); + QTest::newRow("right-top-edge") << QRect(1250, 0, 30, 800) << QRect(1080, 0, 200, 300) << QPoint(1279, 100); + QTest::newRow("right-bottom-edge") << QRect(1250, 200, 30, 824) << QRect(1080, 0, 200, 300) << QPoint(1279, 250); +} + +void PlasmaSurfaceTest::testPanelWindowsCanCover() +{ + // this test verifies the behavior of a panel with windows can cover + // triggering the screen edge should raise the panel. + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + std::unique_ptr plasmaSurface(m_plasmaShell->createSurface(surface.get())); + QVERIFY(plasmaSurface != nullptr); + plasmaSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::Panel); + QFETCH(QRect, panelGeometry); + plasmaSurface->setPosition(panelGeometry.topLeft()); + plasmaSurface->setPanelBehavior(KWayland::Client::PlasmaShellSurface::PanelBehavior::WindowsCanCover); + + // now render and map the window + auto panel = Test::renderAndWaitForShown(surface.get(), panelGeometry.size(), Qt::blue); + + // the panel is on the first output and the current desktop + Output *output = workspace()->outputs().constFirst(); + VirtualDesktop *desktop = VirtualDesktopManager::self()->currentDesktop(); + + QVERIFY(panel); + QCOMPARE(panel->windowType(), NET::Dock); + QVERIFY(panel->isDock()); + QCOMPARE(panel->frameGeometry(), panelGeometry); + QCOMPARE(panel->hasStrut(), false); + QCOMPARE(workspace()->clientArea(MaximizeArea, output, desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(panel->layer(), KWin::NormalLayer); + + // create a Window + std::unique_ptr surface2(Test::createSurface()); + QVERIFY(surface2 != nullptr); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + QVERIFY(shellSurface2 != nullptr); + + QFETCH(QRect, windowGeometry); + auto window = Test::renderAndWaitForShown(surface2.get(), windowGeometry.size(), Qt::red); + + QVERIFY(window); + QCOMPARE(window->windowType(), NET::Normal); + QVERIFY(window->isActive()); + QCOMPARE(window->layer(), KWin::NormalLayer); + window->move(windowGeometry.topLeft()); + QCOMPARE(window->frameGeometry(), windowGeometry); + + auto stackingOrder = workspace()->stackingOrder(); + QCOMPARE(stackingOrder.count(), 2); + QCOMPARE(stackingOrder.first(), panel); + QCOMPARE(stackingOrder.last(), window); + + QSignalSpy stackingOrderChangedSpy(workspace(), &Workspace::stackingOrderChanged); + // trigger screenedge + QFETCH(QPoint, triggerPoint); + KWin::Cursors::self()->mouse()->setPos(triggerPoint); + QVERIFY(stackingOrderChangedSpy.wait()); + QCOMPARE(stackingOrderChangedSpy.count(), 1); + stackingOrder = workspace()->stackingOrder(); + QCOMPARE(stackingOrder.count(), 2); + QCOMPARE(stackingOrder.first(), window); + QCOMPARE(stackingOrder.last(), panel); +} + +void PlasmaSurfaceTest::testPanelActivate_data() +{ + QTest::addColumn("wantsFocus"); + QTest::addColumn("active"); + + QTest::newRow("no focus") << false << false; + QTest::newRow("focus") << true << true; +} + +void PlasmaSurfaceTest::testPanelActivate() +{ + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + std::unique_ptr plasmaSurface(m_plasmaShell->createSurface(surface.get())); + QVERIFY(plasmaSurface != nullptr); + plasmaSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::Panel); + QFETCH(bool, wantsFocus); + plasmaSurface->setPanelTakesFocus(wantsFocus); + + auto panel = Test::renderAndWaitForShown(surface.get(), QSize(100, 200), Qt::blue); + + QVERIFY(panel); + QCOMPARE(panel->windowType(), NET::Dock); + QVERIFY(panel->isDock()); + QFETCH(bool, active); + QCOMPARE(panel->dockWantsInput(), active); + QCOMPARE(panel->isActive(), active); +} + +WAYLANDTEST_MAIN(PlasmaSurfaceTest) +#include "plasma_surface_test.moc" diff --git a/autotests/integration/plasmawindow_test.cpp b/autotests/integration/plasmawindow_test.cpp new file mode 100644 index 0000000..0591951 --- /dev/null +++ b/autotests/integration/plasmawindow_test.cpp @@ -0,0 +1,310 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "wayland/seat_interface.h" +#include "wayland_server.h" +#include "workspace.h" +#include "x11window.h" +#include + +#include +#include +#include +// screenlocker +#if KWIN_BUILD_SCREENLOCKER +#include +#endif + +#include +#include + +#include +#include + +using namespace KWayland::Client; + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_plasma-window-0"); + +class PlasmaWindowTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testCreateDestroyX11PlasmaWindow(); + void testInternalWindowNoPlasmaWindow(); + void testPopupWindowNoPlasmaWindow(); + void testLockScreenNoPlasmaWindow(); + void testDestroyedButNotUnmapped(); + +private: + PlasmaWindowManagement *m_windowManagement = nullptr; + KWayland::Client::Compositor *m_compositor = nullptr; +}; + +void PlasmaWindowTest::initTestCase() +{ + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); + setenv("QT_QPA_PLATFORM", "wayland", true); + setenv("QMLSCENE_DEVICE", "softwarecontext", true); +} + +void PlasmaWindowTest::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::WindowManagement)); + m_windowManagement = Test::waylandWindowManagement(); + m_compositor = Test::waylandCompositor(); + + workspace()->setActiveOutput(QPoint(640, 512)); + Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void PlasmaWindowTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void PlasmaWindowTest::testCreateDestroyX11PlasmaWindow() +{ + // this test verifies that a PlasmaWindow gets unmapped on Client side when an X11 window is destroyed + QSignalSpy plasmaWindowCreatedSpy(m_windowManagement, &PlasmaWindowManagement::windowCreated); + + // create an xcb window + struct XcbConnectionDeleter + { + void operator()(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } + }; + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QVERIFY(window->isDecorated()); + QVERIFY(window->isActive()); + // verify that it gets the keyboard focus + if (!window->surface()) { + // we don't have a surface yet, so focused keyboard surface if set is not ours + QVERIFY(!waylandServer()->seat()->focusedKeyboardSurface()); + QVERIFY(Test::waitForWaylandSurface(window)); + } + QCOMPARE(waylandServer()->seat()->focusedKeyboardSurface(), window->surface()); + + // now that should also give it to us on client side + QVERIFY(plasmaWindowCreatedSpy.wait()); + QCOMPARE(plasmaWindowCreatedSpy.count(), 1); + QCOMPARE(m_windowManagement->windows().count(), 1); + auto pw = m_windowManagement->windows().first(); + QCOMPARE(pw->geometry(), window->frameGeometry()); + QSignalSpy geometryChangedSpy(pw, &PlasmaWindow::geometryChanged); + + QSignalSpy unmappedSpy(m_windowManagement->windows().first(), &PlasmaWindow::unmapped); + QSignalSpy destroyedSpy(m_windowManagement->windows().first(), &QObject::destroyed); + + // now shade the window + const QRectF geoBeforeShade = window->frameGeometry(); + QVERIFY(geoBeforeShade.isValid()); + QVERIFY(!geoBeforeShade.isEmpty()); + workspace()->slotWindowShade(); + QVERIFY(window->isShade()); + QVERIFY(window->frameGeometry() != geoBeforeShade); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(pw->geometry(), window->frameGeometry()); + // and unshade again + workspace()->slotWindowShade(); + QVERIFY(!window->isShade()); + QCOMPARE(window->frameGeometry(), geoBeforeShade); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(pw->geometry(), geoBeforeShade); + + // and destroy the window again + xcb_unmap_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + QVERIFY(windowClosedSpy.wait()); + xcb_destroy_window(c.get(), windowId); + c.reset(); + + QVERIFY(unmappedSpy.wait()); + QCOMPARE(unmappedSpy.count(), 1); + + QVERIFY(destroyedSpy.wait()); +} + +class HelperWindow : public QRasterWindow +{ + Q_OBJECT +public: + HelperWindow(); + ~HelperWindow() override; + +protected: + void paintEvent(QPaintEvent *event) override; +}; + +HelperWindow::HelperWindow() + : QRasterWindow(nullptr) +{ +} + +HelperWindow::~HelperWindow() = default; + +void HelperWindow::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + QPainter p(this); + p.fillRect(0, 0, width(), height(), Qt::red); +} + +void PlasmaWindowTest::testInternalWindowNoPlasmaWindow() +{ + // this test verifies that an internal window is not added as a PlasmaWindow + QSignalSpy plasmaWindowCreatedSpy(m_windowManagement, &PlasmaWindowManagement::windowCreated); + HelperWindow win; + win.setGeometry(0, 0, 100, 100); + win.show(); + + QVERIFY(!plasmaWindowCreatedSpy.wait()); +} + +void PlasmaWindowTest::testPopupWindowNoPlasmaWindow() +{ + // this test verifies that a popup window is not added as a PlasmaWindow + QSignalSpy plasmaWindowCreatedSpy(m_windowManagement, &PlasmaWindowManagement::windowCreated); + + // first create the parent window + std::unique_ptr parentSurface(Test::createSurface()); + std::unique_ptr parentShellSurface(Test::createXdgToplevelSurface(parentSurface.get())); + Window *parentClient = Test::renderAndWaitForShown(parentSurface.get(), QSize(100, 50), Qt::blue); + QVERIFY(parentClient); + QVERIFY(plasmaWindowCreatedSpy.wait()); + QCOMPARE(plasmaWindowCreatedSpy.count(), 1); + + // now let's create a popup window for it + std::unique_ptr positioner(Test::createXdgPositioner()); + positioner->set_size(10, 10); + positioner->set_anchor_rect(0, 0, 10, 10); + positioner->set_anchor(Test::XdgPositioner::anchor_bottom_right); + positioner->set_gravity(Test::XdgPositioner::gravity_bottom_right); + std::unique_ptr popupSurface(Test::createSurface()); + std::unique_ptr popupShellSurface(Test::createXdgPopupSurface(popupSurface.get(), parentShellSurface->xdgSurface(), positioner.get())); + Window *popupWindow = Test::renderAndWaitForShown(popupSurface.get(), QSize(10, 10), Qt::blue); + QVERIFY(popupWindow); + QVERIFY(!plasmaWindowCreatedSpy.wait(100)); + QCOMPARE(plasmaWindowCreatedSpy.count(), 1); + + // let's destroy the windows + popupShellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(popupWindow)); + parentShellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(parentClient)); +} + +void PlasmaWindowTest::testLockScreenNoPlasmaWindow() +{ +#if KWIN_BUILD_SCREENLOCKER + // this test verifies that lock screen windows are not exposed to PlasmaWindow + QSignalSpy plasmaWindowCreatedSpy(m_windowManagement, &PlasmaWindowManagement::windowCreated); + + // this time we use a QSignalSpy on XdgShellClient as it'a a little bit more complex setup + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + // lock + ScreenLocker::KSldApp::self()->lock(ScreenLocker::EstablishLock::Immediate); + QVERIFY(windowAddedSpy.wait()); + QVERIFY(windowAddedSpy.first().first().value()->isLockScreen()); + // should not be sent to the window + QVERIFY(plasmaWindowCreatedSpy.isEmpty()); + QVERIFY(!plasmaWindowCreatedSpy.wait()); + + // fake unlock + QSignalSpy lockStateChangedSpy(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged); + const auto children = ScreenLocker::KSldApp::self()->children(); + for (auto it = children.begin(); it != children.end(); ++it) { + if (qstrcmp((*it)->metaObject()->className(), "LogindIntegration") != 0) { + continue; + } + QMetaObject::invokeMethod(*it, "requestUnlock"); + break; + } + QVERIFY(lockStateChangedSpy.wait()); + QVERIFY(!waylandServer()->isScreenLocked()); +#else + QSKIP("KWin was built without lockscreen support"); +#endif +} + +void PlasmaWindowTest::testDestroyedButNotUnmapped() +{ + // this test verifies that also when a ShellSurface gets destroyed without a prior unmap + // the PlasmaWindow gets destroyed on Client side + QSignalSpy plasmaWindowCreatedSpy(m_windowManagement, &PlasmaWindowManagement::windowCreated); + + // first create the parent window + std::unique_ptr parentSurface(Test::createSurface()); + std::unique_ptr parentShellSurface(Test::createXdgToplevelSurface(parentSurface.get())); + // map that window + Test::render(parentSurface.get(), QSize(100, 50), Qt::blue); + // this should create a plasma window + QVERIFY(plasmaWindowCreatedSpy.wait()); + QCOMPARE(plasmaWindowCreatedSpy.count(), 1); + auto window = plasmaWindowCreatedSpy.first().first().value(); + QVERIFY(window); + QSignalSpy destroyedSpy(window, &QObject::destroyed); + + // now destroy without an unmap + parentShellSurface.reset(); + parentSurface.reset(); + QVERIFY(destroyedSpy.wait()); +} + +} + +WAYLANDTEST_MAIN(KWin::PlasmaWindowTest) +#include "plasmawindow_test.moc" diff --git a/autotests/integration/platformcursor.cpp b/autotests/integration/platformcursor.cpp new file mode 100644 index 0000000..1467fba --- /dev/null +++ b/autotests/integration/platformcursor.cpp @@ -0,0 +1,60 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/platform.h" +#include "cursor.h" +#include "wayland_server.h" + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_platform_cursor-0"); + +class PlatformCursorTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void testPos(); +}; + +void PlatformCursorTest::initTestCase() +{ + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); +} + +void PlatformCursorTest::testPos() +{ + // this test verifies that the PlatformCursor of the QPA plugin forwards ::pos and ::setPos correctly + // that is QCursor should work just like KWin::Cursor + + // cursor should be centered on screen + QCOMPARE(Cursors::self()->mouse()->pos(), QPoint(639, 511)); + QCOMPARE(Cursors::self()->mouse()->pos(), QPoint(639, 511)); + + // let's set the pos through QCursor API + QCursor::setPos(QPoint(10, 10)); + QCOMPARE(Cursors::self()->mouse()->pos(), QPoint(10, 10)); + QCOMPARE(QCursor::pos(), QPoint(10, 10)); + + // and let's set the pos through Cursor API + QCursor::setPos(QPoint(20, 20)); + QCOMPARE(Cursors::self()->mouse()->pos(), QPoint(20, 20)); + QCOMPARE(QCursor::pos(), QPoint(20, 20)); +} + +} + +WAYLANDTEST_MAIN(KWin::PlatformCursorTest) +#include "platformcursor.moc" diff --git a/autotests/integration/pointer_constraints_test.cpp b/autotests/integration/pointer_constraints_test.cpp new file mode 100644 index 0000000..96a04ec --- /dev/null +++ b/autotests/integration/pointer_constraints_test.cpp @@ -0,0 +1,366 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "keyboard_input.h" +#include "pointer_input.h" +#include "wayland/seat_interface.h" +#include "wayland/surface_interface.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +using namespace KWin; +using namespace KWayland::Client; + +typedef std::function PointerFunc; +Q_DECLARE_METATYPE(PointerFunc) + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_pointer_constraints-0"); + +class TestPointerConstraints : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testConfinedPointer_data(); + void testConfinedPointer(); + void testLockedPointer(); + void testCloseWindowWithLockedPointer(); +}; + +void TestPointerConstraints::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + // set custom config which disables the OnScreenNotification + KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup group = config->group("OnScreenNotification"); + group.writeEntry(QStringLiteral("QmlPath"), QString("/does/not/exist.qml")); + group.sync(); + + kwinApp()->setConfig(config); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); +} + +void TestPointerConstraints::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::PointerConstraints)); + QVERIFY(Test::waitForWaylandPointer()); + + workspace()->setActiveOutput(QPoint(640, 512)); + KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void TestPointerConstraints::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void TestPointerConstraints::testConfinedPointer_data() +{ + QTest::addColumn("positionFunction"); + QTest::addColumn("xOffset"); + QTest::addColumn("yOffset"); + PointerFunc bottomLeft = [](const QRectF &rect) { + return rect.toRect().bottomLeft(); + }; + PointerFunc bottomRight = [](const QRectF &rect) { + return rect.toRect().bottomRight(); + }; + PointerFunc topRight = [](const QRectF &rect) { + return rect.toRect().topRight(); + }; + PointerFunc topLeft = [](const QRectF &rect) { + return rect.toRect().topLeft(); + }; + + QTest::newRow("XdgWmBase - bottomLeft") << bottomLeft << -1 << 1; + QTest::newRow("XdgWmBase - bottomRight") << bottomRight << 1 << 1; + QTest::newRow("XdgWmBase - topLeft") << topLeft << -1 << -1; + QTest::newRow("XdgWmBase - topRight") << topRight << 1 << -1; +} + +void TestPointerConstraints::testConfinedPointer() +{ + // this test sets up a Surface with a confined pointer + // simple interaction test to verify that the pointer gets confined + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + std::unique_ptr pointer(Test::waylandSeat()->createPointer()); + std::unique_ptr confinedPointer(Test::waylandPointerConstraints()->confinePointer(surface.get(), pointer.get(), nullptr, PointerConstraints::LifeTime::OneShot)); + QSignalSpy confinedSpy(confinedPointer.get(), &ConfinedPointer::confined); + QSignalSpy unconfinedSpy(confinedPointer.get(), &ConfinedPointer::unconfined); + + // now map the window + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::blue); + QVERIFY(window); + if (window->pos() == QPoint(0, 0)) { + window->move(QPoint(1, 1)); + } + QVERIFY(!window->frameGeometry().contains(KWin::Cursors::self()->mouse()->pos())); + + // now let's confine + QCOMPARE(input()->pointer()->isConstrained(), false); + KWin::Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + QCOMPARE(input()->pointer()->isConstrained(), true); + QVERIFY(confinedSpy.wait()); + + // picking a position outside the window geometry should not move pointer + QSignalSpy pointerPositionChangedSpy(input(), &InputRedirection::globalPointerChanged); + KWin::Cursors::self()->mouse()->setPos(QPoint(512, 512)); + QVERIFY(pointerPositionChangedSpy.isEmpty()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), window->frameGeometry().center()); + + // TODO: test relative motion + QFETCH(PointerFunc, positionFunction); + const QPointF position = positionFunction(window->frameGeometry()); + KWin::Cursors::self()->mouse()->setPos(position); + QCOMPARE(pointerPositionChangedSpy.count(), 1); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), position); + // moving one to right should not be possible + QFETCH(int, xOffset); + KWin::Cursors::self()->mouse()->setPos(position + QPoint(xOffset, 0)); + QCOMPARE(pointerPositionChangedSpy.count(), 1); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), position); + // moving one to bottom should not be possible + QFETCH(int, yOffset); + KWin::Cursors::self()->mouse()->setPos(position + QPoint(0, yOffset)); + QCOMPARE(pointerPositionChangedSpy.count(), 1); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), position); + + // modifier + click should be ignored + // first ensure the settings are ok + KConfigGroup group = kwinApp()->config()->group("MouseBindings"); + group.writeEntry("CommandAllKey", QStringLiteral("Meta")); + group.writeEntry("CommandAll1", "Move"); + group.writeEntry("CommandAll2", "Move"); + group.writeEntry("CommandAll3", "Move"); + group.writeEntry("CommandAllWheel", "change opacity"); + group.sync(); + workspace()->slotReconfigure(); + QCOMPARE(options->commandAllModifier(), Qt::MetaModifier); + QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove); + QCOMPARE(options->commandAll2(), Options::MouseUnrestrictedMove); + QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedMove); + + quint32 timestamp = 1; + Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + QVERIFY(!window->isInteractiveMove()); + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + + // set the opacity to 0.5 + window->setOpacity(0.5); + QCOMPARE(window->opacity(), 0.5); + + // pointer is confined so shortcut should not work + Test::pointerAxisVertical(-5, timestamp++); + QCOMPARE(window->opacity(), 0.5); + Test::pointerAxisVertical(5, timestamp++); + QCOMPARE(window->opacity(), 0.5); + + Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); + + // deactivate the window, this should unconfine + workspace()->activateWindow(nullptr); + QVERIFY(unconfinedSpy.wait()); + QCOMPARE(input()->pointer()->isConstrained(), false); + + // reconfine pointer (this time with persistent life time) + confinedPointer.reset(Test::waylandPointerConstraints()->confinePointer(surface.get(), pointer.get(), nullptr, PointerConstraints::LifeTime::Persistent)); + QSignalSpy confinedSpy2(confinedPointer.get(), &ConfinedPointer::confined); + QSignalSpy unconfinedSpy2(confinedPointer.get(), &ConfinedPointer::unconfined); + + // activate it again, this confines again + workspace()->activateWindow(static_cast(input()->pointer()->focus())); + QVERIFY(confinedSpy2.wait()); + QCOMPARE(input()->pointer()->isConstrained(), true); + + // deactivate the window one more time with the persistent life time constraint, this should unconfine + workspace()->activateWindow(nullptr); + QVERIFY(unconfinedSpy2.wait()); + QCOMPARE(input()->pointer()->isConstrained(), false); + // activate it again, this confines again + workspace()->activateWindow(static_cast(input()->pointer()->focus())); + QVERIFY(confinedSpy2.wait()); + QCOMPARE(input()->pointer()->isConstrained(), true); + + // create a second window and move it above our constrained window + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + auto c2 = Test::renderAndWaitForShown(surface2.get(), QSize(1280, 1024), Qt::blue); + QVERIFY(c2); + QVERIFY(unconfinedSpy2.wait()); + // and unmapping the second window should confine again + shellSurface2.reset(); + surface2.reset(); + QVERIFY(confinedSpy2.wait()); + + // let's set a region which results in unconfined + auto r = Test::waylandCompositor()->createRegion(QRegion(2, 2, 3, 3)); + confinedPointer->setRegion(r.get()); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(unconfinedSpy2.wait()); + QCOMPARE(input()->pointer()->isConstrained(), false); + // and set a full region again, that should confine + confinedPointer->setRegion(nullptr); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(confinedSpy2.wait()); + QCOMPARE(input()->pointer()->isConstrained(), true); + + // delete pointer confine + confinedPointer.reset(nullptr); + Test::flushWaylandConnection(); + + QSignalSpy constraintsChangedSpy(input()->pointer()->focus()->surface(), &KWaylandServer::SurfaceInterface::pointerConstraintsChanged); + QVERIFY(constraintsChangedSpy.wait()); + + // should be unconfined + QCOMPARE(input()->pointer()->isConstrained(), false); + + // confine again + confinedPointer.reset(Test::waylandPointerConstraints()->confinePointer(surface.get(), pointer.get(), nullptr, PointerConstraints::LifeTime::Persistent)); + QSignalSpy confinedSpy3(confinedPointer.get(), &ConfinedPointer::confined); + QVERIFY(confinedSpy3.wait()); + QCOMPARE(input()->pointer()->isConstrained(), true); + + // and now unmap + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); + QCOMPARE(input()->pointer()->isConstrained(), false); +} + +void TestPointerConstraints::testLockedPointer() +{ + // this test sets up a Surface with a locked pointer + // simple interaction test to verify that the pointer gets locked + // the various ways to unlock are not tested as that's already verified by testConfinedPointer + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + std::unique_ptr pointer(Test::waylandSeat()->createPointer()); + std::unique_ptr lockedPointer(Test::waylandPointerConstraints()->lockPointer(surface.get(), pointer.get(), nullptr, PointerConstraints::LifeTime::OneShot)); + QSignalSpy lockedSpy(lockedPointer.get(), &LockedPointer::locked); + QSignalSpy unlockedSpy(lockedPointer.get(), &LockedPointer::unlocked); + + // now map the window + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::blue); + QVERIFY(window); + QVERIFY(!window->frameGeometry().contains(KWin::Cursors::self()->mouse()->pos())); + + // now let's lock + QCOMPARE(input()->pointer()->isConstrained(), false); + KWin::Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), window->frameGeometry().center()); + QCOMPARE(input()->pointer()->isConstrained(), true); + QVERIFY(lockedSpy.wait()); + + // try to move the pointer + // TODO: add relative pointer + KWin::Cursors::self()->mouse()->setPos(window->frameGeometry().center() + QPoint(1, 1)); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), window->frameGeometry().center()); + + // deactivate the window, this should unlock + workspace()->activateWindow(nullptr); + QCOMPARE(input()->pointer()->isConstrained(), false); + QVERIFY(unlockedSpy.wait()); + + // moving cursor should be allowed again + KWin::Cursors::self()->mouse()->setPos(window->frameGeometry().center() + QPoint(1, 1)); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), window->frameGeometry().center() + QPoint(1, 1)); + + lockedPointer.reset(Test::waylandPointerConstraints()->lockPointer(surface.get(), pointer.get(), nullptr, PointerConstraints::LifeTime::Persistent)); + QSignalSpy lockedSpy2(lockedPointer.get(), &LockedPointer::locked); + + // activate the window again, this should lock again + workspace()->activateWindow(static_cast(input()->pointer()->focus())); + QVERIFY(lockedSpy2.wait()); + QCOMPARE(input()->pointer()->isConstrained(), true); + + // try to move the pointer + QCOMPARE(input()->pointer()->isConstrained(), true); + KWin::Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), window->frameGeometry().center() + QPoint(1, 1)); + + // delete pointer lock + lockedPointer.reset(nullptr); + Test::flushWaylandConnection(); + + QSignalSpy constraintsChangedSpy(input()->pointer()->focus()->surface(), &KWaylandServer::SurfaceInterface::pointerConstraintsChanged); + QVERIFY(constraintsChangedSpy.wait()); + + // moving cursor should be allowed again + QCOMPARE(input()->pointer()->isConstrained(), false); + KWin::Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), window->frameGeometry().center()); +} + +void TestPointerConstraints::testCloseWindowWithLockedPointer() +{ + // test case which verifies that the pointer gets unlocked when the window for it gets closed + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + std::unique_ptr pointer(Test::waylandSeat()->createPointer()); + std::unique_ptr lockedPointer(Test::waylandPointerConstraints()->lockPointer(surface.get(), pointer.get(), nullptr, PointerConstraints::LifeTime::OneShot)); + QSignalSpy lockedSpy(lockedPointer.get(), &LockedPointer::locked); + QSignalSpy unlockedSpy(lockedPointer.get(), &LockedPointer::unlocked); + + // now map the window + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 100), Qt::blue); + QVERIFY(window); + QVERIFY(!window->frameGeometry().contains(KWin::Cursors::self()->mouse()->pos())); + + // now let's lock + QCOMPARE(input()->pointer()->isConstrained(), false); + KWin::Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), window->frameGeometry().center()); + QCOMPARE(input()->pointer()->isConstrained(), true); + QVERIFY(lockedSpy.wait()); + + // close the window + shellSurface.reset(); + surface.reset(); + // this should result in unlocked + QVERIFY(unlockedSpy.wait()); + QCOMPARE(input()->pointer()->isConstrained(), false); +} + +WAYLANDTEST_MAIN(TestPointerConstraints) +#include "pointer_constraints_test.moc" diff --git a/autotests/integration/pointer_input.cpp b/autotests/integration/pointer_input.cpp new file mode 100644 index 0000000..9d3a871 --- /dev/null +++ b/autotests/integration/pointer_input.cpp @@ -0,0 +1,1846 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "deleted.h" +#include "effects.h" +#include "options.h" +#include "pointer_input.h" +#include "screenedge.h" +#include "screens.h" +#include "utils/xcursortheme.h" +#include "virtualdesktops.h" +#include "wayland/clientconnection.h" +#include "wayland/seat_interface.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace KWin +{ + +static PlatformCursorImage loadReferenceThemeCursor_helper(const KXcursorTheme &theme, + const QByteArray &name) +{ + const QVector sprites = theme.shape(name); + if (sprites.isEmpty()) { + return PlatformCursorImage(); + } + + return PlatformCursorImage(sprites.constFirst().data(), sprites.constFirst().hotspot()); +} + +static PlatformCursorImage loadReferenceThemeCursor(const QByteArray &name) +{ + const Cursor *pointerCursor = Cursors::self()->mouse(); + + const KXcursorTheme theme(pointerCursor->themeName(), pointerCursor->themeSize(), workspace()->screens()->maxScale()); + if (theme.isEmpty()) { + return PlatformCursorImage(); + } + + PlatformCursorImage platformCursorImage = loadReferenceThemeCursor_helper(theme, name); + if (!platformCursorImage.isNull()) { + return platformCursorImage; + } + + const QVector alternativeNames = Cursor::cursorAlternativeNames(name); + for (const QByteArray &alternativeName : alternativeNames) { + platformCursorImage = loadReferenceThemeCursor_helper(theme, alternativeName); + if (!platformCursorImage.isNull()) { + break; + } + } + + return platformCursorImage; +} + +static PlatformCursorImage loadReferenceThemeCursor(const CursorShape &shape) +{ + return loadReferenceThemeCursor(shape.name()); +} + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_pointer_input-0"); + +class PointerInputTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testWarpingUpdatesFocus(); + void testWarpingGeneratesPointerMotion(); + void testWarpingDuringFilter(); + void testWarpingBetweenWindows(); + void testUpdateFocusAfterScreenChange(); + void testUpdateFocusOnDecorationDestroy(); + void testModifierClickUnrestrictedMove_data(); + void testModifierClickUnrestrictedMove(); + void testModifierClickUnrestrictedFullscreenMove(); + void testModifierClickUnrestrictedMoveGlobalShortcutsDisabled(); + void testModifierScrollOpacity_data(); + void testModifierScrollOpacity(); + void testModifierScrollOpacityGlobalShortcutsDisabled(); + void testScrollAction(); + void testFocusFollowsMouse(); + void testMouseActionInactiveWindow_data(); + void testMouseActionInactiveWindow(); + void testMouseActionActiveWindow_data(); + void testMouseActionActiveWindow(); + void testCursorImage(); + void testEffectOverrideCursorImage(); + void testPopup(); + void testDecoCancelsPopup(); + void testWindowUnderCursorWhileButtonPressed(); + void testConfineToScreenGeometry_data(); + void testConfineToScreenGeometry(); + void testResizeCursor_data(); + void testResizeCursor(); + void testMoveCursor(); + void testHideShowCursor(); + void testDefaultInputRegion(); + void testEmptyInputRegion(); + +private: + void render(KWayland::Client::Surface *surface, const QSize &size = QSize(100, 50)); + KWayland::Client::Compositor *m_compositor = nullptr; + KWayland::Client::Seat *m_seat = nullptr; +}; + +void PointerInputTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); + + if (!QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("icons/DMZ-White/index.theme")).isEmpty()) { + qputenv("XCURSOR_THEME", QByteArrayLiteral("DMZ-White")); + } else { + // might be vanilla-dmz (e.g. Arch, FreeBSD) + qputenv("XCURSOR_THEME", QByteArrayLiteral("Vanilla-DMZ")); + } + qputenv("XCURSOR_SIZE", QByteArrayLiteral("24")); + qputenv("XKB_DEFAULT_RULES", "evdev"); + qputenv("KWIN_COMPOSE", QByteArrayLiteral("O2")); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); + setenv("QT_QPA_PLATFORM", "wayland", true); +} + +void PointerInputTest::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::Decoration | Test::AdditionalWaylandInterface::XdgDecorationV1)); + QVERIFY(Test::waitForWaylandPointer()); + m_compositor = Test::waylandCompositor(); + m_seat = Test::waylandSeat(); + + workspace()->setActiveOutput(QPoint(640, 512)); + Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void PointerInputTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void PointerInputTest::render(KWayland::Client::Surface *surface, const QSize &size) +{ + Test::render(surface, size, Qt::blue); + Test::flushWaylandConnection(); +} + +void PointerInputTest::testWarpingUpdatesFocus() +{ + // this test verifies that warping the pointer creates pointer enter and leave events + using namespace KWayland::Client; + // create pointer and signal spy for enter and leave signals + auto pointer = m_seat->createPointer(m_seat); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + QSignalSpy enteredSpy(pointer, &Pointer::entered); + QSignalSpy leftSpy(pointer, &Pointer::left); + + // create a window + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + render(surface.get()); + QVERIFY(windowAddedSpy.wait()); + Window *window = workspace()->activeWindow(); + QVERIFY(window); + + // currently there should not be a focused pointer surface + QVERIFY(!waylandServer()->seat()->focusedPointerSurface()); + QVERIFY(!pointer->enteredSurface()); + + // enter + Cursors::self()->mouse()->setPos(QPoint(25, 25)); + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.count(), 1); + QCOMPARE(enteredSpy.first().at(1).toPointF(), QPointF(25, 25)); + // window should have focus + QCOMPARE(pointer->enteredSurface(), surface.get()); + // also on the server + QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window->surface()); + + // and out again + Cursors::self()->mouse()->setPos(QPoint(250, 250)); + QVERIFY(leftSpy.wait()); + QCOMPARE(leftSpy.count(), 1); + // there should not be a focused pointer surface anymore + QVERIFY(!waylandServer()->seat()->focusedPointerSurface()); + QVERIFY(!pointer->enteredSurface()); +} + +void PointerInputTest::testWarpingGeneratesPointerMotion() +{ + // this test verifies that warping the pointer creates pointer motion events + using namespace KWayland::Client; + // create pointer and signal spy for enter and motion + auto pointer = m_seat->createPointer(m_seat); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + QSignalSpy enteredSpy(pointer, &Pointer::entered); + QSignalSpy movedSpy(pointer, &Pointer::motion); + + // create a window + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + render(surface.get()); + QVERIFY(windowAddedSpy.wait()); + Window *window = workspace()->activeWindow(); + QVERIFY(window); + + // enter + Test::pointerMotion(QPointF(25, 25), 1); + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.first().at(1).toPointF(), QPointF(25, 25)); + + // now warp + Cursors::self()->mouse()->setPos(QPoint(26, 26)); + QVERIFY(movedSpy.wait()); + QCOMPARE(movedSpy.count(), 1); + QCOMPARE(movedSpy.last().first().toPointF(), QPointF(26, 26)); +} + +void PointerInputTest::testWarpingDuringFilter() +{ + // this test verifies that pointer motion is handled correctly if + // the pointer gets warped during processing of input events + using namespace KWayland::Client; + + // create pointer + auto pointer = m_seat->createPointer(m_seat); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + QSignalSpy movedSpy(pointer, &Pointer::motion); + + // warp cursor into expected geometry + Cursors::self()->mouse()->setPos(10, 10); + + // create a window + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + render(surface.get()); + QVERIFY(windowAddedSpy.wait()); + Window *window = workspace()->activeWindow(); + QVERIFY(window); + + QCOMPARE(window->pos(), QPoint(0, 0)); + QVERIFY(window->frameGeometry().contains(Cursors::self()->mouse()->pos())); + + // is WindowView effect for top left screen edge loaded + QVERIFY(static_cast(effects)->isEffectLoaded("windowview")); + QVERIFY(movedSpy.isEmpty()); + quint32 timestamp = 0; + Test::pointerMotion(QPoint(0, 0), timestamp++); + // screen edges push back + QCOMPARE(Cursors::self()->mouse()->pos(), QPoint(1, 1)); + QVERIFY(movedSpy.wait()); + QCOMPARE(movedSpy.count(), 2); + QCOMPARE(movedSpy.at(0).first().toPoint(), QPoint(0, 0)); + QCOMPARE(movedSpy.at(1).first().toPoint(), QPoint(1, 1)); +} + +void PointerInputTest::testWarpingBetweenWindows() +{ + // This test verifies that the compositor will send correct events when the pointer + // leaves one window and enters another window. + + std::unique_ptr pointer(m_seat->createPointer(m_seat)); + QSignalSpy enteredSpy(pointer.get(), &KWayland::Client::Pointer::entered); + QSignalSpy leftSpy(pointer.get(), &KWayland::Client::Pointer::left); + QSignalSpy motionSpy(pointer.get(), &KWayland::Client::Pointer::motion); + + // create windows + std::unique_ptr surface1(Test::createSurface()); + std::unique_ptr shellSurface1(Test::createXdgToplevelSurface(surface1.get())); + auto window1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::cyan); + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + auto window2 = Test::renderAndWaitForShown(surface2.get(), QSize(200, 100), Qt::red); + + // place windows side by side + window1->move(QPoint(0, 0)); + window2->move(QPoint(100, 0)); + + quint32 timestamp = 0; + + // put the pointer at the center of the first window + Test::pointerMotion(window1->frameGeometry().center(), timestamp++); + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.count(), 1); + QCOMPARE(enteredSpy.last().at(1), QPoint(50, 25)); + QCOMPARE(leftSpy.count(), 0); + QCOMPARE(motionSpy.count(), 0); + QCOMPARE(pointer->enteredSurface(), surface1.get()); + + // put the pointer at the center of the second window + Test::pointerMotion(window2->frameGeometry().center(), timestamp++); + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.count(), 2); + QCOMPARE(enteredSpy.last().at(1).toPointF(), QPointF(100, 50)); + QCOMPARE(leftSpy.count(), 1); + QCOMPARE(motionSpy.count(), 0); + QCOMPARE(pointer->enteredSurface(), surface2.get()); +} + +void PointerInputTest::testUpdateFocusAfterScreenChange() +{ + // this test verifies that a pointer enter event is generated when the cursor changes to another + // screen due to removal of screen + using namespace KWayland::Client; + + // create pointer and signal spy for enter and motion + auto pointer = m_seat->createPointer(m_seat); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + QSignalSpy enteredSpy(pointer, &Pointer::entered); + QSignalSpy leftSpy(pointer, &Pointer::left); + + // create a window + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + render(surface.get(), QSize(1280, 1024)); + QVERIFY(windowAddedSpy.wait()); + Window *window = workspace()->activeWindow(); + QVERIFY(window); + QVERIFY(window->frameGeometry().contains(Cursors::self()->mouse()->pos())); + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.count(), 1); + + // move the cursor to the second screen + Cursors::self()->mouse()->setPos(1500, 300); + QVERIFY(!window->frameGeometry().contains(Cursors::self()->mouse()->pos())); + QVERIFY(leftSpy.wait()); + + // now let's remove the screen containing the cursor + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", + Qt::DirectConnection, + Q_ARG(int, 1), + Q_ARG(QVector, QVector{QRect(0, 0, 1280, 1024)})); + QCOMPARE(workspace()->outputs().count(), 1); + + // this should have warped the cursor + QCOMPARE(Cursors::self()->mouse()->pos(), QPoint(639, 511)); + QVERIFY(window->frameGeometry().contains(Cursors::self()->mouse()->pos())); + + // and we should get an enter event + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.count(), 2); +} + +void PointerInputTest::testUpdateFocusOnDecorationDestroy() +{ + // This test verifies that a maximized window gets it's pointer focus + // if decoration was focused and then destroyed on maximize with BorderlessMaximizedWindows option. + + // create pointer for focus tracking + auto pointer = m_seat->createPointer(m_seat); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + QSignalSpy buttonStateChangedSpy(pointer, &KWayland::Client::Pointer::buttonStateChanged); + + // Enable the borderless maximized windows option. + auto group = kwinApp()->config()->group("Windows"); + group.writeEntry("BorderlessMaximizedWindows", true); + group.sync(); + Workspace::self()->slotReconfigure(); + QCOMPARE(options->borderlessMaximizedWindows(), true); + + // Create the test window. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + std::unique_ptr decoration(Test::createXdgToplevelDecorationV1(shellSurface.get())); + + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + QSignalSpy decorationConfigureRequestedSpy(decoration.get(), &Test::XdgToplevelDecorationV1::configureRequested); + decoration->set_mode(Test::XdgToplevelDecorationV1::mode_server_side); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // Wait for the initial configure event. + Test::XdgToplevel::States states; + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(0, 0)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Map the window. + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(window->isActive()); + QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(window->isDecorated(), true); + + // We should receive a configure event when the window becomes active. + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Simulate decoration hover + quint32 timestamp = 0; + Test::pointerMotion(window->frameGeometry().topLeft(), timestamp++); + QVERIFY(input()->pointer()->decoration()); + + // Maximize when on decoration + workspace()->slotWindowMaximize(); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); + + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(1280, 1024), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(window->maximizeMode(), MaximizeFull); + QCOMPARE(window->requestedMaximizeMode(), MaximizeFull); + QCOMPARE(window->isDecorated(), false); + + // Window should have focus, BUG 411884 + QVERIFY(!input()->pointer()->decoration()); + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + QVERIFY(buttonStateChangedSpy.wait()); + QCOMPARE(pointer->enteredSurface(), surface.get()); + + // Destroy the window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void PointerInputTest::testModifierClickUnrestrictedMove_data() +{ + QTest::addColumn("modifierKey"); + QTest::addColumn("mouseButton"); + QTest::addColumn("modKey"); + QTest::addColumn("capsLock"); + + const QString alt = QStringLiteral("Alt"); + const QString meta = QStringLiteral("Meta"); + + QTest::newRow("Left Alt + Left Click") << KEY_LEFTALT << BTN_LEFT << alt << false; + QTest::newRow("Left Alt + Right Click") << KEY_LEFTALT << BTN_RIGHT << alt << false; + QTest::newRow("Left Alt + Middle Click") << KEY_LEFTALT << BTN_MIDDLE << alt << false; + QTest::newRow("Right Alt + Left Click") << KEY_RIGHTALT << BTN_LEFT << alt << false; + QTest::newRow("Right Alt + Right Click") << KEY_RIGHTALT << BTN_RIGHT << alt << false; + QTest::newRow("Right Alt + Middle Click") << KEY_RIGHTALT << BTN_MIDDLE << alt << false; + // now everything with meta + QTest::newRow("Left Meta + Left Click") << KEY_LEFTMETA << BTN_LEFT << meta << false; + QTest::newRow("Left Meta + Right Click") << KEY_LEFTMETA << BTN_RIGHT << meta << false; + QTest::newRow("Left Meta + Middle Click") << KEY_LEFTMETA << BTN_MIDDLE << meta << false; + QTest::newRow("Right Meta + Left Click") << KEY_RIGHTMETA << BTN_LEFT << meta << false; + QTest::newRow("Right Meta + Right Click") << KEY_RIGHTMETA << BTN_RIGHT << meta << false; + QTest::newRow("Right Meta + Middle Click") << KEY_RIGHTMETA << BTN_MIDDLE << meta << false; + + // and with capslock + QTest::newRow("Left Alt + Left Click/CapsLock") << KEY_LEFTALT << BTN_LEFT << alt << true; + QTest::newRow("Left Alt + Right Click/CapsLock") << KEY_LEFTALT << BTN_RIGHT << alt << true; + QTest::newRow("Left Alt + Middle Click/CapsLock") << KEY_LEFTALT << BTN_MIDDLE << alt << true; + QTest::newRow("Right Alt + Left Click/CapsLock") << KEY_RIGHTALT << BTN_LEFT << alt << true; + QTest::newRow("Right Alt + Right Click/CapsLock") << KEY_RIGHTALT << BTN_RIGHT << alt << true; + QTest::newRow("Right Alt + Middle Click/CapsLock") << KEY_RIGHTALT << BTN_MIDDLE << alt << true; + // now everything with meta + QTest::newRow("Left Meta + Left Click/CapsLock") << KEY_LEFTMETA << BTN_LEFT << meta << true; + QTest::newRow("Left Meta + Right Click/CapsLock") << KEY_LEFTMETA << BTN_RIGHT << meta << true; + QTest::newRow("Left Meta + Middle Click/CapsLock") << KEY_LEFTMETA << BTN_MIDDLE << meta << true; + QTest::newRow("Right Meta + Left Click/CapsLock") << KEY_RIGHTMETA << BTN_LEFT << meta << true; + QTest::newRow("Right Meta + Right Click/CapsLock") << KEY_RIGHTMETA << BTN_RIGHT << meta << true; + QTest::newRow("Right Meta + Middle Click/CapsLock") << KEY_RIGHTMETA << BTN_MIDDLE << meta << true; +} + +void PointerInputTest::testModifierClickUnrestrictedMove() +{ + // this test ensures that Alt+mouse button press triggers unrestricted move + using namespace KWayland::Client; + // create pointer and signal spy for button events + auto pointer = m_seat->createPointer(m_seat); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + QSignalSpy buttonSpy(pointer, &Pointer::buttonStateChanged); + + // first modify the config for this run + QFETCH(QString, modKey); + KConfigGroup group = kwinApp()->config()->group("MouseBindings"); + group.writeEntry("CommandAllKey", modKey); + group.writeEntry("CommandAll1", "Move"); + group.writeEntry("CommandAll2", "Move"); + group.writeEntry("CommandAll3", "Move"); + group.sync(); + workspace()->slotReconfigure(); + QCOMPARE(options->commandAllModifier(), modKey == QStringLiteral("Alt") ? Qt::AltModifier : Qt::MetaModifier); + QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove); + QCOMPARE(options->commandAll2(), Options::MouseUnrestrictedMove); + QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedMove); + + // create a window + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + render(surface.get()); + QVERIFY(windowAddedSpy.wait()); + Window *window = workspace()->activeWindow(); + QVERIFY(window); + + // move cursor on window + Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + + // simulate modifier+click + quint32 timestamp = 1; + QFETCH(bool, capsLock); + if (capsLock) { + Test::keyboardKeyPressed(KEY_CAPSLOCK, timestamp++); + } + QFETCH(int, modifierKey); + QFETCH(int, mouseButton); + Test::keyboardKeyPressed(modifierKey, timestamp++); + QVERIFY(!window->isInteractiveMove()); + Test::pointerButtonPressed(mouseButton, timestamp++); + QVERIFY(window->isInteractiveMove()); + // release modifier should not change it + Test::keyboardKeyReleased(modifierKey, timestamp++); + QVERIFY(window->isInteractiveMove()); + // but releasing the key should end move/resize + Test::pointerButtonReleased(mouseButton, timestamp++); + QVERIFY(!window->isInteractiveMove()); + if (capsLock) { + Test::keyboardKeyReleased(KEY_CAPSLOCK, timestamp++); + } + + // all of that should not have triggered button events on the surface + QCOMPARE(buttonSpy.count(), 0); + // also waiting shouldn't give us the event + QVERIFY(!buttonSpy.wait(100)); +} + +void PointerInputTest::testModifierClickUnrestrictedFullscreenMove() +{ + // this test ensures that Meta+mouse button press triggers unrestricted move for fullscreen windows + if (workspace()->outputs().size() < 2) { + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + } + + // first modify the config for this run + KConfigGroup group = kwinApp()->config()->group("MouseBindings"); + group.writeEntry("CommandAllKey", "Meta"); + group.writeEntry("CommandAll1", "Move"); + group.writeEntry("CommandAll2", "Move"); + group.writeEntry("CommandAll3", "Move"); + group.sync(); + workspace()->slotReconfigure(); + QCOMPARE(options->commandAllModifier(), Qt::MetaModifier); + QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove); + QCOMPARE(options->commandAll2(), Options::MouseUnrestrictedMove); + QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedMove); + + // create a window + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + shellSurface->set_fullscreen(nullptr); + QSignalSpy toplevelConfigureRequestedSpy(shellSurface, &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Window *window = Test::renderAndWaitForShown(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value(), Qt::blue); + QVERIFY(window); + QVERIFY(window->isFullScreen()); + + // move cursor on window + Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + + // simulate modifier+click + quint32 timestamp = 1; + Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++); + QVERIFY(!window->isInteractiveMove()); + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + QVERIFY(window->isInteractiveMove()); + // release modifier should not change it + Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++); + QVERIFY(window->isInteractiveMove()); + // but releasing the key should end move/resize + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + QVERIFY(!window->isInteractiveMove()); +} + +void PointerInputTest::testModifierClickUnrestrictedMoveGlobalShortcutsDisabled() +{ + // this test ensures that Alt+mouse button press triggers unrestricted move + using namespace KWayland::Client; + // create pointer and signal spy for button events + auto pointer = m_seat->createPointer(m_seat); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + QSignalSpy buttonSpy(pointer, &Pointer::buttonStateChanged); + + // first modify the config for this run + KConfigGroup group = kwinApp()->config()->group("MouseBindings"); + group.writeEntry("CommandAllKey", "Meta"); + group.writeEntry("CommandAll1", "Move"); + group.writeEntry("CommandAll2", "Move"); + group.writeEntry("CommandAll3", "Move"); + group.sync(); + workspace()->slotReconfigure(); + QCOMPARE(options->commandAllModifier(), Qt::MetaModifier); + QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove); + QCOMPARE(options->commandAll2(), Options::MouseUnrestrictedMove); + QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedMove); + + // create a window + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + render(surface.get()); + QVERIFY(windowAddedSpy.wait()); + Window *window = workspace()->activeWindow(); + QVERIFY(window); + + // disable global shortcuts + QVERIFY(!workspace()->globalShortcutsDisabled()); + workspace()->disableGlobalShortcutsForClient(true); + QVERIFY(workspace()->globalShortcutsDisabled()); + + // move cursor on window + Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + + // simulate modifier+click + quint32 timestamp = 1; + Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++); + QVERIFY(!window->isInteractiveMove()); + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + QVERIFY(!window->isInteractiveMove()); + // release modifier should not change it + Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++); + QVERIFY(!window->isInteractiveMove()); + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + + workspace()->disableGlobalShortcutsForClient(false); +} + +void PointerInputTest::testModifierScrollOpacity_data() +{ + QTest::addColumn("modifierKey"); + QTest::addColumn("modKey"); + QTest::addColumn("capsLock"); + + const QString alt = QStringLiteral("Alt"); + const QString meta = QStringLiteral("Meta"); + + QTest::newRow("Left Alt") << KEY_LEFTALT << alt << false; + QTest::newRow("Right Alt") << KEY_RIGHTALT << alt << false; + QTest::newRow("Left Meta") << KEY_LEFTMETA << meta << false; + QTest::newRow("Right Meta") << KEY_RIGHTMETA << meta << false; + QTest::newRow("Left Alt/CapsLock") << KEY_LEFTALT << alt << true; + QTest::newRow("Right Alt/CapsLock") << KEY_RIGHTALT << alt << true; + QTest::newRow("Left Meta/CapsLock") << KEY_LEFTMETA << meta << true; + QTest::newRow("Right Meta/CapsLock") << KEY_RIGHTMETA << meta << true; +} + +void PointerInputTest::testModifierScrollOpacity() +{ + // this test verifies that mod+wheel performs a window operation and does not + // pass the wheel to the window + using namespace KWayland::Client; + // create pointer and signal spy for button events + auto pointer = m_seat->createPointer(m_seat); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + QSignalSpy axisSpy(pointer, &Pointer::axisChanged); + + // first modify the config for this run + QFETCH(QString, modKey); + KConfigGroup group = kwinApp()->config()->group("MouseBindings"); + group.writeEntry("CommandAllKey", modKey); + group.writeEntry("CommandAllWheel", "change opacity"); + group.sync(); + workspace()->slotReconfigure(); + + // create a window + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + render(surface.get()); + QVERIFY(windowAddedSpy.wait()); + Window *window = workspace()->activeWindow(); + QVERIFY(window); + // set the opacity to 0.5 + window->setOpacity(0.5); + QCOMPARE(window->opacity(), 0.5); + + // move cursor on window + Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + + // simulate modifier+wheel + quint32 timestamp = 1; + QFETCH(bool, capsLock); + if (capsLock) { + Test::keyboardKeyPressed(KEY_CAPSLOCK, timestamp++); + } + QFETCH(int, modifierKey); + Test::keyboardKeyPressed(modifierKey, timestamp++); + Test::pointerAxisVertical(-5, timestamp++); + QCOMPARE(window->opacity(), 0.6); + Test::pointerAxisVertical(5, timestamp++); + QCOMPARE(window->opacity(), 0.5); + Test::keyboardKeyReleased(modifierKey, timestamp++); + if (capsLock) { + Test::keyboardKeyReleased(KEY_CAPSLOCK, timestamp++); + } + + // axis should have been filtered out + QCOMPARE(axisSpy.count(), 0); + QVERIFY(!axisSpy.wait(100)); +} + +void PointerInputTest::testModifierScrollOpacityGlobalShortcutsDisabled() +{ + // this test verifies that mod+wheel performs a window operation and does not + // pass the wheel to the window + using namespace KWayland::Client; + // create pointer and signal spy for button events + auto pointer = m_seat->createPointer(m_seat); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + QSignalSpy axisSpy(pointer, &Pointer::axisChanged); + + // first modify the config for this run + KConfigGroup group = kwinApp()->config()->group("MouseBindings"); + group.writeEntry("CommandAllKey", "Meta"); + group.writeEntry("CommandAllWheel", "change opacity"); + group.sync(); + workspace()->slotReconfigure(); + + // create a window + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + render(surface.get()); + QVERIFY(windowAddedSpy.wait()); + Window *window = workspace()->activeWindow(); + QVERIFY(window); + // set the opacity to 0.5 + window->setOpacity(0.5); + QCOMPARE(window->opacity(), 0.5); + + // move cursor on window + Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + + // disable global shortcuts + QVERIFY(!workspace()->globalShortcutsDisabled()); + workspace()->disableGlobalShortcutsForClient(true); + QVERIFY(workspace()->globalShortcutsDisabled()); + + // simulate modifier+wheel + quint32 timestamp = 1; + Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++); + Test::pointerAxisVertical(-5, timestamp++); + QCOMPARE(window->opacity(), 0.5); + Test::pointerAxisVertical(5, timestamp++); + QCOMPARE(window->opacity(), 0.5); + Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++); + + workspace()->disableGlobalShortcutsForClient(false); +} + +void PointerInputTest::testScrollAction() +{ + // this test verifies that scroll on inactive window performs a mouse action + using namespace KWayland::Client; + auto pointer = m_seat->createPointer(m_seat); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + QSignalSpy axisSpy(pointer, &Pointer::axisChanged); + + // first modify the config for this run + KConfigGroup group = kwinApp()->config()->group("MouseBindings"); + group.writeEntry("CommandWindowWheel", "activate and scroll"); + group.sync(); + workspace()->slotReconfigure(); + // create two windows + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + std::unique_ptr surface1 = Test::createSurface(); + QVERIFY(surface1); + Test::XdgToplevel *shellSurface1 = Test::createXdgToplevelSurface(surface1.get(), surface1.get()); + QVERIFY(shellSurface1); + render(surface1.get()); + QVERIFY(windowAddedSpy.wait()); + Window *window1 = workspace()->activeWindow(); + QVERIFY(window1); + std::unique_ptr surface2 = Test::createSurface(); + QVERIFY(surface2); + Test::XdgToplevel *shellSurface2 = Test::createXdgToplevelSurface(surface2.get(), surface2.get()); + QVERIFY(shellSurface2); + render(surface2.get()); + QVERIFY(windowAddedSpy.wait()); + Window *window2 = workspace()->activeWindow(); + QVERIFY(window2); + QVERIFY(window1 != window2); + + // move cursor to the inactive window + Cursors::self()->mouse()->setPos(window1->frameGeometry().center()); + + quint32 timestamp = 1; + QVERIFY(!window1->isActive()); + Test::pointerAxisVertical(5, timestamp++); + QVERIFY(window1->isActive()); + + // but also the wheel event should be passed to the window + QVERIFY(axisSpy.wait()); + + // we need to wait a little bit, otherwise the test crashes in effectshandler, needs fixing + QTest::qWait(100); +} + +void PointerInputTest::testFocusFollowsMouse() +{ + using namespace KWayland::Client; + // need to create a pointer, otherwise it doesn't accept focus + auto pointer = m_seat->createPointer(m_seat); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + // move cursor out of the way of first window to be created + Cursors::self()->mouse()->setPos(900, 900); + + // first modify the config for this run + KConfigGroup group = kwinApp()->config()->group("Windows"); + group.writeEntry("AutoRaise", true); + group.writeEntry("AutoRaiseInterval", 20); + group.writeEntry("DelayFocusInterval", 200); + group.writeEntry("FocusPolicy", "FocusFollowsMouse"); + group.sync(); + workspace()->slotReconfigure(); + // verify the settings + QCOMPARE(options->focusPolicy(), Options::FocusFollowsMouse); + QVERIFY(options->isAutoRaise()); + QCOMPARE(options->autoRaiseInterval(), 20); + QCOMPARE(options->delayFocusInterval(), 200); + + // create two windows + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + std::unique_ptr surface1 = Test::createSurface(); + QVERIFY(surface1); + Test::XdgToplevel *shellSurface1 = Test::createXdgToplevelSurface(surface1.get(), surface1.get()); + QVERIFY(shellSurface1); + render(surface1.get(), QSize(800, 800)); + QVERIFY(windowAddedSpy.wait()); + Window *window1 = workspace()->activeWindow(); + QVERIFY(window1); + std::unique_ptr surface2 = Test::createSurface(); + QVERIFY(surface2); + Test::XdgToplevel *shellSurface2 = Test::createXdgToplevelSurface(surface2.get(), surface2.get()); + QVERIFY(shellSurface2); + render(surface2.get(), QSize(800, 800)); + QVERIFY(windowAddedSpy.wait()); + Window *window2 = workspace()->activeWindow(); + QVERIFY(window2); + QVERIFY(window1 != window2); + QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window2); + // geometry of the two windows should be overlapping + QVERIFY(window1->frameGeometry().intersects(window2->frameGeometry())); + + // signal spies for active window changed and stacking order changed + QSignalSpy activeWindowChangedSpy(workspace(), &Workspace::windowActivated); + QSignalSpy stackingOrderChangedSpy(workspace(), &Workspace::stackingOrderChanged); + + QVERIFY(!window1->isActive()); + QVERIFY(window2->isActive()); + + // move on top of first window + QVERIFY(window1->frameGeometry().contains(10, 10)); + QVERIFY(!window2->frameGeometry().contains(10, 10)); + Cursors::self()->mouse()->setPos(10, 10); + QVERIFY(stackingOrderChangedSpy.wait()); + QCOMPARE(stackingOrderChangedSpy.count(), 1); + QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window1); + QTRY_VERIFY(window1->isActive()); + + // move on second window, but move away before active window change delay hits + Cursors::self()->mouse()->setPos(810, 810); + QVERIFY(stackingOrderChangedSpy.wait()); + QCOMPARE(stackingOrderChangedSpy.count(), 2); + QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window2); + Cursors::self()->mouse()->setPos(10, 10); + QVERIFY(!activeWindowChangedSpy.wait(250)); + QVERIFY(window1->isActive()); + QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window1); + // as we moved back on window 1 that should been raised in the mean time + QCOMPARE(stackingOrderChangedSpy.count(), 3); + + // quickly move on window 2 and back on window 1 should not raise window 2 + Cursors::self()->mouse()->setPos(810, 810); + Cursors::self()->mouse()->setPos(10, 10); + QVERIFY(!stackingOrderChangedSpy.wait(250)); +} + +void PointerInputTest::testMouseActionInactiveWindow_data() +{ + QTest::addColumn("button"); + + QTest::newRow("Left") << quint32(BTN_LEFT); + QTest::newRow("Middle") << quint32(BTN_MIDDLE); + QTest::newRow("Right") << quint32(BTN_RIGHT); +} + +void PointerInputTest::testMouseActionInactiveWindow() +{ + // this test performs the mouse button window action on an inactive window + // it should activate the window and raise it + using namespace KWayland::Client; + + // first modify the config for this run - disable FocusFollowsMouse + KConfigGroup group = kwinApp()->config()->group("Windows"); + group.writeEntry("FocusPolicy", "ClickToFocus"); + group.sync(); + group = kwinApp()->config()->group("MouseBindings"); + group.writeEntry("CommandWindow1", "Activate, raise and pass click"); + group.writeEntry("CommandWindow2", "Activate, raise and pass click"); + group.writeEntry("CommandWindow3", "Activate, raise and pass click"); + group.sync(); + workspace()->slotReconfigure(); + + // create two windows + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + std::unique_ptr surface1 = Test::createSurface(); + QVERIFY(surface1); + Test::XdgToplevel *shellSurface1 = Test::createXdgToplevelSurface(surface1.get(), surface1.get()); + QVERIFY(shellSurface1); + render(surface1.get(), QSize(800, 800)); + QVERIFY(windowAddedSpy.wait()); + Window *window1 = workspace()->activeWindow(); + QVERIFY(window1); + std::unique_ptr surface2 = Test::createSurface(); + QVERIFY(surface2); + Test::XdgToplevel *shellSurface2 = Test::createXdgToplevelSurface(surface2.get(), surface2.get()); + QVERIFY(shellSurface2); + render(surface2.get(), QSize(800, 800)); + QVERIFY(windowAddedSpy.wait()); + Window *window2 = workspace()->activeWindow(); + QVERIFY(window2); + QVERIFY(window1 != window2); + QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window2); + // geometry of the two windows should be overlapping + QVERIFY(window1->frameGeometry().intersects(window2->frameGeometry())); + + // signal spies for active window changed and stacking order changed + QSignalSpy activeWindowChangedSpy(workspace(), &Workspace::windowActivated); + QSignalSpy stackingOrderChangedSpy(workspace(), &Workspace::stackingOrderChanged); + + QVERIFY(!window1->isActive()); + QVERIFY(window2->isActive()); + + // move on top of first window + QVERIFY(window1->frameGeometry().contains(10, 10)); + QVERIFY(!window2->frameGeometry().contains(10, 10)); + Cursors::self()->mouse()->setPos(10, 10); + // no focus follows mouse + QVERIFY(!stackingOrderChangedSpy.wait(200)); + QVERIFY(stackingOrderChangedSpy.isEmpty()); + QVERIFY(activeWindowChangedSpy.isEmpty()); + QVERIFY(window2->isActive()); + // and click + quint32 timestamp = 1; + QFETCH(quint32, button); + Test::pointerButtonPressed(button, timestamp++); + // should raise window1 and activate it + QCOMPARE(stackingOrderChangedSpy.count(), 1); + QVERIFY(!activeWindowChangedSpy.isEmpty()); + QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window1); + QVERIFY(window1->isActive()); + QVERIFY(!window2->isActive()); + + // release again + Test::pointerButtonReleased(button, timestamp++); +} + +void PointerInputTest::testMouseActionActiveWindow_data() +{ + QTest::addColumn("clickRaise"); + QTest::addColumn("button"); + + for (quint32 i = BTN_LEFT; i < BTN_JOYSTICK; i++) { + QByteArray number = QByteArray::number(i, 16); + QTest::newRow(QByteArrayLiteral("click raise/").append(number).constData()) << true << i; + QTest::newRow(QByteArrayLiteral("no click raise/").append(number).constData()) << false << i; + } +} + +void PointerInputTest::testMouseActionActiveWindow() +{ + // this test verifies the mouse action performed on an active window + // for all buttons it should trigger a window raise depending on the + // click raise option + using namespace KWayland::Client; + // create a button spy - all clicks should be passed through + auto pointer = m_seat->createPointer(m_seat); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + QSignalSpy buttonSpy(pointer, &Pointer::buttonStateChanged); + + // adjust config for this run + QFETCH(bool, clickRaise); + KConfigGroup group = kwinApp()->config()->group("Windows"); + group.writeEntry("ClickRaise", clickRaise); + group.sync(); + workspace()->slotReconfigure(); + QCOMPARE(options->isClickRaise(), clickRaise); + + // create two windows + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + std::unique_ptr surface1 = Test::createSurface(); + QVERIFY(surface1); + Test::XdgToplevel *shellSurface1 = Test::createXdgToplevelSurface(surface1.get(), surface1.get()); + QVERIFY(shellSurface1); + render(surface1.get(), QSize(800, 800)); + QVERIFY(windowAddedSpy.wait()); + Window *window1 = workspace()->activeWindow(); + QVERIFY(window1); + QSignalSpy window1DestroyedSpy(window1, &QObject::destroyed); + std::unique_ptr surface2 = Test::createSurface(); + QVERIFY(surface2); + Test::XdgToplevel *shellSurface2 = Test::createXdgToplevelSurface(surface2.get(), surface2.get()); + QVERIFY(shellSurface2); + render(surface2.get(), QSize(800, 800)); + QVERIFY(windowAddedSpy.wait()); + Window *window2 = workspace()->activeWindow(); + QVERIFY(window2); + QVERIFY(window1 != window2); + QSignalSpy window2DestroyedSpy(window2, &QObject::destroyed); + QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window2); + // geometry of the two windows should be overlapping + QVERIFY(window1->frameGeometry().intersects(window2->frameGeometry())); + // lower the currently active window + workspace()->lowerWindow(window2); + QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window1); + + // signal spy for stacking order spy + QSignalSpy stackingOrderChangedSpy(workspace(), &Workspace::stackingOrderChanged); + + // move on top of second window + QVERIFY(!window1->frameGeometry().contains(900, 900)); + QVERIFY(window2->frameGeometry().contains(900, 900)); + Cursors::self()->mouse()->setPos(900, 900); + + // and click + quint32 timestamp = 1; + QFETCH(quint32, button); + Test::pointerButtonPressed(button, timestamp++); + QVERIFY(buttonSpy.wait()); + if (clickRaise) { + QCOMPARE(stackingOrderChangedSpy.count(), 1); + QTRY_COMPARE_WITH_TIMEOUT(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window2, 200); + } else { + QCOMPARE(stackingOrderChangedSpy.count(), 0); + QVERIFY(!stackingOrderChangedSpy.wait(100)); + QCOMPARE(workspace()->topWindowOnDesktop(VirtualDesktopManager::self()->currentDesktop()), window1); + } + + // release again + Test::pointerButtonReleased(button, timestamp++); + + surface1.reset(); + QVERIFY(window1DestroyedSpy.wait()); + surface2.reset(); + QVERIFY(window2DestroyedSpy.wait()); +} + +void PointerInputTest::testCursorImage() +{ + // this test verifies that the pointer image gets updated correctly from the client provided data + using namespace KWayland::Client; + // we need a pointer to get the enter event + auto pointer = m_seat->createPointer(m_seat); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + QSignalSpy enteredSpy(pointer, &Pointer::entered); + + // move cursor somewhere the new window won't open + auto cursor = Cursors::self()->mouse(); + cursor->setPos(800, 800); + auto p = input()->pointer(); + // at the moment it should be the fallback cursor + const QImage fallbackCursor = cursor->image(); + QVERIFY(!fallbackCursor.isNull()); + + // create a window + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + render(surface.get()); + QVERIFY(windowAddedSpy.wait()); + Window *window = workspace()->activeWindow(); + QVERIFY(window); + + // move cursor to center of window, this should first set a null pointer, so we still show old cursor + cursor->setPos(window->frameGeometry().center()); + QCOMPARE(p->focus(), window); + QCOMPARE(cursor->image(), fallbackCursor); + QVERIFY(enteredSpy.wait()); + + // create a cursor on the pointer + auto cursorSurface = Test::createSurface(); + QVERIFY(cursorSurface); + QSignalSpy cursorRenderedSpy(cursorSurface.get(), &KWayland::Client::Surface::frameRendered); + QImage red = QImage(QSize(10, 10), QImage::Format_ARGB32_Premultiplied); + red.fill(Qt::red); + cursorSurface->attachBuffer(Test::waylandShmPool()->createBuffer(red)); + cursorSurface->damage(QRect(0, 0, 10, 10)); + cursorSurface->commit(); + pointer->setCursor(cursorSurface.get(), QPoint(5, 5)); + QVERIFY(cursorRenderedSpy.wait()); + QCOMPARE(cursor->image(), red); + QCOMPARE(cursor->hotspot(), QPoint(5, 5)); + // change hotspot + pointer->setCursor(cursorSurface.get(), QPoint(6, 6)); + Test::flushWaylandConnection(); + QTRY_COMPARE(cursor->hotspot(), QPoint(6, 6)); + QCOMPARE(cursor->image(), red); + + // change the buffer + QImage blue = QImage(QSize(10, 10), QImage::Format_ARGB32_Premultiplied); + blue.fill(Qt::blue); + auto b = Test::waylandShmPool()->createBuffer(blue); + cursorSurface->attachBuffer(b); + cursorSurface->damage(QRect(0, 0, 10, 10)); + cursorSurface->commit(); + QVERIFY(cursorRenderedSpy.wait()); + QTRY_COMPARE(cursor->image(), blue); + QCOMPARE(cursor->hotspot(), QPoint(6, 6)); + + // scaled cursor + QImage blueScaled = QImage(QSize(20, 20), QImage::Format_ARGB32_Premultiplied); + blueScaled.setDevicePixelRatio(2); + blueScaled.fill(Qt::blue); + auto bs = Test::waylandShmPool()->createBuffer(blueScaled); + cursorSurface->attachBuffer(bs); + cursorSurface->setScale(2); + cursorSurface->damage(QRect(0, 0, 20, 20)); + cursorSurface->commit(); + QVERIFY(cursorRenderedSpy.wait()); + QTRY_COMPARE(cursor->image(), blueScaled); + QCOMPARE(cursor->hotspot(), QPoint(6, 6)); // surface-local (so not changed) + + // hide the cursor + pointer->setCursor(nullptr); + Test::flushWaylandConnection(); + QTRY_VERIFY(cursor->image().isNull()); + + // move cursor somewhere else, should reset to fallback cursor + Cursors::self()->mouse()->setPos(window->frameGeometry().bottomLeft() + QPoint(20, 20)); + QVERIFY(!p->focus()); + QVERIFY(!cursor->image().isNull()); + QCOMPARE(cursor->image(), fallbackCursor); +} + +class HelperEffect : public Effect +{ + Q_OBJECT +public: + HelperEffect() + { + } + ~HelperEffect() override + { + } +}; + +void PointerInputTest::testEffectOverrideCursorImage() +{ + // this test verifies the effect cursor override handling + using namespace KWayland::Client; + // we need a pointer to get the enter event and set a cursor + auto pointer = m_seat->createPointer(m_seat); + auto cursor = Cursors::self()->mouse(); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + QSignalSpy enteredSpy(pointer, &Pointer::entered); + QSignalSpy leftSpy(pointer, &Pointer::left); + // move cursor somewhere the new window won't open + cursor->setPos(800, 800); + // here we should have the fallback cursor + const QImage fallback = cursor->image(); + QVERIFY(!fallback.isNull()); + + // now let's create a window + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + render(surface.get()); + QVERIFY(windowAddedSpy.wait()); + Window *window = workspace()->activeWindow(); + QVERIFY(window); + + // and move cursor to the window + QVERIFY(!window->frameGeometry().contains(QPoint(800, 800))); + cursor->setPos(window->frameGeometry().center()); + QVERIFY(enteredSpy.wait()); + // cursor image should still be fallback + QCOMPARE(cursor->image(), fallback); + + // now create an effect and set an override cursor + std::unique_ptr effect(new HelperEffect); + effects->startMouseInterception(effect.get(), Qt::SizeAllCursor); + const QImage sizeAll = cursor->image(); + QVERIFY(!sizeAll.isNull()); + QVERIFY(sizeAll != fallback); + QVERIFY(leftSpy.wait()); + + // let's change to arrow cursor, this should be our fallback + effects->defineCursor(Qt::ArrowCursor); + QCOMPARE(cursor->image(), fallback); + + // back to size all + effects->defineCursor(Qt::SizeAllCursor); + QCOMPARE(cursor->image(), sizeAll); + + // move cursor outside the window area + Cursors::self()->mouse()->setPos(800, 800); + // and end the override, which should switch to fallback + effects->stopMouseInterception(effect.get()); + QCOMPARE(cursor->image(), fallback); + + // start mouse interception again + effects->startMouseInterception(effect.get(), Qt::SizeAllCursor); + QCOMPARE(cursor->image(), sizeAll); + + // move cursor to area of window + Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + // this should not result in an enter event + QVERIFY(!enteredSpy.wait(100)); + + // after ending the interception we should get an enter event + effects->stopMouseInterception(effect.get()); + QVERIFY(enteredSpy.wait()); + QVERIFY(cursor->image().isNull()); +} + +void PointerInputTest::testPopup() +{ + // this test validates the basic popup behavior + // a button press outside the window should dismiss the popup + + // first create a parent surface + using namespace KWayland::Client; + auto pointer = m_seat->createPointer(m_seat); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + QSignalSpy enteredSpy(pointer, &Pointer::entered); + QSignalSpy leftSpy(pointer, &Pointer::left); + QSignalSpy buttonStateChangedSpy(pointer, &Pointer::buttonStateChanged); + QSignalSpy motionSpy(pointer, &Pointer::motion); + + Cursors::self()->mouse()->setPos(800, 800); + + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + render(surface.get()); + QVERIFY(windowAddedSpy.wait()); + Window *window = workspace()->activeWindow(); + QVERIFY(window); + QCOMPARE(window->hasPopupGrab(), false); + // move pointer into window + QVERIFY(!window->frameGeometry().contains(QPoint(800, 800))); + Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + QVERIFY(enteredSpy.wait()); + // click inside window to create serial + quint32 timestamp = 0; + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + QVERIFY(buttonStateChangedSpy.wait()); + + // now create the popup surface + std::unique_ptr positioner(Test::createXdgPositioner()); + positioner->set_size(100, 50); + positioner->set_anchor_rect(0, 0, 80, 20); + positioner->set_anchor(Test::XdgPositioner::anchor_bottom_right); + positioner->set_gravity(Test::XdgPositioner::gravity_bottom_right); + std::unique_ptr popupSurface = Test::createSurface(); + QVERIFY(popupSurface); + Test::XdgPopup *popupShellSurface = Test::createXdgPopupSurface(popupSurface.get(), shellSurface->xdgSurface(), positioner.get()); + QVERIFY(popupShellSurface); + QSignalSpy doneReceivedSpy(popupShellSurface, &Test::XdgPopup::doneReceived); + popupShellSurface->grab(*Test::waylandSeat(), 0); // FIXME: Serial. + render(popupSurface.get(), QSize(100, 50)); + QVERIFY(windowAddedSpy.wait()); + auto popupWindow = windowAddedSpy.last().first().value(); + QVERIFY(popupWindow); + QVERIFY(popupWindow != window); + QCOMPARE(window, workspace()->activeWindow()); + QCOMPARE(popupWindow->transientFor(), window); + QCOMPARE(popupWindow->pos(), window->pos() + QPoint(80, 20)); + QCOMPARE(popupWindow->hasPopupGrab(), true); + + // let's move the pointer into the center of the window + Cursors::self()->mouse()->setPos(popupWindow->frameGeometry().center()); + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.count(), 2); + QCOMPARE(leftSpy.count(), 1); + QCOMPARE(pointer->enteredSurface(), popupSurface.get()); + + // let's move the pointer outside of the popup window + // this should not really change anything, it gets a leave event + Cursors::self()->mouse()->setPos(popupWindow->frameGeometry().bottomRight() + QPoint(2, 2)); + QVERIFY(leftSpy.wait()); + QCOMPARE(leftSpy.count(), 2); + QVERIFY(doneReceivedSpy.isEmpty()); + // now click, should trigger popupDone + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + QVERIFY(doneReceivedSpy.wait()); + Test::pointerButtonReleased(BTN_LEFT, timestamp++); +} + +void PointerInputTest::testDecoCancelsPopup() +{ + // this test verifies that clicking the window decoration of parent window + // cancels the popup + + // first create a parent surface + using namespace KWayland::Client; + auto pointer = m_seat->createPointer(m_seat); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + QSignalSpy enteredSpy(pointer, &Pointer::entered); + QSignalSpy leftSpy(pointer, &Pointer::left); + QSignalSpy buttonStateChangedSpy(pointer, &Pointer::buttonStateChanged); + QSignalSpy motionSpy(pointer, &Pointer::motion); + + Cursors::self()->mouse()->setPos(800, 800); + + // create a decorated window + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + std::unique_ptr decoration(Test::createXdgToplevelDecorationV1(shellSurface.get())); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + decoration->set_mode(Test::XdgToplevelDecorationV1::mode_server_side); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QCOMPARE(window->hasPopupGrab(), false); + QVERIFY(window->isDecorated()); + + // move pointer into window + QVERIFY(!window->frameGeometry().contains(QPoint(800, 800))); + Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + QVERIFY(enteredSpy.wait()); + // click inside window to create serial + quint32 timestamp = 0; + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + QVERIFY(buttonStateChangedSpy.wait()); + + // now create the popup surface + std::unique_ptr positioner(Test::createXdgPositioner()); + positioner->set_size(100, 50); + positioner->set_anchor_rect(0, 0, 80, 20); + positioner->set_anchor(Test::XdgPositioner::anchor_bottom_right); + positioner->set_gravity(Test::XdgPositioner::gravity_bottom_right); + std::unique_ptr popupSurface = Test::createSurface(); + QVERIFY(popupSurface); + Test::XdgPopup *popupShellSurface = Test::createXdgPopupSurface(popupSurface.get(), shellSurface->xdgSurface(), positioner.get()); + QVERIFY(popupShellSurface); + QSignalSpy doneReceivedSpy(popupShellSurface, &Test::XdgPopup::doneReceived); + popupShellSurface->grab(*Test::waylandSeat(), 0); // FIXME: Serial. + auto popupWindow = Test::renderAndWaitForShown(popupSurface.get(), QSize(100, 50), Qt::red); + QVERIFY(popupWindow); + QVERIFY(popupWindow != window); + QCOMPARE(window, workspace()->activeWindow()); + QCOMPARE(popupWindow->transientFor(), window); + QCOMPARE(popupWindow->pos(), window->pos() + window->clientPos() + QPoint(80, 20)); + QCOMPARE(popupWindow->hasPopupGrab(), true); + + // let's move the pointer into the center of the deco + Cursors::self()->mouse()->setPos(window->frameGeometry().center().x(), window->y() + (window->height() - window->clientSize().height()) / 2); + + Test::pointerButtonPressed(BTN_RIGHT, timestamp++); + QVERIFY(doneReceivedSpy.wait()); + Test::pointerButtonReleased(BTN_RIGHT, timestamp++); +} + +void PointerInputTest::testWindowUnderCursorWhileButtonPressed() +{ + // this test verifies that opening a window underneath the mouse cursor does not + // trigger a leave event if a button is pressed + // see BUG: 372876 + + // first create a parent surface + using namespace KWayland::Client; + auto pointer = m_seat->createPointer(m_seat); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + QSignalSpy enteredSpy(pointer, &Pointer::entered); + QSignalSpy leftSpy(pointer, &Pointer::left); + + Cursors::self()->mouse()->setPos(800, 800); + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), surface.get()); + QVERIFY(shellSurface); + render(surface.get()); + QVERIFY(windowAddedSpy.wait()); + Window *window = workspace()->activeWindow(); + QVERIFY(window); + + // move cursor over window + QVERIFY(!window->frameGeometry().contains(QPoint(800, 800))); + Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + QVERIFY(enteredSpy.wait()); + // click inside window + quint32 timestamp = 0; + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + + // now create a second window as transient + std::unique_ptr positioner(Test::createXdgPositioner()); + positioner->set_size(99, 49); + positioner->set_anchor_rect(0, 0, 1, 1); + positioner->set_anchor(Test::XdgPositioner::anchor_bottom_right); + positioner->set_gravity(Test::XdgPositioner::gravity_bottom_right); + std::unique_ptr popupSurface = Test::createSurface(); + QVERIFY(popupSurface); + Test::XdgPopup *popupShellSurface = Test::createXdgPopupSurface(popupSurface.get(), shellSurface->xdgSurface(), positioner.get()); + QVERIFY(popupShellSurface); + render(popupSurface.get(), QSize(99, 49)); + QVERIFY(windowAddedSpy.wait()); + auto popupWindow = windowAddedSpy.last().first().value(); + QVERIFY(popupWindow); + QVERIFY(popupWindow != window); + QVERIFY(window->frameGeometry().contains(Cursors::self()->mouse()->pos())); + QVERIFY(popupWindow->frameGeometry().contains(Cursors::self()->mouse()->pos())); + QVERIFY(!leftSpy.wait()); + + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + // now that the button is no longer pressed we should get the leave event + QVERIFY(leftSpy.wait()); + QCOMPARE(leftSpy.count(), 1); + QCOMPARE(enteredSpy.count(), 2); +} + +void PointerInputTest::testConfineToScreenGeometry_data() +{ + QTest::addColumn("startPos"); + QTest::addColumn("targetPos"); + QTest::addColumn("expectedPos"); + + // screen layout: + // + // +----------+----------+---------+ + // | left | top | right | + // +----------+----------+---------+ + // | bottom | + // +----------+ + // + + QTest::newRow("move top-left - left screen") << QPoint(640, 512) << QPoint(-100, -100) << QPoint(0, 0); + QTest::newRow("move top - left screen") << QPoint(640, 512) << QPoint(640, -100) << QPoint(640, 0); + QTest::newRow("move top-right - left screen") << QPoint(640, 512) << QPoint(1380, -100) << QPoint(1380, 0); + QTest::newRow("move right - left screen") << QPoint(640, 512) << QPoint(1380, 512) << QPoint(1380, 512); + QTest::newRow("move bottom-right - left screen") << QPoint(640, 512) << QPoint(1380, 1124) << QPoint(1380, 1124); + QTest::newRow("move bottom - left screen") << QPoint(640, 512) << QPoint(640, 1124) << QPoint(640, 1023); + QTest::newRow("move bottom-left - left screen") << QPoint(640, 512) << QPoint(-100, 1124) << QPoint(0, 1023); + QTest::newRow("move left - left screen") << QPoint(640, 512) << QPoint(-100, 512) << QPoint(0, 512); + + QTest::newRow("move top-left - top screen") << QPoint(1920, 512) << QPoint(1180, -100) << QPoint(1180, 0); + QTest::newRow("move top - top screen") << QPoint(1920, 512) << QPoint(1920, -100) << QPoint(1920, 0); + QTest::newRow("move top-right - top screen") << QPoint(1920, 512) << QPoint(2660, -100) << QPoint(2660, 0); + QTest::newRow("move right - top screen") << QPoint(1920, 512) << QPoint(2660, 512) << QPoint(2660, 512); + QTest::newRow("move bottom-right - top screen") << QPoint(1920, 512) << QPoint(2660, 1124) << QPoint(2559, 1023); + QTest::newRow("move bottom - top screen") << QPoint(1920, 512) << QPoint(1920, 1124) << QPoint(1920, 1124); + QTest::newRow("move bottom-left - top screen") << QPoint(1920, 512) << QPoint(1180, 1124) << QPoint(1280, 1023); + QTest::newRow("move left - top screen") << QPoint(1920, 512) << QPoint(1180, 512) << QPoint(1180, 512); + + QTest::newRow("move top-left - right screen") << QPoint(3200, 512) << QPoint(2460, -100) << QPoint(2460, 0); + QTest::newRow("move top - right screen") << QPoint(3200, 512) << QPoint(3200, -100) << QPoint(3200, 0); + QTest::newRow("move top-right - right screen") << QPoint(3200, 512) << QPoint(3940, -100) << QPoint(3839, 0); + QTest::newRow("move right - right screen") << QPoint(3200, 512) << QPoint(3940, 512) << QPoint(3839, 512); + QTest::newRow("move bottom-right - right screen") << QPoint(3200, 512) << QPoint(3940, 1124) << QPoint(3839, 1023); + QTest::newRow("move bottom - right screen") << QPoint(3200, 512) << QPoint(3200, 1124) << QPoint(3200, 1023); + QTest::newRow("move bottom-left - right screen") << QPoint(3200, 512) << QPoint(2460, 1124) << QPoint(2460, 1124); + QTest::newRow("move left - right screen") << QPoint(3200, 512) << QPoint(2460, 512) << QPoint(2460, 512); + + QTest::newRow("move top-left - bottom screen") << QPoint(1920, 1536) << QPoint(1180, 924) << QPoint(1180, 924); + QTest::newRow("move top - bottom screen") << QPoint(1920, 1536) << QPoint(1920, 924) << QPoint(1920, 924); + QTest::newRow("move top-right - bottom screen") << QPoint(1920, 1536) << QPoint(2660, 924) << QPoint(2660, 924); + QTest::newRow("move right - bottom screen") << QPoint(1920, 1536) << QPoint(2660, 1536) << QPoint(2559, 1536); + QTest::newRow("move bottom-right - bottom screen") << QPoint(1920, 1536) << QPoint(2660, 2148) << QPoint(2559, 2047); + QTest::newRow("move bottom - bottom screen") << QPoint(1920, 1536) << QPoint(1920, 2148) << QPoint(1920, 2047); + QTest::newRow("move bottom-left - bottom screen") << QPoint(1920, 1536) << QPoint(1180, 2148) << QPoint(1280, 2047); + QTest::newRow("move left - bottom screen") << QPoint(1920, 1536) << QPoint(1180, 1536) << QPoint(1280, 1536); +} + +void PointerInputTest::testConfineToScreenGeometry() +{ + // this test verifies that pointer belongs to at least one screen + // after moving it to off-screen area + + // unload the Window View effect because it pushes back + // pointer if it's at (0, 0) + static_cast(effects)->unloadEffect(QStringLiteral("windowview")); + + // setup screen layout + const QVector geometries{ + QRect(0, 0, 1280, 1024), + QRect(1280, 0, 1280, 1024), + QRect(2560, 0, 1280, 1024), + QRect(1280, 1024, 1280, 1024)}; + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", + Qt::DirectConnection, + Q_ARG(int, geometries.count()), + Q_ARG(QVector, geometries)); + + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), geometries.count()); + QCOMPARE(outputs[0]->geometry(), geometries.at(0)); + QCOMPARE(outputs[1]->geometry(), geometries.at(1)); + QCOMPARE(outputs[2]->geometry(), geometries.at(2)); + QCOMPARE(outputs[3]->geometry(), geometries.at(3)); + + // move pointer to initial position + QFETCH(QPoint, startPos); + Cursors::self()->mouse()->setPos(startPos); + QCOMPARE(Cursors::self()->mouse()->pos(), startPos); + + // perform movement + QFETCH(QPoint, targetPos); + Test::pointerMotion(targetPos, 1); + + QFETCH(QPoint, expectedPos); + QCOMPARE(Cursors::self()->mouse()->pos(), expectedPos); +} + +void PointerInputTest::testResizeCursor_data() +{ + QTest::addColumn("edges"); + QTest::addColumn("cursorShape"); + + QTest::newRow("top-left") << Qt::Edges(Qt::TopEdge | Qt::LeftEdge) << CursorShape(ExtendedCursor::SizeNorthWest); + QTest::newRow("top") << Qt::Edges(Qt::TopEdge) << CursorShape(ExtendedCursor::SizeNorth); + QTest::newRow("top-right") << Qt::Edges(Qt::TopEdge | Qt::RightEdge) << CursorShape(ExtendedCursor::SizeNorthEast); + QTest::newRow("right") << Qt::Edges(Qt::RightEdge) << CursorShape(ExtendedCursor::SizeEast); + QTest::newRow("bottom-right") << Qt::Edges(Qt::BottomEdge | Qt::RightEdge) << CursorShape(ExtendedCursor::SizeSouthEast); + QTest::newRow("bottom") << Qt::Edges(Qt::BottomEdge) << CursorShape(ExtendedCursor::SizeSouth); + QTest::newRow("bottom-left") << Qt::Edges(Qt::BottomEdge | Qt::LeftEdge) << CursorShape(ExtendedCursor::SizeSouthWest); + QTest::newRow("left") << Qt::Edges(Qt::LeftEdge) << CursorShape(ExtendedCursor::SizeWest); +} + +void PointerInputTest::testResizeCursor() +{ + // this test verifies that the cursor has correct shape during resize operation + + // first modify the config for this run + KConfigGroup group = kwinApp()->config()->group("MouseBindings"); + group.writeEntry("CommandAllKey", "Meta"); + group.writeEntry("CommandAll3", "Resize"); + group.sync(); + workspace()->slotReconfigure(); + QCOMPARE(options->commandAllModifier(), Qt::MetaModifier); + QCOMPARE(options->commandAll3(), Options::MouseUnrestrictedResize); + + // load the fallback cursor (arrow cursor) + const PlatformCursorImage arrowCursor = loadReferenceThemeCursor(Qt::ArrowCursor); + QVERIFY(!arrowCursor.isNull()); + QCOMPARE(kwinApp()->platform()->cursorImage().image(), arrowCursor.image()); + QCOMPARE(kwinApp()->platform()->cursorImage().hotSpot(), arrowCursor.hotSpot()); + + // we need a pointer to get the enter event + auto pointer = m_seat->createPointer(m_seat); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered); + + // create a test window + using namespace KWayland::Client; + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // move the cursor to the test position + QPoint cursorPos; + QFETCH(Qt::Edges, edges); + + if (edges & Qt::LeftEdge) { + cursorPos.setX(window->frameGeometry().left()); + } else if (edges & Qt::RightEdge) { + cursorPos.setX(window->frameGeometry().right() - 1); + } else { + cursorPos.setX(window->frameGeometry().center().x()); + } + + if (edges & Qt::TopEdge) { + cursorPos.setY(window->frameGeometry().top()); + } else if (edges & Qt::BottomEdge) { + cursorPos.setY(window->frameGeometry().bottom() - 1); + } else { + cursorPos.setY(window->frameGeometry().center().y()); + } + + Cursors::self()->mouse()->setPos(cursorPos); + + // wait for the enter event and set the cursor + QVERIFY(enteredSpy.wait()); + std::unique_ptr cursorSurface(Test::createSurface()); + QVERIFY(cursorSurface); + QSignalSpy cursorRenderedSpy(cursorSurface.get(), &KWayland::Client::Surface::frameRendered); + cursorSurface->attachBuffer(Test::waylandShmPool()->createBuffer(arrowCursor.image())); + cursorSurface->damage(arrowCursor.image().rect()); + cursorSurface->commit(); + pointer->setCursor(cursorSurface.get(), arrowCursor.hotSpot()); + QVERIFY(cursorRenderedSpy.wait()); + + // start resizing the window + int timestamp = 1; + Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++); + Test::pointerButtonPressed(BTN_RIGHT, timestamp++); + QVERIFY(window->isInteractiveResize()); + + QFETCH(KWin::CursorShape, cursorShape); + const PlatformCursorImage resizeCursor = loadReferenceThemeCursor(cursorShape); + QVERIFY(!resizeCursor.isNull()); + QCOMPARE(kwinApp()->platform()->cursorImage().image(), resizeCursor.image()); + QCOMPARE(kwinApp()->platform()->cursorImage().hotSpot(), resizeCursor.hotSpot()); + + // finish resizing the window + Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++); + Test::pointerButtonReleased(BTN_RIGHT, timestamp++); + QVERIFY(!window->isInteractiveResize()); + + QCOMPARE(kwinApp()->platform()->cursorImage().image(), arrowCursor.image()); + QCOMPARE(kwinApp()->platform()->cursorImage().hotSpot(), arrowCursor.hotSpot()); +} + +void PointerInputTest::testMoveCursor() +{ + // this test verifies that the cursor has correct shape during move operation + + // first modify the config for this run + KConfigGroup group = kwinApp()->config()->group("MouseBindings"); + group.writeEntry("CommandAllKey", "Meta"); + group.writeEntry("CommandAll1", "Move"); + group.sync(); + workspace()->slotReconfigure(); + QCOMPARE(options->commandAllModifier(), Qt::MetaModifier); + QCOMPARE(options->commandAll1(), Options::MouseUnrestrictedMove); + + // load the fallback cursor (arrow cursor) + const PlatformCursorImage arrowCursor = loadReferenceThemeCursor(Qt::ArrowCursor); + QVERIFY(!arrowCursor.isNull()); + QCOMPARE(kwinApp()->platform()->cursorImage().image(), arrowCursor.image()); + QCOMPARE(kwinApp()->platform()->cursorImage().hotSpot(), arrowCursor.hotSpot()); + + // we need a pointer to get the enter event + auto pointer = m_seat->createPointer(m_seat); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + QSignalSpy enteredSpy(pointer, &KWayland::Client::Pointer::entered); + + // create a test window + using namespace KWayland::Client; + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // move cursor to the test position + Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + + // wait for the enter event and set the cursor + QVERIFY(enteredSpy.wait()); + std::unique_ptr cursorSurface = Test::createSurface(); + QVERIFY(cursorSurface); + QSignalSpy cursorRenderedSpy(cursorSurface.get(), &KWayland::Client::Surface::frameRendered); + cursorSurface->attachBuffer(Test::waylandShmPool()->createBuffer(arrowCursor.image())); + cursorSurface->damage(arrowCursor.image().rect()); + cursorSurface->commit(); + pointer->setCursor(cursorSurface.get(), arrowCursor.hotSpot()); + QVERIFY(cursorRenderedSpy.wait()); + + // start moving the window + int timestamp = 1; + Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++); + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + QVERIFY(window->isInteractiveMove()); + + const PlatformCursorImage sizeAllCursor = loadReferenceThemeCursor(Qt::SizeAllCursor); + QVERIFY(!sizeAllCursor.isNull()); + QCOMPARE(kwinApp()->platform()->cursorImage().image(), sizeAllCursor.image()); + QCOMPARE(kwinApp()->platform()->cursorImage().hotSpot(), sizeAllCursor.hotSpot()); + + // finish moving the window + Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++); + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + QVERIFY(!window->isInteractiveMove()); + + QCOMPARE(kwinApp()->platform()->cursorImage().image(), arrowCursor.image()); + QCOMPARE(kwinApp()->platform()->cursorImage().hotSpot(), arrowCursor.hotSpot()); +} + +void PointerInputTest::testHideShowCursor() +{ + QCOMPARE(Cursors::self()->isCursorHidden(), false); + Cursors::self()->hideCursor(); + QCOMPARE(Cursors::self()->isCursorHidden(), true); + Cursors::self()->showCursor(); + QCOMPARE(Cursors::self()->isCursorHidden(), false); + + Cursors::self()->hideCursor(); + QCOMPARE(Cursors::self()->isCursorHidden(), true); + Cursors::self()->hideCursor(); + Cursors::self()->hideCursor(); + Cursors::self()->hideCursor(); + QCOMPARE(Cursors::self()->isCursorHidden(), true); + + Cursors::self()->showCursor(); + QCOMPARE(Cursors::self()->isCursorHidden(), true); + Cursors::self()->showCursor(); + QCOMPARE(Cursors::self()->isCursorHidden(), true); + Cursors::self()->showCursor(); + QCOMPARE(Cursors::self()->isCursorHidden(), true); + Cursors::self()->showCursor(); + QCOMPARE(Cursors::self()->isCursorHidden(), false); +} + +void PointerInputTest::testDefaultInputRegion() +{ + // This test verifies that a surface that hasn't specified the input region can be focused. + + // Create a test window. + using namespace KWayland::Client; + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // Move the point to the center of the surface. + Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window->surface()); + + // Destroy the test window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void PointerInputTest::testEmptyInputRegion() +{ + // This test verifies that a surface that has specified an empty input region can't be focused. + + // Create a test window. + using namespace KWayland::Client; + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr inputRegion(m_compositor->createRegion(QRegion())); + surface->setInputRegion(inputRegion.get()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // Move the point to the center of the surface. + Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + QVERIFY(!waylandServer()->seat()->focusedPointerSurface()); + + // Destroy the test window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +} + +WAYLANDTEST_MAIN(KWin::PointerInputTest) +#include "pointer_input.moc" diff --git a/autotests/integration/protocols/wlr-layer-shell-unstable-v1.xml b/autotests/integration/protocols/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 0000000..0736a45 --- /dev/null +++ b/autotests/integration/protocols/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,325 @@ + + + + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + + + + + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + After creating a layer_surface object and setting it up, the client + must perform an initial commit without any buffer attached. + The compositor will reply with a layer_surface.configure event. + The client must acknowledge it and is then allowed to attach a buffer + to map the surface. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + + + + + + + + + + + + + + + + + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + + + + + + + + + + + + + This request indicates that the client will not use the layer_shell + object any more. Objects that have been created through this instance + are not affected. + + + + + + + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (layer, size, anchor, exclusive zone, + margin, interactivity) is double-buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + Attaching a null buffer to a layer surface unmaps it. + + Unmapping a layer_surface means that the surface cannot be shown by the + compositor until it is explicitly mapped again. The layer_surface + returns to the state it had right after layer_shell.get_layer_surface. + The client can re-map the surface by performing a commit without any + buffer attached, waiting for a configure event and handling it as usual. + + + + + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + + + + + + + + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthogonal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + + + + + + + Requests that the compositor avoids occluding an area with other + surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to one + edge or an edge and both perpendicular edges. If the surface is not + anchored, anchored to only two perpendicular edges (a corner), anchored + to only two parallel edges or anchored to all edges, a positive value + will be treated the same as zero. + + A positive zone is the distance from the edge in surface-local + coordinates to consider exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive exclusive zone. If set to -1, the surface + indicates that it would not like to be moved to accommodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + + + + + + + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + + + + + + + + + + Set to 1 to request that the seat send keyboard events to this layer + surface. For layers below the shell surface layer, the seat will use + normal focus semantics. For layers above the shell surface layers, the + seat will always give exclusive keyboard focus to the top-most layer + which has keyboard interactivity set to true. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Events is double-buffered, see wl_surface.commit. + + + + + + + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + This request destroys the layer surface. + + + + + + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + 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. + + 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). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + + + + + + + + + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + + + + + + + + + + + + + + + + + + + + + Change the layer that the surface is rendered on. + + Layer is double-buffered, see wl_surface.commit. + + + + + diff --git a/autotests/integration/quick_tiling_test.cpp b/autotests/integration/quick_tiling_test.cpp new file mode 100644 index 0000000..239d3ce --- /dev/null +++ b/autotests/integration/quick_tiling_test.cpp @@ -0,0 +1,875 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2015 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "decorations/decorationbridge.h" +#include "decorations/settings.h" +#include "scripting/scripting.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" +#include "x11window.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include + +Q_DECLARE_METATYPE(KWin::QuickTileMode) +Q_DECLARE_METATYPE(KWin::MaximizeMode) + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_quick_tiling-0"); + +class QuickTilingTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testQuickTiling_data(); + void testQuickTiling(); + void testQuickMaximizing_data(); + void testQuickMaximizing(); + void testQuickTilingKeyboardMove_data(); + void testQuickTilingKeyboardMove(); + void testQuickTilingPointerMove_data(); + void testQuickTilingPointerMove(); + void testQuickTilingTouchMove_data(); + void testQuickTilingTouchMove(); + void testX11QuickTiling_data(); + void testX11QuickTiling(); + void testX11QuickTilingAfterVertMaximize_data(); + void testX11QuickTilingAfterVertMaximize(); + void testShortcut_data(); + void testShortcut(); + void testScript_data(); + void testScript(); + +private: + KWayland::Client::ConnectionThread *m_connection = nullptr; + KWayland::Client::Compositor *m_compositor = nullptr; +}; + +void QuickTilingTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType("MaximizeMode"); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + // set custom config which disables the Outline + KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup group = config->group("Outline"); + group.writeEntry(QStringLiteral("QmlPath"), QString("/does/not/exist.qml")); + group.sync(); + + kwinApp()->setConfig(config); + + qputenv("XKB_DEFAULT_RULES", "evdev"); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); +} + +void QuickTilingTest::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration)); + m_connection = Test::waylandConnection(); + m_compositor = Test::waylandCompositor(); + + workspace()->setActiveOutput(QPoint(640, 512)); + Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void QuickTilingTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void QuickTilingTest::testQuickTiling_data() +{ + QTest::addColumn("mode"); + QTest::addColumn("expectedGeometry"); + QTest::addColumn("secondScreen"); + QTest::addColumn("expectedModeAfterToggle"); + +#define FLAG(name) QuickTileMode(QuickTileFlag::name) + + QTest::newRow("left") << FLAG(Left) << QRectF(0, 0, 640, 1024) << QRectF(1280, 0, 640, 1024) << FLAG(Right); + QTest::newRow("top") << FLAG(Top) << QRectF(0, 0, 1280, 512) << QRectF(1280, 0, 1280, 512) << FLAG(Top); + QTest::newRow("right") << FLAG(Right) << QRectF(640, 0, 640, 1024) << QRectF(1920, 0, 640, 1024) << QuickTileMode(); + QTest::newRow("bottom") << FLAG(Bottom) << QRectF(0, 512, 1280, 512) << QRectF(1280, 512, 1280, 512) << FLAG(Bottom); + + QTest::newRow("top left") << (FLAG(Left) | FLAG(Top)) << QRectF(0, 0, 640, 512) << QRectF(1280, 0, 640, 512) << (FLAG(Right) | FLAG(Top)); + QTest::newRow("top right") << (FLAG(Right) | FLAG(Top)) << QRectF(640, 0, 640, 512) << QRectF(1920, 0, 640, 512) << QuickTileMode(); + QTest::newRow("bottom left") << (FLAG(Left) | FLAG(Bottom)) << QRectF(0, 512, 640, 512) << QRectF(1280, 512, 640, 512) << (FLAG(Right) | FLAG(Bottom)); + QTest::newRow("bottom right") << (FLAG(Right) | FLAG(Bottom)) << QRectF(640, 512, 640, 512) << QRectF(1920, 512, 640, 512) << QuickTileMode(); + + QTest::newRow("maximize") << FLAG(Maximize) << QRectF(0, 0, 1280, 1024) << QRectF(1280, 0, 1280, 1024) << QuickTileMode(); + +#undef FLAG +} + +void QuickTilingTest::testQuickTiling() +{ + using namespace KWayland::Client; + + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + + // Map the window. + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QCOMPARE(workspace()->activeWindow(), window); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50)); + QCOMPARE(window->quickTileMode(), QuickTileMode(QuickTileFlag::None)); + + // We have to receive a configure event when the window becomes active. + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + + QSignalSpy quickTileChangedSpy(window, &Window::quickTileModeChanged); + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + + QFETCH(QuickTileMode, mode); + QFETCH(QRectF, expectedGeometry); + window->setQuickTileMode(mode, true); + QCOMPARE(quickTileChangedSpy.count(), 1); + // at this point the geometry did not yet change + QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50)); + // but quick tile mode already changed + QCOMPARE(window->quickTileMode(), mode); + + // but we got requested a new geometry + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), expectedGeometry.size()); + + // attach a new image + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), expectedGeometry.size().toSize(), Qt::red); + + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(frameGeometryChangedSpy.count(), 1); + QCOMPARE(window->frameGeometry(), expectedGeometry); + + // send window to other screen + QList outputs = workspace()->outputs(); + QCOMPARE(window->output(), outputs[0]); + window->sendToOutput(outputs[1]); + QCOMPARE(window->output(), outputs[1]); + // quick tile should not be changed + QCOMPARE(window->quickTileMode(), mode); + QTEST(window->frameGeometry(), "secondScreen"); + + // now try to toggle again + window->setQuickTileMode(mode, true); + QTEST(window->quickTileMode(), "expectedModeAfterToggle"); +} + +void QuickTilingTest::testQuickMaximizing_data() +{ + QTest::addColumn("mode"); + +#define FLAG(name) QuickTileMode(QuickTileFlag::name) + + QTest::newRow("maximize") << FLAG(Maximize); + QTest::newRow("none") << FLAG(None); + +#undef FLAG +} + +void QuickTilingTest::testQuickMaximizing() +{ + using namespace KWayland::Client; + + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + + // Map the window. + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QCOMPARE(workspace()->activeWindow(), window); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50)); + QCOMPARE(window->quickTileMode(), QuickTileMode(QuickTileFlag::None)); + QCOMPARE(window->maximizeMode(), MaximizeRestore); + + // We have to receive a configure event upon becoming active. + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + + QSignalSpy quickTileChangedSpy(window, &Window::quickTileModeChanged); + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + QSignalSpy maximizeChangedSpy1(window, qOverload(&Window::clientMaximizedStateChanged)); + QSignalSpy maximizeChangedSpy2(window, qOverload(&Window::clientMaximizedStateChanged)); + + window->setQuickTileMode(QuickTileFlag::Maximize, true); + QCOMPARE(quickTileChangedSpy.count(), 1); + + // at this point the geometry did not yet change + QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50)); + // but quick tile mode already changed + QCOMPARE(window->quickTileMode(), QuickTileFlag::Maximize); + QCOMPARE(window->geometryRestore(), QRect(0, 0, 100, 50)); + + // but we got requested a new geometry + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024)); + + // attach a new image + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(1280, 1024), Qt::red); + + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(frameGeometryChangedSpy.count(), 1); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(window->geometryRestore(), QRect(0, 0, 100, 50)); + + // window is now set to maximised + QCOMPARE(maximizeChangedSpy1.count(), 1); + QCOMPARE(maximizeChangedSpy1.first().first().value(), window); + QCOMPARE(maximizeChangedSpy1.first().last().value(), MaximizeFull); + QCOMPARE(maximizeChangedSpy2.count(), 1); + QCOMPARE(maximizeChangedSpy2.first().first().value(), window); + QCOMPARE(maximizeChangedSpy2.first().at(1).toBool(), true); + QCOMPARE(maximizeChangedSpy2.first().at(2).toBool(), true); + QCOMPARE(window->maximizeMode(), MaximizeFull); + + // go back to quick tile none + QFETCH(QuickTileMode, mode); + window->setQuickTileMode(mode, true); + QCOMPARE(window->quickTileMode(), QuickTileMode(QuickTileFlag::None)); + QCOMPARE(quickTileChangedSpy.count(), 2); + // geometry not yet changed + QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(window->geometryRestore(), QRect(0, 0, 100, 50)); + // we got requested a new geometry + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(100, 50)); + + // render again + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(100, 50), Qt::yellow); + + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(frameGeometryChangedSpy.count(), 2); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50)); + QCOMPARE(window->geometryRestore(), QRect(0, 0, 100, 50)); + QCOMPARE(maximizeChangedSpy1.count(), 2); + QCOMPARE(maximizeChangedSpy1.last().first().value(), window); + QCOMPARE(maximizeChangedSpy1.last().last().value(), MaximizeRestore); + QCOMPARE(maximizeChangedSpy2.count(), 2); + QCOMPARE(maximizeChangedSpy2.last().first().value(), window); + QCOMPARE(maximizeChangedSpy2.last().at(1).toBool(), false); + QCOMPARE(maximizeChangedSpy2.last().at(2).toBool(), false); +} + +void QuickTilingTest::testQuickTilingKeyboardMove_data() +{ + QTest::addColumn("targetPos"); + QTest::addColumn("expectedMode"); + + QTest::newRow("topRight") << QPoint(2559, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Right); + QTest::newRow("right") << QPoint(2559, 512) << QuickTileMode(QuickTileFlag::Right); + QTest::newRow("bottomRight") << QPoint(2559, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Right); + QTest::newRow("bottomLeft") << QPoint(0, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Left); + QTest::newRow("Left") << QPoint(0, 512) << QuickTileMode(QuickTileFlag::Left); + QTest::newRow("topLeft") << QPoint(0, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Left); +} + +void QuickTilingTest::testQuickTilingKeyboardMove() +{ + using namespace KWayland::Client; + + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + // let's render + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + + QVERIFY(window); + QCOMPARE(workspace()->activeWindow(), window); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50)); + QCOMPARE(window->quickTileMode(), QuickTileMode(QuickTileFlag::None)); + QCOMPARE(window->maximizeMode(), MaximizeRestore); + + QSignalSpy quickTileChangedSpy(window, &Window::quickTileModeChanged); + + workspace()->performWindowOperation(window, Options::UnrestrictedMoveOp); + QCOMPARE(window, workspace()->moveResizeWindow()); + QCOMPARE(Cursors::self()->mouse()->pos(), QPoint(50, 25)); + + QFETCH(QPoint, targetPos); + quint32 timestamp = 1; + Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + while (Cursors::self()->mouse()->pos().x() > targetPos.x()) { + Test::keyboardKeyPressed(KEY_LEFT, timestamp++); + Test::keyboardKeyReleased(KEY_LEFT, timestamp++); + } + while (Cursors::self()->mouse()->pos().x() < targetPos.x()) { + Test::keyboardKeyPressed(KEY_RIGHT, timestamp++); + Test::keyboardKeyReleased(KEY_RIGHT, timestamp++); + } + while (Cursors::self()->mouse()->pos().y() < targetPos.y()) { + Test::keyboardKeyPressed(KEY_DOWN, timestamp++); + Test::keyboardKeyReleased(KEY_DOWN, timestamp++); + } + while (Cursors::self()->mouse()->pos().y() > targetPos.y()) { + Test::keyboardKeyPressed(KEY_UP, timestamp++); + Test::keyboardKeyReleased(KEY_UP, timestamp++); + } + Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + Test::keyboardKeyPressed(KEY_ENTER, timestamp++); + Test::keyboardKeyReleased(KEY_ENTER, timestamp++); + QCOMPARE(Cursors::self()->mouse()->pos(), targetPos); + QVERIFY(!workspace()->moveResizeWindow()); + + QCOMPARE(quickTileChangedSpy.count(), 1); + QTEST(window->quickTileMode(), "expectedMode"); +} + +void QuickTilingTest::testQuickTilingPointerMove_data() +{ + QTest::addColumn("pointerPos"); + QTest::addColumn("tileSize"); + QTest::addColumn("expectedMode"); + + QTest::newRow("topRight") << QPoint(2559, 24) << QSize(640, 512) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Right); + QTest::newRow("right") << QPoint(2559, 512) << QSize(640, 1024) << QuickTileMode(QuickTileFlag::Right); + QTest::newRow("bottomRight") << QPoint(2559, 1023) << QSize(640, 512) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Right); + QTest::newRow("bottomLeft") << QPoint(0, 1023) << QSize(640, 512) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Left); + QTest::newRow("Left") << QPoint(0, 512) << QSize(640, 1024) << QuickTileMode(QuickTileFlag::Left); + QTest::newRow("topLeft") << QPoint(0, 24) << QSize(640, 512) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Left); +} + +void QuickTilingTest::testQuickTilingPointerMove() +{ + using namespace KWayland::Client; + + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + + // let's render + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QCOMPARE(workspace()->activeWindow(), window); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50)); + QCOMPARE(window->quickTileMode(), QuickTileMode(QuickTileFlag::None)); + QCOMPARE(window->maximizeMode(), MaximizeRestore); + + // we have to receive a configure event when the window becomes active + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + + // verify that basic quick tile mode works as expected, i.e. the window is going to be + // tiled if the user drags it to a screen edge or a corner + QSignalSpy quickTileChangedSpy(window, &Window::quickTileModeChanged); + workspace()->performWindowOperation(window, Options::UnrestrictedMoveOp); + QCOMPARE(window, workspace()->moveResizeWindow()); + QCOMPARE(Cursors::self()->mouse()->pos(), QPoint(50, 25)); + + QFETCH(QPoint, pointerPos); + QFETCH(QSize, tileSize); + quint32 timestamp = 1; + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + Test::pointerMotion(pointerPos, timestamp++); + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + QCOMPARE(quickTileChangedSpy.count(), 1); + QTEST(window->quickTileMode(), "expectedMode"); + QCOMPARE(window->geometryRestore(), QRect(0, 0, 100, 50)); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), tileSize); + + // verify that geometry restore is correct after user untiles the window, but changes + // their mind and tiles the window again while still holding left button + workspace()->performWindowOperation(window, Options::UnrestrictedMoveOp); + QCOMPARE(window, workspace()->moveResizeWindow()); + + Test::pointerButtonPressed(BTN_LEFT, timestamp++); // untile the window + Test::pointerMotion(QPoint(1280, 1024) / 2, timestamp++); + QCOMPARE(quickTileChangedSpy.count(), 2); + QCOMPARE(window->quickTileMode(), QuickTileMode(QuickTileFlag::None)); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(100, 50)); + + Test::pointerMotion(pointerPos, timestamp++); // tile the window again + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + QCOMPARE(quickTileChangedSpy.count(), 3); + QTEST(window->quickTileMode(), "expectedMode"); + QCOMPARE(window->geometryRestore(), QRect(0, 0, 100, 50)); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 4); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), tileSize); +} + +void QuickTilingTest::testQuickTilingTouchMove_data() +{ + QTest::addColumn("targetPos"); + QTest::addColumn("expectedMode"); + + QTest::newRow("topRight") << QPoint(2559, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Right); + QTest::newRow("right") << QPoint(2559, 512) << QuickTileMode(QuickTileFlag::Right); + QTest::newRow("bottomRight") << QPoint(2559, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Right); + QTest::newRow("bottomLeft") << QPoint(0, 1023) << QuickTileMode(QuickTileFlag::Bottom | QuickTileFlag::Left); + QTest::newRow("Left") << QPoint(0, 512) << QuickTileMode(QuickTileFlag::Left); + QTest::newRow("topLeft") << QPoint(0, 24) << QuickTileMode(QuickTileFlag::Top | QuickTileFlag::Left); +} + +void QuickTilingTest::testQuickTilingTouchMove() +{ + // test verifies that touch on decoration also allows quick tiling + // see BUG: 390113 + using namespace KWayland::Client; + + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr deco(Test::waylandServerSideDecoration()->create(surface.get())); + + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + QVERIFY(shellSurface != nullptr); + + // wait for the initial configure event + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + + // let's render + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(1000, 50), Qt::blue); + + QVERIFY(window); + QVERIFY(window->isDecorated()); + const auto decoration = window->decoration(); + QCOMPARE(workspace()->activeWindow(), window); + QCOMPARE(window->frameGeometry(), QRect(-decoration->borderLeft(), 0, 1000 + decoration->borderLeft() + decoration->borderRight(), 50 + decoration->borderTop() + decoration->borderBottom())); + QCOMPARE(window->quickTileMode(), QuickTileMode(QuickTileFlag::None)); + QCOMPARE(window->maximizeMode(), MaximizeRestore); + + // we have to receive a configure event when the window becomes active + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QTRY_COMPARE(surfaceConfigureRequestedSpy.count(), 2); + + QSignalSpy quickTileChangedSpy(window, &Window::quickTileModeChanged); + + // Note that interactive move will be started with a delay. + quint32 timestamp = 1; + QSignalSpy clientStartUserMovedResizedSpy(window, &Window::clientStartUserMovedResized); + Test::touchDown(0, QPointF(window->frameGeometry().center().x(), window->frameGeometry().y() + decoration->borderTop() / 2), timestamp++); + QVERIFY(clientStartUserMovedResizedSpy.wait()); + QCOMPARE(window, workspace()->moveResizeWindow()); + + QFETCH(QPoint, targetPos); + Test::touchMotion(0, targetPos, timestamp++); + Test::touchUp(0, timestamp++); + QVERIFY(!workspace()->moveResizeWindow()); + + // When there are no borders, there is no change to them when quick-tiling. + // TODO: we should test both cases with fixed fake decoration for autotests. + const bool hasBorders = Workspace::self()->decorationBridge()->settings()->borderSize() != KDecoration2::BorderSize::None; + + QCOMPARE(quickTileChangedSpy.count(), 1); + QTEST(window->quickTileMode(), "expectedMode"); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QTRY_COMPARE(surfaceConfigureRequestedSpy.count(), hasBorders ? 4 : 3); + QCOMPARE(false, toplevelConfigureRequestedSpy.last().first().toSize().isEmpty()); +} + +struct XcbConnectionDeleter +{ + void operator()(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } +}; + +void QuickTilingTest::testX11QuickTiling_data() +{ + QTest::addColumn("mode"); + QTest::addColumn("expectedGeometry"); + QTest::addColumn("screenId"); + QTest::addColumn("modeAfterToggle"); + +#define FLAG(name) QuickTileMode(QuickTileFlag::name) + + QTest::newRow("left") << FLAG(Left) << QRectF(0, 0, 640, 1024) << 0 << QuickTileMode(); + QTest::newRow("top") << FLAG(Top) << QRectF(0, 0, 1280, 512) << 1 << FLAG(Top); + QTest::newRow("right") << FLAG(Right) << QRectF(640, 0, 640, 1024) << 1 << FLAG(Left); + QTest::newRow("bottom") << FLAG(Bottom) << QRectF(0, 512, 1280, 512) << 1 << FLAG(Bottom); + + QTest::newRow("top left") << (FLAG(Left) | FLAG(Top)) << QRectF(0, 0, 640, 512) << 0 << QuickTileMode(); + QTest::newRow("top right") << (FLAG(Right) | FLAG(Top)) << QRectF(640, 0, 640, 512) << 1 << (FLAG(Left) | FLAG(Top)); + QTest::newRow("bottom left") << (FLAG(Left) | FLAG(Bottom)) << QRectF(0, 512, 640, 512) << 0 << QuickTileMode(); + QTest::newRow("bottom right") << (FLAG(Right) | FLAG(Bottom)) << QRectF(640, 512, 640, 512) << 1 << (FLAG(Left) | FLAG(Bottom)); + + QTest::newRow("maximize") << FLAG(Maximize) << QRectF(0, 0, 1280, 1024) << 0 << QuickTileMode(); + +#undef FLAG +} +void QuickTilingTest::testX11QuickTiling() +{ + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + + // now quick tile + QSignalSpy quickTileChangedSpy(window, &Window::quickTileModeChanged); + const QRectF origGeo = window->frameGeometry(); + QFETCH(QuickTileMode, mode); + window->setQuickTileMode(mode, true); + QCOMPARE(window->quickTileMode(), mode); + QTEST(window->frameGeometry(), "expectedGeometry"); + QCOMPARE(window->geometryRestore(), origGeo); + QEXPECT_FAIL("maximize", "For maximize we get two changed signals", Continue); + QCOMPARE(quickTileChangedSpy.count(), 1); + + // quick tile to same edge again should also act like send to screen + const auto outputs = workspace()->outputs(); + QCOMPARE(window->output(), outputs[0]); + window->setQuickTileMode(mode, true); + QFETCH(int, screenId); + QCOMPARE(window->output(), outputs[screenId]); + QTEST(window->quickTileMode(), "modeAfterToggle"); + QCOMPARE(window->geometryRestore(), origGeo); + + // and destroy the window again + xcb_unmap_window(c.get(), windowId); + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + c.reset(); + + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + QVERIFY(windowClosedSpy.wait()); +} + +void QuickTilingTest::testX11QuickTilingAfterVertMaximize_data() +{ + QTest::addColumn("mode"); + QTest::addColumn("expectedGeometry"); + +#define FLAG(name) QuickTileMode(QuickTileFlag::name) + + QTest::newRow("left") << FLAG(Left) << QRectF(0, 0, 640, 1024); + QTest::newRow("top") << FLAG(Top) << QRectF(0, 0, 1280, 512); + QTest::newRow("right") << FLAG(Right) << QRectF(640, 0, 640, 1024); + QTest::newRow("bottom") << FLAG(Bottom) << QRectF(0, 512, 1280, 512); + + QTest::newRow("top left") << (FLAG(Left) | FLAG(Top)) << QRectF(0, 0, 640, 512); + QTest::newRow("top right") << (FLAG(Right) | FLAG(Top)) << QRectF(640, 0, 640, 512); + QTest::newRow("bottom left") << (FLAG(Left) | FLAG(Bottom)) << QRectF(0, 512, 640, 512); + QTest::newRow("bottom right") << (FLAG(Right) | FLAG(Bottom)) << QRectF(640, 512, 640, 512); + + QTest::newRow("maximize") << FLAG(Maximize) << QRectF(0, 0, 1280, 1024); + +#undef FLAG +} + +void QuickTilingTest::testX11QuickTilingAfterVertMaximize() +{ + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + + const QRectF origGeo = window->frameGeometry(); + QCOMPARE(window->maximizeMode(), MaximizeRestore); + // vertically maximize the window + window->maximize(window->maximizeMode() ^ MaximizeVertical); + QCOMPARE(window->frameGeometry().width(), origGeo.width()); + QCOMPARE(window->height(), window->output()->geometry().height()); + QCOMPARE(window->geometryRestore(), origGeo); + + // now quick tile + QSignalSpy quickTileChangedSpy(window, &Window::quickTileModeChanged); + QFETCH(QuickTileMode, mode); + window->setQuickTileMode(mode, true); + QCOMPARE(window->quickTileMode(), mode); + QTEST(window->frameGeometry(), "expectedGeometry"); + QEXPECT_FAIL("", "We get two changed events", Continue); + QCOMPARE(quickTileChangedSpy.count(), 1); + + // and destroy the window again + xcb_unmap_window(c.get(), windowId); + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + c.reset(); + + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + QVERIFY(windowClosedSpy.wait()); +} + +void QuickTilingTest::testShortcut_data() +{ + QTest::addColumn("shortcutList"); + QTest::addColumn("expectedMode"); + QTest::addColumn("expectedGeometry"); + +#define FLAG(name) QuickTileMode(QuickTileFlag::name) + QTest::newRow("top") << QStringList{QStringLiteral("Window Quick Tile Top")} << FLAG(Top) << QRect(0, 0, 1280, 512); + QTest::newRow("bottom") << QStringList{QStringLiteral("Window Quick Tile Bottom")} << FLAG(Bottom) << QRect(0, 512, 1280, 512); + QTest::newRow("top right") << QStringList{QStringLiteral("Window Quick Tile Top Right")} << (FLAG(Top) | FLAG(Right)) << QRect(640, 0, 640, 512); + QTest::newRow("top left") << QStringList{QStringLiteral("Window Quick Tile Top Left")} << (FLAG(Top) | FLAG(Left)) << QRect(0, 0, 640, 512); + QTest::newRow("bottom right") << QStringList{QStringLiteral("Window Quick Tile Bottom Right")} << (FLAG(Bottom) | FLAG(Right)) << QRect(640, 512, 640, 512); + QTest::newRow("bottom left") << QStringList{QStringLiteral("Window Quick Tile Bottom Left")} << (FLAG(Bottom) | FLAG(Left)) << QRect(0, 512, 640, 512); + QTest::newRow("left") << QStringList{QStringLiteral("Window Quick Tile Left")} << FLAG(Left) << QRect(0, 0, 640, 1024); + QTest::newRow("right") << QStringList{QStringLiteral("Window Quick Tile Right")} << FLAG(Right) << QRect(640, 0, 640, 1024); + + // Test combined actions for corner tiling + QTest::newRow("top left combined") << QStringList{QStringLiteral("Window Quick Tile Left"), QStringLiteral("Window Quick Tile Top")} << (FLAG(Top) | FLAG(Left)) << QRect(0, 0, 640, 512); + QTest::newRow("top right combined") << QStringList{QStringLiteral("Window Quick Tile Right"), QStringLiteral("Window Quick Tile Top")} << (FLAG(Top) | FLAG(Right)) << QRect(640, 0, 640, 512); + QTest::newRow("bottom left combined") << QStringList{QStringLiteral("Window Quick Tile Left"), QStringLiteral("Window Quick Tile Bottom")} << (FLAG(Bottom) | FLAG(Left)) << QRect(0, 512, 640, 512); + QTest::newRow("bottom right combined") << QStringList{QStringLiteral("Window Quick Tile Right"), QStringLiteral("Window Quick Tile Bottom")} << (FLAG(Bottom) | FLAG(Right)) << QRect(640, 512, 640, 512); +#undef FLAG +} + +void QuickTilingTest::testShortcut() +{ + using namespace KWayland::Client; + + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + + // Map the window. + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QCOMPARE(workspace()->activeWindow(), window); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50)); + QCOMPARE(window->quickTileMode(), QuickTileMode(QuickTileFlag::None)); + + // We have to receive a configure event when the window becomes active. + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + + QFETCH(QStringList, shortcutList); + QFETCH(QRect, expectedGeometry); + + const int numberOfQuickTileActions = shortcutList.count(); + + if (numberOfQuickTileActions > 1) { + QTest::qWait(1001); + } + + for (QString shortcut : shortcutList) { + // invoke global shortcut through dbus + auto msg = QDBusMessage::createMethodCall( + QStringLiteral("org.kde.kglobalaccel"), + QStringLiteral("/component/kwin"), + QStringLiteral("org.kde.kglobalaccel.Component"), + QStringLiteral("invokeShortcut")); + msg.setArguments(QList{shortcut}); + QDBusConnection::sessionBus().asyncCall(msg); + } + + QSignalSpy quickTileChangedSpy(window, &Window::quickTileModeChanged); + QTRY_COMPARE(quickTileChangedSpy.count(), numberOfQuickTileActions); + // at this point the geometry did not yet change + QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50)); + // but quick tile mode already changed + QTEST(window->quickTileMode(), "expectedMode"); + + // but we got requested a new geometry + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), expectedGeometry.size()); + + // attach a new image + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), expectedGeometry.size(), Qt::red); + + QVERIFY(frameGeometryChangedSpy.wait()); + QEXPECT_FAIL("maximize", "Geometry changed called twice for maximize", Continue); + QCOMPARE(frameGeometryChangedSpy.count(), 1); + QCOMPARE(window->frameGeometry(), expectedGeometry); +} + +void QuickTilingTest::testScript_data() +{ + QTest::addColumn("action"); + QTest::addColumn("expectedMode"); + QTest::addColumn("expectedGeometry"); + +#define FLAG(name) QuickTileMode(QuickTileFlag::name) + QTest::newRow("top") << QStringLiteral("Top") << FLAG(Top) << QRect(0, 0, 1280, 512); + QTest::newRow("bottom") << QStringLiteral("Bottom") << FLAG(Bottom) << QRect(0, 512, 1280, 512); + QTest::newRow("top right") << QStringLiteral("TopRight") << (FLAG(Top) | FLAG(Right)) << QRect(640, 0, 640, 512); + QTest::newRow("top left") << QStringLiteral("TopLeft") << (FLAG(Top) | FLAG(Left)) << QRect(0, 0, 640, 512); + QTest::newRow("bottom right") << QStringLiteral("BottomRight") << (FLAG(Bottom) | FLAG(Right)) << QRect(640, 512, 640, 512); + QTest::newRow("bottom left") << QStringLiteral("BottomLeft") << (FLAG(Bottom) | FLAG(Left)) << QRect(0, 512, 640, 512); + QTest::newRow("left") << QStringLiteral("Left") << FLAG(Left) << QRect(0, 0, 640, 1024); + QTest::newRow("right") << QStringLiteral("Right") << FLAG(Right) << QRect(640, 0, 640, 1024); +#undef FLAG +} + +void QuickTilingTest::testScript() +{ + using namespace KWayland::Client; + + std::unique_ptr surface(Test::createSurface()); + QVERIFY(surface != nullptr); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface != nullptr); + + // Map the window. + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QCOMPARE(workspace()->activeWindow(), window); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50)); + QCOMPARE(window->quickTileMode(), QuickTileMode(QuickTileFlag::None)); + + // We have to receive a configure event upon the window becoming active. + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + + QSignalSpy quickTileChangedSpy(window, &Window::quickTileModeChanged); + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + + QVERIFY(Scripting::self()); + QTemporaryFile tmpFile; + QVERIFY(tmpFile.open()); + QTextStream out(&tmpFile); + + QFETCH(QString, action); + out << "workspace.slotWindowQuickTile" << action << "()"; + out.flush(); + + QFETCH(QuickTileMode, expectedMode); + QFETCH(QRect, expectedGeometry); + + const int id = Scripting::self()->loadScript(tmpFile.fileName()); + QVERIFY(id != -1); + QVERIFY(Scripting::self()->isScriptLoaded(tmpFile.fileName())); + auto s = Scripting::self()->findScript(tmpFile.fileName()); + QVERIFY(s); + QSignalSpy runningChangedSpy(s, &AbstractScript::runningChanged); + s->run(); + + QVERIFY(quickTileChangedSpy.wait()); + QCOMPARE(quickTileChangedSpy.count(), 1); + + QCOMPARE(runningChangedSpy.count(), 1); + QCOMPARE(runningChangedSpy.first().first().toBool(), true); + + // at this point the geometry did not yet change + QCOMPARE(window->frameGeometry(), QRect(0, 0, 100, 50)); + // but quick tile mode already changed + QCOMPARE(window->quickTileMode(), expectedMode); + + // but we got requested a new geometry + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), expectedGeometry.size()); + + // attach a new image + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), expectedGeometry.size(), Qt::red); + + QVERIFY(frameGeometryChangedSpy.wait()); + QEXPECT_FAIL("maximize", "Geometry changed called twice for maximize", Continue); + QCOMPARE(frameGeometryChangedSpy.count(), 1); + QCOMPARE(window->frameGeometry(), expectedGeometry); +} + +} + +WAYLANDTEST_MAIN(KWin::QuickTilingTest) +#include "quick_tiling_test.moc" diff --git a/autotests/integration/scene_opengl_es_test.cpp b/autotests/integration/scene_opengl_es_test.cpp new file mode 100644 index 0000000..90c115d --- /dev/null +++ b/autotests/integration/scene_opengl_es_test.cpp @@ -0,0 +1,22 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2017 Martin Flöser + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "generic_scene_opengl_test.h" + +class SceneOpenGLESTest : public GenericSceneOpenGLTest +{ + Q_OBJECT +public: + SceneOpenGLESTest() + : GenericSceneOpenGLTest(QByteArrayLiteral("O2ES")) + { + } +}; + +WAYLANDTEST_MAIN(SceneOpenGLESTest) +#include "scene_opengl_es_test.moc" diff --git a/autotests/integration/scene_opengl_test.cpp b/autotests/integration/scene_opengl_test.cpp new file mode 100644 index 0000000..bbc15d8 --- /dev/null +++ b/autotests/integration/scene_opengl_test.cpp @@ -0,0 +1,22 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "generic_scene_opengl_test.h" + +class SceneOpenGLTest : public GenericSceneOpenGLTest +{ + Q_OBJECT +public: + SceneOpenGLTest() + : GenericSceneOpenGLTest(QByteArrayLiteral("O2")) + { + } +}; + +WAYLANDTEST_MAIN(SceneOpenGLTest) +#include "scene_opengl_test.moc" diff --git a/autotests/integration/scene_qpainter_test.cpp b/autotests/integration/scene_qpainter_test.cpp new file mode 100644 index 0000000..490636d --- /dev/null +++ b/autotests/integration/scene_qpainter_test.cpp @@ -0,0 +1,386 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "composite.h" +#include "core/platform.h" +#include "cursor.h" +#include "effectloader.h" +#include "effects.h" +#include "wayland/shmclientbuffer.h" +#include "wayland/surface_interface.h" +#include "wayland_server.h" +#include "workspace.h" +#include "x11window.h" + +#include + +#include +#include +#include + +#include + +#include +#include + +using namespace KWin; +static const QString s_socketName = QStringLiteral("wayland_test_kwin_scene_qpainter-0"); + +class SceneQPainterTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void cleanup(); + void testStartFrame(); + void testCursorMoving(); + void testWindow(); + void testWindowScaled(); + void testCompositorRestart(); + void testX11Window(); + +private: + QImage grab(Output *output); +}; + +void SceneQPainterTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void SceneQPainterTest::initTestCase() +{ + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + // disable all effects - we don't want to have it interact with the rendering + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup plugins(config, QStringLiteral("Plugins")); + const auto builtinNames = EffectLoader().listOfKnownEffects(); + for (QString name : builtinNames) { + plugins.writeEntry(name + QStringLiteral("Enabled"), false); + } + + config->sync(); + kwinApp()->setConfig(config); + + if (!QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("icons/DMZ-White/index.theme")).isEmpty()) { + qputenv("XCURSOR_THEME", QByteArrayLiteral("DMZ-White")); + } else { + // might be vanilla-dmz (e.g. Arch, FreeBSD) + qputenv("XCURSOR_THEME", QByteArrayLiteral("Vanilla-DMZ")); + } + qputenv("XCURSOR_SIZE", QByteArrayLiteral("24")); + qputenv("KWIN_COMPOSE", QByteArrayLiteral("Q")); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + QVERIFY(Compositor::self()); +} + +QImage SceneQPainterTest::grab(Output *output) +{ + QImage image; + QMetaObject::invokeMethod(kwinApp()->platform(), + "captureOutput", + Qt::DirectConnection, + Q_RETURN_ARG(QImage, image), + Q_ARG(Output *, output)); + return image; +} + +void SceneQPainterTest::testStartFrame() +{ + // this test verifies that the initial rendering is correct + Compositor::self()->scene()->addRepaintFull(); + auto scene = Compositor::self()->scene(); + QVERIFY(scene); + QCOMPARE(kwinApp()->platform()->selectedCompositor(), QPainterCompositing); + QSignalSpy frameRenderedSpy(scene, &Scene::frameRendered); + QVERIFY(frameRenderedSpy.wait()); + // now let's render a reference image for comparison + QImage referenceImage(QSize(1280, 1024), QImage::Format_RGB32); + referenceImage.fill(Qt::black); + QPainter p(&referenceImage); + + auto cursor = KWin::Cursors::self()->mouse(); + const QImage cursorImage = cursor->image(); + QVERIFY(!cursorImage.isNull()); + p.drawImage(cursor->pos() - cursor->hotspot(), cursorImage); + const auto outputs = workspace()->outputs(); + QCOMPARE(referenceImage, grab(outputs.constFirst())); +} + +void SceneQPainterTest::testCursorMoving() +{ + // this test verifies that rendering is correct also after moving the cursor a few times + auto scene = Compositor::self()->scene(); + QVERIFY(scene); + QSignalSpy frameRenderedSpy(scene, &Scene::frameRendered); + KWin::Cursors::self()->mouse()->setPos(0, 0); + QVERIFY(frameRenderedSpy.wait()); + KWin::Cursors::self()->mouse()->setPos(10, 0); + QVERIFY(frameRenderedSpy.wait()); + KWin::Cursors::self()->mouse()->setPos(10, 12); + QVERIFY(frameRenderedSpy.wait()); + KWin::Cursors::self()->mouse()->setPos(12, 14); + QVERIFY(frameRenderedSpy.wait()); + KWin::Cursors::self()->mouse()->setPos(50, 60); + QVERIFY(frameRenderedSpy.wait()); + KWin::Cursors::self()->mouse()->setPos(45, 45); + QVERIFY(frameRenderedSpy.wait()); + // now let's render a reference image for comparison + QImage referenceImage(QSize(1280, 1024), QImage::Format_RGB32); + referenceImage.fill(Qt::black); + QPainter p(&referenceImage); + + auto cursor = Cursors::self()->currentCursor(); + const QImage cursorImage = cursor->image(); + QVERIFY(!cursorImage.isNull()); + p.drawImage(QPoint(45, 45) - cursor->hotspot(), cursorImage); + const auto outputs = workspace()->outputs(); + QCOMPARE(referenceImage, grab(outputs.constFirst())); +} + +void SceneQPainterTest::testWindow() +{ + KWin::Cursors::self()->mouse()->setPos(45, 45); + // this test verifies that a window is rendered correctly + using namespace KWayland::Client; + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat)); + QVERIFY(Test::waitForWaylandPointer()); + std::unique_ptr s(Test::createSurface()); + std::unique_ptr ss(Test::createXdgToplevelSurface(s.get())); + std::unique_ptr p(Test::waylandSeat()->createPointer()); + + auto scene = KWin::Compositor::self()->scene(); + QVERIFY(scene); + QSignalSpy frameRenderedSpy(scene, &Scene::frameRendered); + + // now let's map the window + QVERIFY(Test::renderAndWaitForShown(s.get(), QSize(200, 300), Qt::blue)); + // which should trigger a frame + if (frameRenderedSpy.isEmpty()) { + QVERIFY(frameRenderedSpy.wait()); + } + // we didn't set a cursor image on the surface yet, so it should be just black + window and previous cursor + QImage referenceImage(QSize(1280, 1024), QImage::Format_RGB32); + referenceImage.fill(Qt::black); + QPainter painter(&referenceImage); + painter.fillRect(0, 0, 200, 300, Qt::blue); + + // now let's set a cursor image + std::unique_ptr cs(Test::createSurface()); + QVERIFY(cs != nullptr); + Test::render(cs.get(), QSize(10, 10), Qt::red); + p->setCursor(cs.get(), QPoint(5, 5)); + QVERIFY(frameRenderedSpy.wait()); + painter.fillRect(KWin::Cursors::self()->mouse()->pos().x() - 5, KWin::Cursors::self()->mouse()->pos().y() - 5, 10, 10, Qt::red); + const auto outputs = workspace()->outputs(); + QCOMPARE(referenceImage, grab(outputs.constFirst())); + // let's move the cursor again + KWin::Cursors::self()->mouse()->setPos(10, 10); + QVERIFY(frameRenderedSpy.wait()); + painter.fillRect(0, 0, 200, 300, Qt::blue); + painter.fillRect(5, 5, 10, 10, Qt::red); + QCOMPARE(referenceImage, grab(outputs.constFirst())); +} + +void SceneQPainterTest::testWindowScaled() +{ + KWin::Cursors::self()->mouse()->setPos(10, 10); + // this test verifies that a window is rendered correctly + using namespace KWayland::Client; + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat)); + QVERIFY(Test::waitForWaylandPointer()); + std::unique_ptr s(Test::createSurface()); + std::unique_ptr ss(Test::createXdgToplevelSurface(s.get())); + std::unique_ptr p(Test::waylandSeat()->createPointer()); + QSignalSpy pointerEnteredSpy(p.get(), &Pointer::entered); + + auto scene = KWin::Compositor::self()->scene(); + QVERIFY(scene); + QSignalSpy frameRenderedSpy(scene, &Scene::frameRendered); + + // now let's set a cursor image + std::unique_ptr cs(Test::createSurface()); + QVERIFY(cs != nullptr); + Test::render(cs.get(), QSize(10, 10), Qt::red); + + // now let's map the window + s->setScale(2); + + // draw a blue square@400x600 with red rectangle@200x200 in the middle + const QSize size(400, 600); + QImage img(size, QImage::Format_ARGB32_Premultiplied); + img.fill(Qt::blue); + QPainter surfacePainter(&img); + surfacePainter.fillRect(200, 300, 200, 200, Qt::red); + + // add buffer + Test::render(s.get(), img); + QVERIFY(pointerEnteredSpy.wait()); + p->setCursor(cs.get(), QPoint(5, 5)); + + // which should trigger a frame + QVERIFY(frameRenderedSpy.wait()); + QImage referenceImage(QSize(1280, 1024), QImage::Format_RGB32); + referenceImage.fill(Qt::black); + QPainter painter(&referenceImage); + painter.fillRect(0, 0, 200, 300, Qt::blue); + painter.fillRect(100, 150, 100, 100, Qt::red); + painter.fillRect(5, 5, 10, 10, Qt::red); // cursor + + const auto outputs = workspace()->outputs(); + QCOMPARE(referenceImage, grab(outputs.constFirst())); +} + +void SceneQPainterTest::testCompositorRestart() +{ + // this test verifies that the compositor/SceneQPainter survive a restart of the compositor and still render correctly + KWin::Cursors::self()->mouse()->setPos(400, 400); + + // first create a window + using namespace KWayland::Client; + QVERIFY(Test::setupWaylandConnection()); + std::unique_ptr s(Test::createSurface()); + std::unique_ptr ss(Test::createXdgToplevelSurface(s.get())); + QVERIFY(Test::renderAndWaitForShown(s.get(), QSize(200, 300), Qt::blue)); + + // now let's try to reinitialize the compositing scene + auto oldScene = KWin::Compositor::self()->scene(); + QVERIFY(oldScene); + QSignalSpy sceneCreatedSpy(KWin::Compositor::self(), &KWin::Compositor::sceneCreated); + KWin::Compositor::self()->reinitialize(); + if (sceneCreatedSpy.isEmpty()) { + QVERIFY(sceneCreatedSpy.wait()); + } + QCOMPARE(sceneCreatedSpy.count(), 1); + auto scene = KWin::Compositor::self()->scene(); + QVERIFY(scene); + + // this should directly trigger a frame + KWin::Compositor::self()->scene()->addRepaintFull(); + QSignalSpy frameRenderedSpy(scene, &Scene::frameRendered); + QVERIFY(frameRenderedSpy.wait()); + + // render reference image + QImage referenceImage(QSize(1280, 1024), QImage::Format_RGB32); + referenceImage.fill(Qt::black); + QPainter painter(&referenceImage); + painter.fillRect(0, 0, 200, 300, Qt::blue); + + auto cursor = Cursors::self()->mouse(); + const QImage cursorImage = cursor->image(); + QVERIFY(!cursorImage.isNull()); + painter.drawImage(QPoint(400, 400) - cursor->hotspot(), cursorImage); + const auto outputs = workspace()->outputs(); + QCOMPARE(referenceImage, grab(outputs.constFirst())); +} + +struct XcbConnectionDeleter +{ + void operator()(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } +}; + +static bool waitForXwaylandBuffer(Window *window, const QSize &size) +{ + // Usually, when an Xwayland surface is created, it has a buffer of size 1x1, + // a buffer with the correct size will be committed a bit later. + KWaylandServer::SurfaceInterface *surface = window->surface(); + int attemptCount = 0; + do { + if (surface->buffer() && surface->buffer()->size() == size) { + return true; + } + QSignalSpy committedSpy(surface, &KWaylandServer::SurfaceInterface::committed); + if (!committedSpy.wait()) { + return false; + } + + ++attemptCount; + } while (attemptCount <= 3); + + return false; +} + +void SceneQPainterTest::testX11Window() +{ + // this test verifies the condition of BUG: 382748 + + // create X11 window + QSignalSpy windowAddedSpy(effects, &EffectsHandler::windowAdded); + + // create an xcb window + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t windowId = xcb_generate_id(c.get()); + uint32_t value = Xcb::defaultScreen()->white_pixel; + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, XCB_CW_BACK_PIXEL, &value); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QCOMPARE(window->clientSize(), QSize(100, 200)); + QVERIFY(Test::waitForWaylandSurface(window)); + QVERIFY(waitForXwaylandBuffer(window, window->size().toSize())); + QImage compareImage(window->clientSize().toSize(), QImage::Format_RGB32); + compareImage.fill(Qt::white); + auto buffer = qobject_cast(window->surface()->buffer()); + QCOMPARE(buffer->data().copy(QRectF(window->clientPos(), window->clientSize()).toRect()), compareImage); + + // enough time for rendering the window + QTest::qWait(100); + + auto scene = KWin::Compositor::self()->scene(); + QVERIFY(scene); + + // this should directly trigger a frame + KWin::Compositor::self()->scene()->addRepaintFull(); + QSignalSpy frameRenderedSpy(scene, &Scene::frameRendered); + QVERIFY(frameRenderedSpy.wait()); + + const QPointF startPos = window->pos() + window->clientPos(); + auto image = grab(workspace()->outputs().constFirst()); + QCOMPARE(image.copy(QRectF(startPos, window->clientSize()).toAlignedRect()), compareImage); + + // and destroy the window again + xcb_unmap_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + QVERIFY(windowClosedSpy.wait()); + xcb_destroy_window(c.get(), windowId); + c.reset(); +} + +WAYLANDTEST_MAIN(SceneQPainterTest) +#include "scene_qpainter_test.moc" diff --git a/autotests/integration/screen_changes_test.cpp b/autotests/integration/screen_changes_test.cpp new file mode 100644 index 0000000..25bdb3b --- /dev/null +++ b/autotests/integration/screen_changes_test.cpp @@ -0,0 +1,178 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "wayland_server.h" +#include "workspace.h" + +#include +#include +#include + +using namespace KWin; +using namespace KWayland::Client; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_screen_changes-0"); + +class ScreenChangesTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testScreenAddRemove(); +}; + +void ScreenChangesTest::initTestCase() +{ + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + setenv("QT_QPA_PLATFORM", "wayland", true); +} + +void ScreenChangesTest::init() +{ + QVERIFY(Test::setupWaylandConnection()); + + workspace()->setActiveOutput(QPoint(640, 512)); + KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void ScreenChangesTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void ScreenChangesTest::testScreenAddRemove() +{ + // this test verifies that when a new screen is added it gets synced to Wayland + + // first create a registry to get signals about Outputs announced/removed + Registry registry; + QSignalSpy allAnnounced(®istry, &Registry::interfacesAnnounced); + QSignalSpy outputAnnouncedSpy(®istry, &Registry::outputAnnounced); + QSignalSpy outputRemovedSpy(®istry, &Registry::outputRemoved); + registry.create(Test::waylandConnection()); + QVERIFY(registry.isValid()); + registry.setup(); + QVERIFY(allAnnounced.wait()); + const auto xdgOMData = registry.interface(Registry::Interface::XdgOutputUnstableV1); + auto xdgOutputManager = registry.createXdgOutputManager(xdgOMData.name, xdgOMData.version); + + // should be one output + QCOMPARE(workspace()->outputs().count(), 1); + QCOMPARE(outputAnnouncedSpy.count(), 1); + const quint32 firstOutputId = outputAnnouncedSpy.first().first().value(); + QVERIFY(firstOutputId != 0u); + outputAnnouncedSpy.clear(); + + // let's announce a new output + const QVector geometries{QRect(0, 0, 1280, 1024), QRect(1280, 0, 1280, 1024)}; + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", + Qt::DirectConnection, + Q_ARG(int, 2), + Q_ARG(QVector, geometries)); + auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), geometries[0]); + QCOMPARE(outputs[1]->geometry(), geometries[1]); + + // this should result in it getting announced, two new outputs are added... + QVERIFY(outputAnnouncedSpy.wait()); + if (outputAnnouncedSpy.count() < 2) { + QVERIFY(outputAnnouncedSpy.wait()); + } + QCOMPARE(outputAnnouncedSpy.count(), 2); + // ... and afterward the previous output gets removed + if (outputRemovedSpy.isEmpty()) { + QVERIFY(outputRemovedSpy.wait()); + } + QCOMPARE(outputRemovedSpy.count(), 1); + QCOMPARE(outputRemovedSpy.first().first().value(), firstOutputId); + + // let's wait a little bit to ensure we don't get more events + QTest::qWait(100); + QCOMPARE(outputAnnouncedSpy.count(), 2); + QCOMPARE(outputRemovedSpy.count(), 1); + + // let's create the output objects to ensure they are correct + std::unique_ptr o1(registry.createOutput(outputAnnouncedSpy.first().first().value(), outputAnnouncedSpy.first().last().value())); + QVERIFY(o1->isValid()); + QSignalSpy o1ChangedSpy(o1.get(), &KWayland::Client::Output::changed); + QVERIFY(o1ChangedSpy.wait()); + KWin::Output *serverOutput1 = kwinApp()->platform()->findOutput(o1->name()); // use wl_output.name to find the compositor side output + QCOMPARE(o1->globalPosition(), serverOutput1->geometry().topLeft()); + QCOMPARE(o1->pixelSize(), serverOutput1->modeSize()); + std::unique_ptr o2(registry.createOutput(outputAnnouncedSpy.last().first().value(), outputAnnouncedSpy.last().last().value())); + QVERIFY(o2->isValid()); + QSignalSpy o2ChangedSpy(o2.get(), &KWayland::Client::Output::changed); + QVERIFY(o2ChangedSpy.wait()); + KWin::Output *serverOutput2 = kwinApp()->platform()->findOutput(o2->name()); // use wl_output.name to find the compositor side output + QCOMPARE(o2->globalPosition(), serverOutput2->geometry().topLeft()); + QCOMPARE(o2->pixelSize(), serverOutput2->modeSize()); + + // and check XDGOutput is synced + std::unique_ptr xdgO1(xdgOutputManager->getXdgOutput(o1.get())); + QSignalSpy xdgO1ChangedSpy(xdgO1.get(), &XdgOutput::changed); + QVERIFY(xdgO1ChangedSpy.wait()); + QCOMPARE(xdgO1->logicalPosition(), serverOutput1->geometry().topLeft()); + QCOMPARE(xdgO1->logicalSize(), serverOutput1->geometry().size()); + std::unique_ptr xdgO2(xdgOutputManager->getXdgOutput(o2.get())); + QSignalSpy xdgO2ChangedSpy(xdgO2.get(), &XdgOutput::changed); + QVERIFY(xdgO2ChangedSpy.wait()); + QCOMPARE(xdgO2->logicalPosition(), serverOutput2->geometry().topLeft()); + QCOMPARE(xdgO2->logicalSize(), serverOutput2->geometry().size()); + + QVERIFY(xdgO1->name().startsWith("Virtual-")); + QVERIFY(xdgO1->name() != xdgO2->name()); + QVERIFY(!xdgO1->description().isEmpty()); + + // now let's try to remove one output again + outputAnnouncedSpy.clear(); + outputRemovedSpy.clear(); + + QSignalSpy o1RemovedSpy(o1.get(), &KWayland::Client::Output::removed); + QSignalSpy o2RemovedSpy(o2.get(), &KWayland::Client::Output::removed); + + const QVector geometries2{QRect(0, 0, 1280, 1024)}; + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", + Qt::DirectConnection, + Q_ARG(int, 1), + Q_ARG(QVector, geometries2)); + outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 1); + QCOMPARE(outputs[0]->geometry(), geometries2.at(0)); + + QVERIFY(outputAnnouncedSpy.wait()); + QCOMPARE(outputAnnouncedSpy.count(), 1); + if (o1RemovedSpy.isEmpty()) { + QVERIFY(o1RemovedSpy.wait()); + } + if (o2RemovedSpy.isEmpty()) { + QVERIFY(o2RemovedSpy.wait()); + } + // now wait a bit to ensure we don't get more events + QTest::qWait(100); + QCOMPARE(outputAnnouncedSpy.count(), 1); + QCOMPARE(o1RemovedSpy.count(), 1); + QCOMPARE(o2RemovedSpy.count(), 1); + QCOMPARE(outputRemovedSpy.count(), 2); +} + +WAYLANDTEST_MAIN(ScreenChangesTest) +#include "screen_changes_test.moc" diff --git a/autotests/integration/screenedge_client_show_test.cpp b/autotests/integration/screenedge_client_show_test.cpp new file mode 100644 index 0000000..7fe773b --- /dev/null +++ b/autotests/integration/screenedge_client_show_test.cpp @@ -0,0 +1,270 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "deleted.h" +#include "screenedge.h" +#include "wayland_server.h" +#include "workspace.h" +#include "x11window.h" +#include + +#include +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_screenedge_client_show-0"); + +class ScreenEdgeClientShowTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void testScreenEdgeShowHideX11_data(); + void testScreenEdgeShowHideX11(); + void testScreenEdgeShowX11Touch_data(); + void testScreenEdgeShowX11Touch(); +}; + +void ScreenEdgeClientShowTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + // set custom config which disable touch edge + KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup group = config->group("TabBox"); + group.writeEntry(QStringLiteral("TouchBorderActivate"), "9"); + group.sync(); + + kwinApp()->setConfig(config); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); + setenv("QT_QPA_PLATFORM", "wayland", true); +} + +void ScreenEdgeClientShowTest::init() +{ + workspace()->setActiveOutput(QPoint(640, 512)); + Cursors::self()->mouse()->setPos(QPoint(640, 512)); + QVERIFY(waylandServer()->windows().isEmpty()); +} + +struct XcbConnectionDeleter +{ + void operator()(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } +}; + +void ScreenEdgeClientShowTest::testScreenEdgeShowHideX11_data() +{ + QTest::addColumn("windowGeometry"); + QTest::addColumn("resizedWindowGeometry"); + QTest::addColumn("location"); + QTest::addColumn("triggerPos"); + + QTest::newRow("bottom/left") << QRect(50, 1004, 1180, 20) << QRect(150, 1004, 1000, 20) << 2u << QPoint(100, 1023); + QTest::newRow("bottom/right") << QRect(1330, 1004, 1180, 20) << QRect(1410, 1004, 1000, 20) << 2u << QPoint(1400, 1023); + QTest::newRow("top/left") << QRect(50, 0, 1180, 20) << QRect(150, 0, 1000, 20) << 0u << QPoint(100, 0); + QTest::newRow("top/right") << QRect(1330, 0, 1180, 20) << QRect(1410, 0, 1000, 20) << 0u << QPoint(1400, 0); + QTest::newRow("left") << QRect(0, 10, 20, 1000) << QRect(0, 70, 20, 800) << 3u << QPoint(0, 50); + QTest::newRow("right") << QRect(2540, 10, 20, 1000) << QRect(2540, 70, 20, 800) << 1u << QPoint(2559, 60); +} + +void ScreenEdgeClientShowTest::testScreenEdgeShowHideX11() +{ + // this test creates a window which borders the screen and sets the screenedge show hint + // that should trigger a show of the window whenever the cursor is pushed against the screen edge + + // create the test window + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + // atom for the screenedge show hide functionality + Xcb::Atom atom(QByteArrayLiteral("_KDE_NET_WM_SCREEN_EDGE_SHOW"), false, c.get()); + + xcb_window_t windowId = xcb_generate_id(c.get()); + QFETCH(QRect, windowGeometry); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + NETWinInfo info(c.get(), windowId, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); + info.setWindowType(NET::Dock); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.last().first().value(); + QVERIFY(window); + QVERIFY(!window->isDecorated()); + QCOMPARE(window->frameGeometry(), windowGeometry); + QVERIFY(!window->hasStrut()); + QVERIFY(!window->isHiddenInternal()); + + QSignalSpy effectsWindowAdded(effects, &EffectsHandler::windowAdded); + QVERIFY(effectsWindowAdded.wait()); + + // now try to hide + QFETCH(quint32, location); + xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atom, XCB_ATOM_CARDINAL, 32, 1, &location); + xcb_flush(c.get()); + + QSignalSpy effectsWindowHiddenSpy(effects, &EffectsHandler::windowHidden); + QSignalSpy clientHiddenSpy(window, &X11Window::windowHidden); + QVERIFY(clientHiddenSpy.wait()); + QVERIFY(window->isHiddenInternal()); + QCOMPARE(effectsWindowHiddenSpy.count(), 1); + + // now trigger the edge + QSignalSpy effectsWindowShownSpy(effects, &EffectsHandler::windowShown); + QFETCH(QPoint, triggerPos); + Cursors::self()->mouse()->setPos(triggerPos); + QVERIFY(!window->isHiddenInternal()); + QCOMPARE(effectsWindowShownSpy.count(), 1); + + // go into event loop to trigger xcb_flush + QTest::qWait(1); + + // hide window again + Cursors::self()->mouse()->setPos(QPoint(640, 512)); + xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atom, XCB_ATOM_CARDINAL, 32, 1, &location); + xcb_flush(c.get()); + QVERIFY(clientHiddenSpy.wait()); + QVERIFY(window->isHiddenInternal()); + QFETCH(QRect, resizedWindowGeometry); + // resizewhile hidden + window->moveResize(resizedWindowGeometry); + // triggerPos shouldn't be valid anymore + Cursors::self()->mouse()->setPos(triggerPos); + QVERIFY(window->isHiddenInternal()); + + // destroy window again + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + xcb_unmap_window(c.get(), windowId); + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + QVERIFY(windowClosedSpy.wait()); +} + +void ScreenEdgeClientShowTest::testScreenEdgeShowX11Touch_data() +{ + QTest::addColumn("windowGeometry"); + QTest::addColumn("location"); + QTest::addColumn("touchDownPos"); + QTest::addColumn("targetPos"); + + QTest::newRow("bottom/left") << QRect(50, 1004, 1180, 20) << 2u << QPoint(100, 1023) << QPoint(100, 540); + QTest::newRow("bottom/right") << QRect(1330, 1004, 1180, 20) << 2u << QPoint(1400, 1023) << QPoint(1400, 520); + QTest::newRow("top/left") << QRect(50, 0, 1180, 20) << 0u << QPoint(100, 0) << QPoint(100, 350); + QTest::newRow("top/right") << QRect(1330, 0, 1180, 20) << 0u << QPoint(1400, 0) << QPoint(1400, 400); + QTest::newRow("left") << QRect(0, 10, 20, 1000) << 3u << QPoint(0, 50) << QPoint(400, 50); + QTest::newRow("right") << QRect(2540, 10, 20, 1000) << 1u << QPoint(2559, 60) << QPoint(2200, 60); +} + +void ScreenEdgeClientShowTest::testScreenEdgeShowX11Touch() +{ + // this test creates a window which borders the screen and sets the screenedge show hint + // that should trigger a show of the window whenever the touch screen swipe gesture is triggered + + // create the test window + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + // atom for the screenedge show hide functionality + Xcb::Atom atom(QByteArrayLiteral("_KDE_NET_WM_SCREEN_EDGE_SHOW"), false, c.get()); + + xcb_window_t windowId = xcb_generate_id(c.get()); + QFETCH(QRect, windowGeometry); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + NETWinInfo info(c.get(), windowId, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); + info.setWindowType(NET::Dock); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.last().first().value(); + QVERIFY(window); + QVERIFY(!window->isDecorated()); + QCOMPARE(window->frameGeometry(), windowGeometry); + QVERIFY(!window->hasStrut()); + QVERIFY(!window->isHiddenInternal()); + + QSignalSpy effectsWindowAdded(effects, &EffectsHandler::windowAdded); + QVERIFY(effectsWindowAdded.wait()); + + // now try to hide + QFETCH(quint32, location); + xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atom, XCB_ATOM_CARDINAL, 32, 1, &location); + xcb_flush(c.get()); + + QSignalSpy effectsWindowHiddenSpy(effects, &EffectsHandler::windowHidden); + QSignalSpy clientHiddenSpy(window, &X11Window::windowHidden); + QVERIFY(clientHiddenSpy.wait()); + QVERIFY(window->isHiddenInternal()); + QCOMPARE(effectsWindowHiddenSpy.count(), 1); + + // now trigger the edge + QSignalSpy effectsWindowShownSpy(effects, &EffectsHandler::windowShown); + quint32 timestamp = 0; + QFETCH(QPoint, touchDownPos); + QFETCH(QPoint, targetPos); + Test::touchDown(0, touchDownPos, timestamp++); + Test::touchMotion(0, targetPos, timestamp++); + Test::touchUp(0, timestamp++); + QVERIFY(effectsWindowShownSpy.wait()); + QVERIFY(!window->isHiddenInternal()); + QCOMPARE(effectsWindowShownSpy.count(), 1); + + // destroy window again + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + xcb_unmap_window(c.get(), windowId); + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + QVERIFY(windowClosedSpy.wait()); +} + +} + +WAYLANDTEST_MAIN(KWin::ScreenEdgeClientShowTest) +#include "screenedge_client_show_test.moc" diff --git a/autotests/integration/screenedges_test.cpp b/autotests/integration/screenedges_test.cpp new file mode 100644 index 0000000..526b442 --- /dev/null +++ b/autotests/integration/screenedges_test.cpp @@ -0,0 +1,347 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2014 Martin Gräßlin + SPDX-FileCopyrightText: 2021 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "kwin_wayland_test.h" + +#include "core/platform.h" +#include "cursor.h" +#include "effectloader.h" +#include "main.h" +#include "screenedge.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include +#include + +Q_DECLARE_METATYPE(KWin::ElectricBorder) + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_screen-edges-0"); + +class TestObject : public QObject +{ + Q_OBJECT + +public Q_SLOTS: + bool callback(ElectricBorder border) + { + Q_EMIT gotCallback(border); + return true; + } + +Q_SIGNALS: + void gotCallback(KWin::ElectricBorder); +}; + +class ScreenEdgesTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testTouchCallback_data(); + void testTouchCallback(); + void testPushBack_data(); + void testPushBack(); + void testClientEdge_data(); + void testClientEdge(); + void testObjectEdge_data(); + void testObjectEdge(); +}; + +void ScreenEdgesTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType("ElectricBorder"); + + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + // Disable effects, in particular present windows, which reserves a screen edge. + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup plugins(config, QStringLiteral("Plugins")); + const auto builtinNames = EffectLoader().listOfKnownEffects(); + for (const QString &name : builtinNames) { + plugins.writeEntry(name + QStringLiteral("Enabled"), false); + } + + config->sync(); + kwinApp()->setConfig(config); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); +} + +void ScreenEdgesTest::init() +{ + workspace()->screenEdges()->recreateEdges(); + Workspace::self()->setActiveOutput(QPoint(640, 512)); + KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512)); + + QVERIFY(Test::setupWaylandConnection()); +} + +void ScreenEdgesTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void ScreenEdgesTest::testTouchCallback_data() +{ + QTest::addColumn("border"); + QTest::addColumn("startPos"); + QTest::addColumn("delta"); + + QTest::newRow("left") << ElectricLeft << QPointF(0, 50) << QPointF(256, 20); + QTest::newRow("top") << ElectricTop << QPointF(50, 0) << QPointF(20, 250); + QTest::newRow("right") << ElectricRight << QPointF(1279, 50) << QPointF(-256, 0); + QTest::newRow("bottom") << ElectricBottom << QPointF(50, 1023) << QPointF(0, -205); +} + +void ScreenEdgesTest::testTouchCallback() +{ + // This test verifies that touch screen edges trigger associated callbacks. + + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + auto group = config->group("TouchEdges"); + group.writeEntry("Top", "none"); + group.writeEntry("Left", "none"); + group.writeEntry("Bottom", "none"); + group.writeEntry("Right", "none"); + config->sync(); + + auto s = workspace()->screenEdges(); + s->setConfig(config); + s->reconfigure(); + + // none of our actions should be reserved + const QList edges = s->findChildren(QString(), Qt::FindDirectChildrenOnly); + QCOMPARE(edges.size(), 8); + for (auto edge : edges) { + QCOMPARE(edge->isReserved(), false); + QCOMPARE(edge->activatesForPointer(), false); + QCOMPARE(edge->activatesForTouchGesture(), false); + } + + // let's reserve an action + QAction action; + QSignalSpy actionTriggeredSpy(&action, &QAction::triggered); + + // reserve on edge + QFETCH(KWin::ElectricBorder, border); + s->reserveTouch(border, &action); + for (auto edge : edges) { + QCOMPARE(edge->isReserved(), edge->border() == border); + QCOMPARE(edge->activatesForPointer(), false); + QCOMPARE(edge->activatesForTouchGesture(), edge->border() == border); + } + + quint32 timestamp = 0; + + // press the finger + QFETCH(QPointF, startPos); + Test::touchDown(1, startPos, timestamp++); + QVERIFY(actionTriggeredSpy.isEmpty()); + + // move the finger + QFETCH(QPointF, delta); + Test::touchMotion(1, startPos + delta, timestamp++); + QVERIFY(actionTriggeredSpy.isEmpty()); + + // release the finger + Test::touchUp(1, timestamp++); + QVERIFY(actionTriggeredSpy.wait()); + QCOMPARE(actionTriggeredSpy.count(), 1); + + // unreserve again + s->unreserveTouch(border, &action); + for (auto edge : edges) { + QCOMPARE(edge->isReserved(), false); + QCOMPARE(edge->activatesForPointer(), false); + QCOMPARE(edge->activatesForTouchGesture(), false); + } + + // reserve another action + std::unique_ptr action2(new QAction); + s->reserveTouch(border, action2.get()); + for (auto edge : edges) { + QCOMPARE(edge->isReserved(), edge->border() == border); + QCOMPARE(edge->activatesForPointer(), false); + QCOMPARE(edge->activatesForTouchGesture(), edge->border() == border); + } + + // and unreserve by destroying + action2.reset(); + for (auto edge : edges) { + QCOMPARE(edge->isReserved(), false); + QCOMPARE(edge->activatesForPointer(), false); + QCOMPARE(edge->activatesForTouchGesture(), false); + } +} + +void ScreenEdgesTest::testPushBack_data() +{ + QTest::addColumn("border"); + QTest::addColumn("pushback"); + QTest::addColumn("trigger"); + QTest::addColumn("expected"); + + QTest::newRow("top-left-3") << ElectricTopLeft << 3 << QPoint(0, 0) << QPoint(3, 3); + QTest::newRow("top-5") << ElectricTop << 5 << QPoint(50, 0) << QPoint(50, 5); + QTest::newRow("top-right-2") << ElectricTopRight << 2 << QPoint(1279, 0) << QPoint(1277, 2); + QTest::newRow("right-10") << ElectricRight << 10 << QPoint(1279, 50) << QPoint(1269, 50); + QTest::newRow("bottom-right-5") << ElectricBottomRight << 5 << QPoint(1279, 1023) << QPoint(1274, 1018); + QTest::newRow("bottom-10") << ElectricBottom << 10 << QPoint(50, 1023) << QPoint(50, 1013); + QTest::newRow("bottom-left-3") << ElectricBottomLeft << 3 << QPoint(0, 1023) << QPoint(3, 1020); + QTest::newRow("left-10") << ElectricLeft << 10 << QPoint(0, 50) << QPoint(10, 50); + QTest::newRow("invalid") << ElectricLeft << 10 << QPoint(50, 0) << QPoint(50, 0); +} + +void ScreenEdgesTest::testPushBack() +{ + // This test verifies that the pointer will be pushed back if it approached a screen edge. + + QFETCH(int, pushback); + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + config->group("Windows").writeEntry("ElectricBorderPushbackPixels", pushback); + config->sync(); + + auto s = workspace()->screenEdges(); + s->setConfig(config); + s->reconfigure(); + + TestObject callback; + QSignalSpy spy(&callback, &TestObject::gotCallback); + + QFETCH(ElectricBorder, border); + s->reserve(border, &callback, "callback"); + + QFETCH(QPoint, trigger); + Test::pointerMotion(trigger, 0); + QVERIFY(spy.isEmpty()); + QTEST(Cursors::self()->mouse()->pos(), "expected"); +} + +void ScreenEdgesTest::testClientEdge_data() +{ + QTest::addColumn("border"); + QTest::addColumn("geometry"); + QTest::addColumn("triggerPoint"); + + QTest::newRow("top") << ElectricTop << QRect(540, 0, 200, 5) << QPointF(640, 0); + QTest::newRow("right") << ElectricRight << QRect(1275, 412, 5, 200) << QPointF(1279, 512); + QTest::newRow("bottom") << ElectricBottom << QRect(540, 1019, 200, 5) << QPointF(640, 1023); + QTest::newRow("left") << ElectricLeft << QRect(0, 412, 5, 200) << QPointF(0, 512); +} + +void ScreenEdgesTest::testClientEdge() +{ + // This test verifies that a window will be shown when its screen edge is activated. + QFETCH(QRect, geometry); + + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + Window *window = Test::renderAndWaitForShown(surface.get(), geometry.size(), Qt::red); + QVERIFY(window); + QVERIFY(window->isActive()); + window->move(geometry.topLeft()); + + // Reserve an electric border. + QFETCH(ElectricBorder, border); + workspace()->screenEdges()->reserve(window, border); + + // Hide the window. + window->hideClient(); + QVERIFY(window->isHiddenInternal()); + + // Trigger the screen edge. + QFETCH(QPointF, triggerPoint); + quint32 timestamp = 0; + Test::pointerMotion(triggerPoint, timestamp); + QVERIFY(window->isHiddenInternal()); + + timestamp += 150 + 1; + Test::pointerMotion(triggerPoint, timestamp); + QTRY_VERIFY(!window->isHiddenInternal()); +} + +void ScreenEdgesTest::testObjectEdge_data() +{ + QTest::addColumn("border"); + QTest::addColumn("triggerPoint"); + QTest::addColumn("delta"); + + QTest::newRow("top") << ElectricTop << QPointF(640, 0) << QPointF(0, 50); + QTest::newRow("right") << ElectricRight << QPointF(1279, 512) << QPointF(-50, 0); + QTest::newRow("bottom") << ElectricBottom << QPointF(640, 1023) << QPointF(0, -50); + QTest::newRow("left") << ElectricLeft << QPointF(0, 512) << QPointF(50, 0); +} + +void ScreenEdgesTest::testObjectEdge() +{ + // This test verifies that a screen edge reserved by a script or any QObject is activated. + + TestObject callback; + QSignalSpy spy(&callback, &TestObject::gotCallback); + + // Reserve a screen edge border. + QFETCH(ElectricBorder, border); + workspace()->screenEdges()->reserve(border, &callback, "callback"); + + QFETCH(QPointF, triggerPoint); + QFETCH(QPointF, delta); + + // doesn't trigger as the edge was not triggered yet + qint64 timestamp = 0; + Test::pointerMotion(triggerPoint + delta, timestamp); + QVERIFY(spy.isEmpty()); + + // test doesn't trigger due to too much offset + timestamp += 160; + Test::pointerMotion(triggerPoint, timestamp); + QVERIFY(spy.isEmpty()); + + // doesn't activate as we are waiting too short + timestamp += 50; + Test::pointerMotion(triggerPoint, timestamp); + QVERIFY(spy.isEmpty()); + + // and this one triggers + timestamp += 110; + Test::pointerMotion(triggerPoint, timestamp); + QVERIFY(!spy.isEmpty()); + + // now let's try to trigger again + timestamp += 351; + Test::pointerMotion(triggerPoint, timestamp); + QCOMPARE(spy.count(), 1); + + // it's still under the reactivation + timestamp += 50; + Test::pointerMotion(triggerPoint, timestamp); + QCOMPARE(spy.count(), 1); + + // now it should trigger again + timestamp += 250; + Test::pointerMotion(triggerPoint, timestamp); + QCOMPARE(spy.count(), 2); +} + +} // namespace KWin + +WAYLANDTEST_MAIN(KWin::ScreenEdgesTest) +#include "screenedges_test.moc" diff --git a/autotests/integration/screens_test.cpp b/autotests/integration/screens_test.cpp new file mode 100644 index 0000000..d8a9e8d --- /dev/null +++ b/autotests/integration/screens_test.cpp @@ -0,0 +1,190 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2014 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "screens.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_screens-0"); + +class ScreensTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testCurrent_data(); + void testCurrent(); + void testCurrentWithFollowsMouse_data(); + void testCurrentWithFollowsMouse(); + void testCurrentPoint_data(); + void testCurrentPoint(); +}; + +void ScreensTest::initTestCase() +{ + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); +} + +void ScreensTest::init() +{ + workspace()->setActiveOutput(QPoint(640, 512)); + KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512)); + + QVERIFY(Test::setupWaylandConnection()); +} + +static void purge(KConfig *config) +{ + const QStringList groups = config->groupList(); + for (const QString &group : groups) { + config->deleteGroup(group); + } +} + +void ScreensTest::cleanup() +{ + // Destroy the wayland connection of the test window. + Test::destroyWaylandConnection(); + + // Wipe the screens config clean. + auto config = kwinApp()->config(); + purge(config.data()); + config->sync(); + workspace()->slotReconfigure(); + + // Reset the screen layout of the test environment. + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); +} + +void ScreensTest::testCurrent_data() +{ + QTest::addColumn("currentId"); + + QTest::newRow("first") << 0; + QTest::newRow("second") << 1; +} + +void ScreensTest::testCurrent() +{ + QFETCH(int, currentId); + Output *output = workspace()->outputs().at(currentId); + + // Disable "active screen follows mouse" + auto group = kwinApp()->config()->group("Windows"); + group.writeEntry("ActiveMouseScreen", false); + group.sync(); + workspace()->slotReconfigure(); + + workspace()->setActiveOutput(output); + QCOMPARE(workspace()->activeOutput(), output); +} + +void ScreensTest::testCurrentWithFollowsMouse_data() +{ + QTest::addColumn>("geometries"); + QTest::addColumn("cursorPos"); + QTest::addColumn("expectedId"); + + QTest::newRow("empty") << QVector{{QRect()}} << QPoint(100, 100) << 0; + QTest::newRow("cloned") << QVector{{QRect{0, 0, 200, 100}, QRect{0, 0, 200, 100}}} << QPoint(50, 50) << 0; + QTest::newRow("adjacent-0") << QVector{{QRect{0, 0, 200, 100}, QRect{200, 100, 400, 300}}} << QPoint(199, 99) << 0; + QTest::newRow("adjacent-1") << QVector{{QRect{0, 0, 200, 100}, QRect{200, 100, 400, 300}}} << QPoint(200, 100) << 1; + QTest::newRow("gap") << QVector{{QRect{0, 0, 10, 20}, QRect{20, 40, 10, 20}}} << QPoint(15, 30) << 1; +} + +void ScreensTest::testCurrentWithFollowsMouse() +{ + QSignalSpy changedSpy(workspace()->screens(), &Screens::changed); + + // Enable "active screen follows mouse" + auto group = kwinApp()->config()->group("Windows"); + group.writeEntry("ActiveMouseScreen", true); + group.sync(); + workspace()->slotReconfigure(); + + QFETCH(QVector, geometries); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::QueuedConnection, + Q_ARG(int, geometries.count()), Q_ARG(QVector, geometries)); + QVERIFY(changedSpy.wait()); + + QFETCH(QPoint, cursorPos); + KWin::Cursors::self()->mouse()->setPos(cursorPos); + + QFETCH(int, expectedId); + Output *expected = workspace()->outputs().at(expectedId); + QCOMPARE(workspace()->activeOutput(), expected); +} + +void ScreensTest::testCurrentPoint_data() +{ + QTest::addColumn>("geometries"); + QTest::addColumn("cursorPos"); + QTest::addColumn("expectedId"); + + QTest::newRow("empty") << QVector{{QRect()}} << QPoint(100, 100) << 0; + QTest::newRow("cloned") << QVector{{QRect{0, 0, 200, 100}, QRect{0, 0, 200, 100}}} << QPoint(50, 50) << 0; + QTest::newRow("adjacent-0") << QVector{{QRect{0, 0, 200, 100}, QRect{200, 100, 400, 300}}} << QPoint(199, 99) << 0; + QTest::newRow("adjacent-1") << QVector{{QRect{0, 0, 200, 100}, QRect{200, 100, 400, 300}}} << QPoint(200, 100) << 1; + QTest::newRow("gap") << QVector{{QRect{0, 0, 10, 20}, QRect{20, 40, 10, 20}}} << QPoint(15, 30) << 1; +} + +void ScreensTest::testCurrentPoint() +{ + QSignalSpy changedSpy(workspace()->screens(), &KWin::Screens::changed); + + QFETCH(QVector, geometries); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::QueuedConnection, + Q_ARG(int, geometries.count()), Q_ARG(QVector, geometries)); + QVERIFY(changedSpy.wait()); + + // Disable "active screen follows mouse" + auto group = kwinApp()->config()->group("Windows"); + group.writeEntry("ActiveMouseScreen", false); + group.sync(); + workspace()->slotReconfigure(); + + QFETCH(QPoint, cursorPos); + workspace()->setActiveOutput(cursorPos); + + QFETCH(int, expectedId); + Output *expected = workspace()->outputs().at(expectedId); + QCOMPARE(workspace()->activeOutput(), expected); +} + +} // namespace KWin + +WAYLANDTEST_MAIN(KWin::ScreensTest) +#include "screens_test.moc" diff --git a/autotests/integration/scripting/CMakeLists.txt b/autotests/integration/scripting/CMakeLists.txt new file mode 100644 index 0000000..29ea184 --- /dev/null +++ b/autotests/integration/scripting/CMakeLists.txt @@ -0,0 +1,2 @@ +integrationTest(NAME testScriptingScreenEdge SRCS screenedge_test.cpp) +integrationTest(WAYLAND_ONLY NAME testMinimizeAllScript SRCS minimizeall_test.cpp) diff --git a/autotests/integration/scripting/minimizeall_test.cpp b/autotests/integration/scripting/minimizeall_test.cpp new file mode 100644 index 0000000..c122d3c --- /dev/null +++ b/autotests/integration/scripting/minimizeall_test.cpp @@ -0,0 +1,155 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2019 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "scripting/scripting.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include +#include + +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_minimizeall-0"); +static const QString s_scriptName = QStringLiteral("minimizeall"); + +class MinimizeAllScriptTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testMinimizeUnminimize(); +}; + +void MinimizeAllScriptTest::initTestCase() +{ + qputenv("XDG_DATA_DIRS", QCoreApplication::applicationDirPath().toUtf8()); + + qRegisterMetaType(); + + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); +} + +static QString locateMainScript(const QString &pluginName) +{ + const QList offers = KPackage::PackageLoader::self()->findPackages( + QStringLiteral("KWin/Script"), + QStringLiteral("kwin/scripts"), + [&](const KPluginMetaData &metaData) { + return metaData.pluginId() == pluginName; + }); + if (offers.isEmpty()) { + return QString(); + } + const KPluginMetaData &metaData = offers.first(); + const QString mainScriptFileName = metaData.value(QStringLiteral("X-Plasma-MainScript")); + const QFileInfo metaDataFileInfo(metaData.fileName()); + return metaDataFileInfo.path() + QLatin1String("/contents/") + mainScriptFileName; +} + +void MinimizeAllScriptTest::init() +{ + QVERIFY(Test::setupWaylandConnection()); + + Scripting::self()->loadScript(locateMainScript(s_scriptName), s_scriptName); + QTRY_VERIFY(Scripting::self()->isScriptLoaded(s_scriptName)); + + AbstractScript *script = Scripting::self()->findScript(s_scriptName); + QVERIFY(script); + QSignalSpy runningChangedSpy(script, &AbstractScript::runningChanged); + script->run(); + QTRY_COMPARE(runningChangedSpy.count(), 1); +} + +void MinimizeAllScriptTest::cleanup() +{ + Test::destroyWaylandConnection(); + + Scripting::self()->unloadScript(s_scriptName); + QTRY_VERIFY(!Scripting::self()->isScriptLoaded(s_scriptName)); +} + +void MinimizeAllScriptTest::testMinimizeUnminimize() +{ + // This test verifies that all windows are minimized when Meta+Shift+D + // is pressed, and unminimized when the shortcut is pressed once again. + + using namespace KWayland::Client; + + // Create a couple of test windows. + std::unique_ptr surface1(Test::createSurface()); + std::unique_ptr shellSurface1(Test::createXdgToplevelSurface(surface1.get())); + Window *window1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::blue); + QVERIFY(window1); + QVERIFY(window1->isActive()); + QVERIFY(window1->isMinimizable()); + + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + Window *window2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::red); + QVERIFY(window2); + QVERIFY(window2->isActive()); + QVERIFY(window2->isMinimizable()); + + // Minimize the windows. + quint32 timestamp = 1; + Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++); + Test::keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); + Test::keyboardKeyPressed(KEY_D, timestamp++); + Test::keyboardKeyReleased(KEY_D, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++); + + QTRY_VERIFY(window1->isMinimized()); + QTRY_VERIFY(window2->isMinimized()); + + // Unminimize the windows. + Test::keyboardKeyPressed(KEY_LEFTMETA, timestamp++); + Test::keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); + Test::keyboardKeyPressed(KEY_D, timestamp++); + Test::keyboardKeyReleased(KEY_D, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTMETA, timestamp++); + + QTRY_VERIFY(!window1->isMinimized()); + QTRY_VERIFY(!window2->isMinimized()); + + // Destroy test windows. + shellSurface2.reset(); + QVERIFY(Test::waitForWindowDestroyed(window2)); + shellSurface1.reset(); + QVERIFY(Test::waitForWindowDestroyed(window1)); +} + +} + +WAYLANDTEST_MAIN(KWin::MinimizeAllScriptTest) +#include "minimizeall_test.moc" diff --git a/autotests/integration/scripting/screenedge_test.cpp b/autotests/integration/scripting/screenedge_test.cpp new file mode 100644 index 0000000..354f07d --- /dev/null +++ b/autotests/integration/scripting/screenedge_test.cpp @@ -0,0 +1,280 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/platform.h" +#include "cursor.h" +#include "effectloader.h" +#include "scripting/scripting.h" +#include "wayland_server.h" +#include "workspace.h" + +#define private public +#include "screenedge.h" +#undef private + +#include + +Q_DECLARE_METATYPE(KWin::ElectricBorder) + +using namespace KWin; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_scripting_screenedge-0"); + +class ScreenEdgeTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testEdge_data(); + void testEdge(); + void testTouchEdge_data(); + void testTouchEdge(); + void testEdgeUnregister(); + void testDeclarativeTouchEdge(); + +private: + void triggerConfigReload(); +}; + +void ScreenEdgeTest::initTestCase() +{ + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + // empty config to have defaults + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + + // disable all effects to prevent them grabbing edges + KConfigGroup plugins(config, QStringLiteral("Plugins")); + const auto builtinNames = EffectLoader().listOfKnownEffects(); + for (QString name : builtinNames) { + plugins.writeEntry(name + QStringLiteral("Enabled"), false); + } + + // disable electric border pushback + config->group("Windows").writeEntry("ElectricBorderPushbackPixels", 0); + config->group("TabBox").writeEntry("TouchBorderActivate", int(ElectricNone)); + + config->sync(); + kwinApp()->setConfig(config); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + QVERIFY(Scripting::self()); + + workspace()->screenEdges()->setTimeThreshold(0); + workspace()->screenEdges()->setReActivationThreshold(0); +} + +void ScreenEdgeTest::init() +{ + KWin::Cursors::self()->mouse()->setPos(640, 512); + if (workspace()->showingDesktop()) { + workspace()->slotToggleShowDesktop(); + } + QVERIFY(!workspace()->showingDesktop()); +} + +void ScreenEdgeTest::cleanup() +{ + // try to unload the script + const QStringList scripts = {QFINDTESTDATA("./scripts/screenedge.js"), QFINDTESTDATA("./scripts/screenedgeunregister.js"), QFINDTESTDATA("./scripts/touchScreenedge.js")}; + for (const QString &script : scripts) { + if (!script.isEmpty()) { + if (Scripting::self()->isScriptLoaded(script)) { + QVERIFY(Scripting::self()->unloadScript(script)); + QTRY_VERIFY(!Scripting::self()->isScriptLoaded(script)); + } + } + } +} + +void ScreenEdgeTest::testEdge_data() +{ + QTest::addColumn("edge"); + QTest::addColumn("triggerPos"); + + QTest::newRow("Top") << KWin::ElectricTop << QPoint(512, 0); + QTest::newRow("TopRight") << KWin::ElectricTopRight << QPoint(1279, 0); + QTest::newRow("Right") << KWin::ElectricRight << QPoint(1279, 512); + QTest::newRow("BottomRight") << KWin::ElectricBottomRight << QPoint(1279, 1023); + QTest::newRow("Bottom") << KWin::ElectricBottom << QPoint(512, 1023); + QTest::newRow("BottomLeft") << KWin::ElectricBottomLeft << QPoint(0, 1023); + QTest::newRow("Left") << KWin::ElectricLeft << QPoint(0, 512); + QTest::newRow("TopLeft") << KWin::ElectricTopLeft << QPoint(0, 0); + + // repeat a row to show previously unloading and re-registering works + QTest::newRow("Top") << KWin::ElectricTop << QPoint(512, 0); +} + +void ScreenEdgeTest::testEdge() +{ + const QString scriptToLoad = QFINDTESTDATA("./scripts/screenedge.js"); + QVERIFY(!scriptToLoad.isEmpty()); + + // mock the config + auto config = kwinApp()->config(); + QFETCH(KWin::ElectricBorder, edge); + config->group(QLatin1String("Script-") + scriptToLoad).writeEntry("Edge", int(edge)); + config->sync(); + + QVERIFY(!Scripting::self()->isScriptLoaded(scriptToLoad)); + const int id = Scripting::self()->loadScript(scriptToLoad); + QVERIFY(id != -1); + QVERIFY(Scripting::self()->isScriptLoaded(scriptToLoad)); + auto s = Scripting::self()->findScript(scriptToLoad); + QVERIFY(s); + QSignalSpy runningChangedSpy(s, &AbstractScript::runningChanged); + s->run(); + QVERIFY(runningChangedSpy.wait()); + QCOMPARE(runningChangedSpy.count(), 1); + QCOMPARE(runningChangedSpy.first().first().toBool(), true); + // triggering the edge will result in show desktop being triggered + QSignalSpy showDesktopSpy(workspace(), &Workspace::showingDesktopChanged); + + // trigger the edge + QFETCH(QPoint, triggerPos); + KWin::Cursors::self()->mouse()->setPos(triggerPos); + QCOMPARE(showDesktopSpy.count(), 1); + QVERIFY(workspace()->showingDesktop()); +} + +void ScreenEdgeTest::testTouchEdge_data() +{ + QTest::addColumn("edge"); + QTest::addColumn("triggerPos"); + QTest::addColumn("motionPos"); + + QTest::newRow("Top") << KWin::ElectricTop << QPoint(50, 0) << QPoint(50, 500); + QTest::newRow("Right") << KWin::ElectricRight << QPoint(1279, 50) << QPoint(500, 50); + QTest::newRow("Bottom") << KWin::ElectricBottom << QPoint(512, 1023) << QPoint(512, 500); + QTest::newRow("Left") << KWin::ElectricLeft << QPoint(0, 50) << QPoint(500, 50); + + // repeat a row to show previously unloading and re-registering works + QTest::newRow("Top") << KWin::ElectricTop << QPoint(512, 0) << QPoint(512, 500); +} + +void ScreenEdgeTest::testTouchEdge() +{ + const QString scriptToLoad = QFINDTESTDATA("./scripts/touchScreenedge.js"); + QVERIFY(!scriptToLoad.isEmpty()); + + // mock the config + auto config = kwinApp()->config(); + QFETCH(KWin::ElectricBorder, edge); + config->group(QLatin1String("Script-") + scriptToLoad).writeEntry("Edge", int(edge)); + config->sync(); + + QVERIFY(!Scripting::self()->isScriptLoaded(scriptToLoad)); + const int id = Scripting::self()->loadScript(scriptToLoad); + QVERIFY(id != -1); + QVERIFY(Scripting::self()->isScriptLoaded(scriptToLoad)); + auto s = Scripting::self()->findScript(scriptToLoad); + QVERIFY(s); + QSignalSpy runningChangedSpy(s, &AbstractScript::runningChanged); + s->run(); + QVERIFY(runningChangedSpy.wait()); + QCOMPARE(runningChangedSpy.count(), 1); + QCOMPARE(runningChangedSpy.first().first().toBool(), true); + // triggering the edge will result in show desktop being triggered + QSignalSpy showDesktopSpy(workspace(), &Workspace::showingDesktopChanged); + + // trigger the edge + QFETCH(QPoint, triggerPos); + quint32 timestamp = 0; + Test::touchDown(0, triggerPos, timestamp++); + QFETCH(QPoint, motionPos); + Test::touchMotion(0, motionPos, timestamp++); + Test::touchUp(0, timestamp++); + QVERIFY(showDesktopSpy.wait()); + QCOMPARE(showDesktopSpy.count(), 1); + QVERIFY(workspace()->showingDesktop()); +} + +void ScreenEdgeTest::triggerConfigReload() +{ + workspace()->slotReconfigure(); +} + +void ScreenEdgeTest::testEdgeUnregister() +{ + const QString scriptToLoad = QFINDTESTDATA("./scripts/screenedgeunregister.js"); + QVERIFY(!scriptToLoad.isEmpty()); + + Scripting::self()->loadScript(scriptToLoad); + auto s = Scripting::self()->findScript(scriptToLoad); + auto configGroup = s->config(); + configGroup.writeEntry("Edge", int(KWin::ElectricLeft)); + configGroup.sync(); + const QPoint triggerPos = QPoint(0, 512); + + QSignalSpy runningChangedSpy(s, &AbstractScript::runningChanged); + s->run(); + QVERIFY(runningChangedSpy.wait()); + + QSignalSpy showDesktopSpy(workspace(), &Workspace::showingDesktopChanged); + + // trigger the edge + KWin::Cursors::self()->mouse()->setPos(triggerPos); + QCOMPARE(showDesktopSpy.count(), 1); + + // reset + KWin::Cursors::self()->mouse()->setPos(500, 500); + workspace()->slotToggleShowDesktop(); + showDesktopSpy.clear(); + + // trigger again, to show that retriggering works + KWin::Cursors::self()->mouse()->setPos(triggerPos); + QCOMPARE(showDesktopSpy.count(), 1); + + // reset + KWin::Cursors::self()->mouse()->setPos(500, 500); + workspace()->slotToggleShowDesktop(); + showDesktopSpy.clear(); + + // make the script unregister the edge + configGroup.writeEntry("mode", "unregister"); + triggerConfigReload(); + KWin::Cursors::self()->mouse()->setPos(triggerPos); + QCOMPARE(showDesktopSpy.count(), 0); // not triggered + + // force the script to unregister a non-registered edge to prove it doesn't explode + triggerConfigReload(); +} + +void ScreenEdgeTest::testDeclarativeTouchEdge() +{ + const QString scriptToLoad = QFINDTESTDATA("./scripts/screenedgetouch.qml"); + QVERIFY(!scriptToLoad.isEmpty()); + QVERIFY(Scripting::self()->loadDeclarativeScript(scriptToLoad) != -1); + QVERIFY(Scripting::self()->isScriptLoaded(scriptToLoad)); + + auto s = Scripting::self()->findScript(scriptToLoad); + QSignalSpy runningChangedSpy(s, &AbstractScript::runningChanged); + s->run(); + QTRY_COMPARE(runningChangedSpy.count(), 1); + + QSignalSpy showDesktopSpy(workspace(), &Workspace::showingDesktopChanged); + + // Trigger the edge through touch + quint32 timestamp = 0; + Test::touchDown(0, QPointF(0, 50), timestamp++); + Test::touchMotion(0, QPointF(500, 50), timestamp++); + Test::touchUp(0, timestamp++); + + QVERIFY(showDesktopSpy.wait()); +} + +WAYLANDTEST_MAIN(ScreenEdgeTest) +#include "screenedge_test.moc" diff --git a/autotests/integration/scripting/scripts/screenedge.js b/autotests/integration/scripting/scripts/screenedge.js new file mode 100644 index 0000000..49c995a --- /dev/null +++ b/autotests/integration/scripting/scripts/screenedge.js @@ -0,0 +1 @@ +registerScreenEdge(readConfig("Edge", 1), workspace.slotToggleShowDesktop); diff --git a/autotests/integration/scripting/scripts/screenedgetouch.qml b/autotests/integration/scripting/scripts/screenedgetouch.qml new file mode 100644 index 0000000..04c136a --- /dev/null +++ b/autotests/integration/scripting/scripts/screenedgetouch.qml @@ -0,0 +1,10 @@ +import QtQuick 2.0; +import org.kde.kwin 2.0; + +ScreenEdgeItem { + edge: ScreenEdgeItem.LeftEdge + mode: ScreenEdgeItem.Touch + onActivated: { + workspace.slotToggleShowDesktop(); + } +} diff --git a/autotests/integration/scripting/scripts/screenedgeunregister.js b/autotests/integration/scripting/scripts/screenedgeunregister.js new file mode 100644 index 0000000..240ad18 --- /dev/null +++ b/autotests/integration/scripting/scripts/screenedgeunregister.js @@ -0,0 +1,12 @@ +function init() { + const edge = readConfig("Edge", 1); + if (readConfig("mode", "") == "unregister") { + unregisterScreenEdge(edge); + } else { + registerScreenEdge(edge, workspace.slotToggleShowDesktop); + } +} +options.configChanged.connect(init); + +init(); + diff --git a/autotests/integration/scripting/scripts/touchScreenedge.js b/autotests/integration/scripting/scripts/touchScreenedge.js new file mode 100644 index 0000000..0d762a9 --- /dev/null +++ b/autotests/integration/scripting/scripts/touchScreenedge.js @@ -0,0 +1 @@ +registerTouchScreenEdge(readConfig("Edge", 1), workspace.slotToggleShowDesktop); diff --git a/autotests/integration/shade_test.cpp b/autotests/integration/shade_test.cpp new file mode 100644 index 0000000..7967aac --- /dev/null +++ b/autotests/integration/shade_test.cpp @@ -0,0 +1,127 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "wayland_server.h" +#include "workspace.h" +#include "x11window.h" +#include + +#include + +#include +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_shade-0"); + +class ShadeTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void testShadeGeometry(); +}; + +void ShadeTest::initTestCase() +{ + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); + setenv("QT_QPA_PLATFORM", "wayland", true); +} + +void ShadeTest::init() +{ + workspace()->setActiveOutput(QPoint(640, 512)); + Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void ShadeTest::testShadeGeometry() +{ + // this test verifies that the geometry is properly restored after shading + // see BUG: 362501 + // create an xcb window + struct XcbConnectionDeleter + { + void operator()(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } + }; + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QVERIFY(window->isDecorated()); + QVERIFY(window->isShadeable()); + QVERIFY(!window->isShade()); + QVERIFY(window->isActive()); + + // now shade the window + const QRectF geoBeforeShade = window->frameGeometry(); + QVERIFY(geoBeforeShade.isValid()); + QVERIFY(!geoBeforeShade.isEmpty()); + workspace()->slotWindowShade(); + QVERIFY(window->isShade()); + QVERIFY(window->frameGeometry() != geoBeforeShade); + // and unshade again + workspace()->slotWindowShade(); + QVERIFY(!window->isShade()); + QCOMPARE(window->frameGeometry(), geoBeforeShade); + + // and destroy the window again + xcb_unmap_window(c.get(), windowId); + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + c.reset(); + + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + QVERIFY(windowClosedSpy.wait()); +} + +} + +WAYLANDTEST_MAIN(KWin::ShadeTest) +#include "shade_test.moc" diff --git a/autotests/integration/showing_desktop_test.cpp b/autotests/integration/showing_desktop_test.cpp new file mode 100644 index 0000000..1bbd79d --- /dev/null +++ b/autotests/integration/showing_desktop_test.cpp @@ -0,0 +1,114 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2017 Martin Flöser + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/platform.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include +#include + +using namespace KWin; +using namespace KWayland::Client; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_showing_desktop-0"); + +class ShowingDesktopTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testRestoreFocus(); + void testRestoreFocusWithDesktopWindow(); +}; + +void ShowingDesktopTest::initTestCase() +{ + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); +} + +void ShowingDesktopTest::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::PlasmaShell)); +} + +void ShowingDesktopTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void ShowingDesktopTest::testRestoreFocus() +{ + std::unique_ptr surface1(Test::createSurface()); + std::unique_ptr shellSurface1(Test::createXdgToplevelSurface(surface1.get())); + auto window1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::blue); + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + auto window2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::blue); + QVERIFY(window1 != window2); + + QCOMPARE(workspace()->activeWindow(), window2); + workspace()->slotToggleShowDesktop(); + QVERIFY(workspace()->showingDesktop()); + workspace()->slotToggleShowDesktop(); + QVERIFY(!workspace()->showingDesktop()); + + QVERIFY(workspace()->activeWindow()); + QCOMPARE(workspace()->activeWindow(), window2); +} + +void ShowingDesktopTest::testRestoreFocusWithDesktopWindow() +{ + // first create a desktop window + + std::unique_ptr desktopSurface(Test::createSurface()); + QVERIFY(desktopSurface != nullptr); + std::unique_ptr desktopShellSurface(Test::createXdgToplevelSurface(desktopSurface.get())); + QVERIFY(desktopSurface != nullptr); + std::unique_ptr plasmaSurface(Test::waylandPlasmaShell()->createSurface(desktopSurface.get())); + QVERIFY(plasmaSurface != nullptr); + plasmaSurface->setRole(PlasmaShellSurface::Role::Desktop); + + auto desktop = Test::renderAndWaitForShown(desktopSurface.get(), QSize(100, 50), Qt::blue); + QVERIFY(desktop); + QVERIFY(desktop->isDesktop()); + + // now create some windows + std::unique_ptr surface1(Test::createSurface()); + std::unique_ptr shellSurface1(Test::createXdgToplevelSurface(surface1.get())); + auto window1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::blue); + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + auto window2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::blue); + QVERIFY(window1 != window2); + + QCOMPARE(workspace()->activeWindow(), window2); + workspace()->slotToggleShowDesktop(); + QVERIFY(workspace()->showingDesktop()); + QCOMPARE(workspace()->activeWindow(), desktop); + workspace()->slotToggleShowDesktop(); + QVERIFY(!workspace()->showingDesktop()); + + QVERIFY(workspace()->activeWindow()); + QCOMPARE(workspace()->activeWindow(), window2); +} + +WAYLANDTEST_MAIN(ShowingDesktopTest) +#include "showing_desktop_test.moc" diff --git a/autotests/integration/stacking_order_test.cpp b/autotests/integration/stacking_order_test.cpp new file mode 100644 index 0000000..34bbc42 --- /dev/null +++ b/autotests/integration/stacking_order_test.cpp @@ -0,0 +1,857 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2018 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "kwin_wayland_test.h" + +#include "atoms.h" +#include "core/platform.h" +#include "deleted.h" +#include "main.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" +#include "x11window.h" + +#include +#include + +#include +#include + +using namespace KWin; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_stacking_order-0"); + +class StackingOrderTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testTransientIsAboveParent(); + void testRaiseTransient(); + void testDeletedTransient(); + + void testGroupTransientIsAboveWindowGroup(); + void testRaiseGroupTransient(); + void testDeletedGroupTransient(); + void testDontKeepAboveNonModalDialogGroupTransients(); + + void testKeepAbove(); + void testKeepBelow(); +}; + +void StackingOrderTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); +} + +void StackingOrderTest::init() +{ + QVERIFY(Test::setupWaylandConnection()); +} + +void StackingOrderTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void StackingOrderTest::testTransientIsAboveParent() +{ + // This test verifies that transients are always above their parents. + + // Create the parent. + std::unique_ptr parentSurface = Test::createSurface(); + QVERIFY(parentSurface); + Test::XdgToplevel *parentShellSurface = Test::createXdgToplevelSurface(parentSurface.get(), parentSurface.get()); + QVERIFY(parentShellSurface); + Window *parent = Test::renderAndWaitForShown(parentSurface.get(), QSize(256, 256), Qt::blue); + QVERIFY(parent); + QVERIFY(parent->isActive()); + QVERIFY(!parent->isTransient()); + + // Initially, the stacking order should contain only the parent window. + QCOMPARE(workspace()->stackingOrder(), (QList{parent})); + + // Create the transient. + std::unique_ptr transientSurface = Test::createSurface(); + QVERIFY(transientSurface); + Test::XdgToplevel *transientShellSurface = Test::createXdgToplevelSurface(transientSurface.get(), transientSurface.get()); + QVERIFY(transientShellSurface); + transientShellSurface->set_parent(parentShellSurface->object()); + Window *transient = Test::renderAndWaitForShown(transientSurface.get(), QSize(128, 128), Qt::red); + QVERIFY(transient); + QVERIFY(transient->isActive()); + QVERIFY(transient->isTransient()); + + // The transient should be above the parent. + QCOMPARE(workspace()->stackingOrder(), (QList{parent, transient})); + + // The transient still stays above the parent if we activate the latter. + workspace()->activateWindow(parent); + QTRY_VERIFY(parent->isActive()); + QTRY_VERIFY(!transient->isActive()); + QCOMPARE(workspace()->stackingOrder(), (QList{parent, transient})); +} + +void StackingOrderTest::testRaiseTransient() +{ + // This test verifies that both the parent and the transient will be + // raised if either one of them is activated. + + // Create the parent. + std::unique_ptr parentSurface = Test::createSurface(); + QVERIFY(parentSurface); + Test::XdgToplevel *parentShellSurface = Test::createXdgToplevelSurface(parentSurface.get(), parentSurface.get()); + QVERIFY(parentShellSurface); + Window *parent = Test::renderAndWaitForShown(parentSurface.get(), QSize(256, 256), Qt::blue); + QVERIFY(parent); + QVERIFY(parent->isActive()); + QVERIFY(!parent->isTransient()); + + // Initially, the stacking order should contain only the parent window. + QCOMPARE(workspace()->stackingOrder(), (QList{parent})); + + // Create the transient. + std::unique_ptr transientSurface = Test::createSurface(); + QVERIFY(transientSurface); + Test::XdgToplevel *transientShellSurface = Test::createXdgToplevelSurface(transientSurface.get(), transientSurface.get()); + QVERIFY(transientShellSurface); + transientShellSurface->set_parent(parentShellSurface->object()); + Window *transient = Test::renderAndWaitForShown(transientSurface.get(), QSize(128, 128), Qt::red); + QVERIFY(transient); + QTRY_VERIFY(transient->isActive()); + QVERIFY(transient->isTransient()); + + // The transient should be above the parent. + QCOMPARE(workspace()->stackingOrder(), (QList{parent, transient})); + + // Create a window that doesn't have any relationship to the parent or the transient. + std::unique_ptr anotherSurface = Test::createSurface(); + QVERIFY(anotherSurface); + Test::XdgToplevel *anotherShellSurface = Test::createXdgToplevelSurface(anotherSurface.get(), anotherSurface.get()); + QVERIFY(anotherShellSurface); + Window *anotherWindow = Test::renderAndWaitForShown(anotherSurface.get(), QSize(128, 128), Qt::green); + QVERIFY(anotherWindow); + QVERIFY(anotherWindow->isActive()); + QVERIFY(!anotherWindow->isTransient()); + + // The newly created surface has to be above both the parent and the transient. + QCOMPARE(workspace()->stackingOrder(), (QList{parent, transient, anotherWindow})); + + // If we activate the parent, the transient should be raised too. + workspace()->activateWindow(parent); + QTRY_VERIFY(parent->isActive()); + QTRY_VERIFY(!transient->isActive()); + QTRY_VERIFY(!anotherWindow->isActive()); + QCOMPARE(workspace()->stackingOrder(), (QList{anotherWindow, parent, transient})); + + // Go back to the initial setup. + workspace()->activateWindow(anotherWindow); + QTRY_VERIFY(!parent->isActive()); + QTRY_VERIFY(!transient->isActive()); + QTRY_VERIFY(anotherWindow->isActive()); + QCOMPARE(workspace()->stackingOrder(), (QList{parent, transient, anotherWindow})); + + // If we activate the transient, the parent should be raised too. + workspace()->activateWindow(transient); + QTRY_VERIFY(!parent->isActive()); + QTRY_VERIFY(transient->isActive()); + QTRY_VERIFY(!anotherWindow->isActive()); + QCOMPARE(workspace()->stackingOrder(), (QList{anotherWindow, parent, transient})); +} + +struct WindowUnrefDeleter +{ + void operator()(Deleted *d) + { + if (d != nullptr) { + d->unrefWindow(); + } + } +}; + +void StackingOrderTest::testDeletedTransient() +{ + // This test verifies that deleted transients are kept above their + // old parents. + + // Create the parent. + std::unique_ptr parentSurface = Test::createSurface(); + QVERIFY(parentSurface); + Test::XdgToplevel *parentShellSurface = + Test::createXdgToplevelSurface(parentSurface.get(), parentSurface.get()); + QVERIFY(parentShellSurface); + Window *parent = Test::renderAndWaitForShown(parentSurface.get(), QSize(256, 256), Qt::blue); + QVERIFY(parent); + QVERIFY(parent->isActive()); + QVERIFY(!parent->isTransient()); + + QCOMPARE(workspace()->stackingOrder(), (QList{parent})); + + // Create the first transient. + std::unique_ptr transient1Surface = Test::createSurface(); + QVERIFY(transient1Surface); + Test::XdgToplevel *transient1ShellSurface = Test::createXdgToplevelSurface(transient1Surface.get(), transient1Surface.get()); + QVERIFY(transient1ShellSurface); + transient1ShellSurface->set_parent(parentShellSurface->object()); + Window *transient1 = Test::renderAndWaitForShown(transient1Surface.get(), QSize(128, 128), Qt::red); + QVERIFY(transient1); + QTRY_VERIFY(transient1->isActive()); + QVERIFY(transient1->isTransient()); + QCOMPARE(transient1->transientFor(), parent); + + QCOMPARE(workspace()->stackingOrder(), (QList{parent, transient1})); + + // Create the second transient. + std::unique_ptr transient2Surface = Test::createSurface(); + QVERIFY(transient2Surface); + Test::XdgToplevel *transient2ShellSurface = Test::createXdgToplevelSurface(transient2Surface.get(), transient2Surface.get()); + QVERIFY(transient2ShellSurface); + transient2ShellSurface->set_parent(transient1ShellSurface->object()); + Window *transient2 = Test::renderAndWaitForShown(transient2Surface.get(), QSize(128, 128), Qt::red); + QVERIFY(transient2); + QTRY_VERIFY(transient2->isActive()); + QVERIFY(transient2->isTransient()); + QCOMPARE(transient2->transientFor(), transient1); + + QCOMPARE(workspace()->stackingOrder(), (QList{parent, transient1, transient2})); + + // Activate the parent, both transients have to be above it. + workspace()->activateWindow(parent); + QTRY_VERIFY(parent->isActive()); + QTRY_VERIFY(!transient1->isActive()); + QTRY_VERIFY(!transient2->isActive()); + + // Close the top-most transient. + connect(transient2, &Window::windowClosed, this, [](Window *original, Deleted *deleted) { + Q_UNUSED(original) + deleted->refWindow(); + }); + + QSignalSpy windowClosedSpy(transient2, &Window::windowClosed); + delete transient2ShellSurface; + transient2Surface.reset(); + QVERIFY(windowClosedSpy.wait()); + + std::unique_ptr deletedTransient( + windowClosedSpy.first().at(1).value()); + QVERIFY(deletedTransient.get()); + + // The deleted transient still has to be above its old parent (transient1). + QTRY_VERIFY(parent->isActive()); + QTRY_VERIFY(!transient1->isActive()); + QCOMPARE(workspace()->stackingOrder(), (QList{parent, transient1, deletedTransient.get()})); +} + +static xcb_window_t createGroupWindow(xcb_connection_t *conn, + const QRect &geometry, + xcb_window_t leaderWid = XCB_WINDOW_NONE) +{ + xcb_window_t wid = xcb_generate_id(conn); + xcb_create_window( + conn, // c + XCB_COPY_FROM_PARENT, // depth + wid, // wid + rootWindow(), // parent + geometry.x(), // x + geometry.y(), // y + geometry.width(), // width + geometry.height(), // height + 0, // border_width + XCB_WINDOW_CLASS_INPUT_OUTPUT, // _class + XCB_COPY_FROM_PARENT, // visual + 0, // value_mask + nullptr // value_list + ); + + xcb_size_hints_t sizeHints = {}; + xcb_icccm_size_hints_set_position(&sizeHints, 1, geometry.x(), geometry.y()); + xcb_icccm_size_hints_set_size(&sizeHints, 1, geometry.width(), geometry.height()); + xcb_icccm_set_wm_normal_hints(conn, wid, &sizeHints); + + if (leaderWid == XCB_WINDOW_NONE) { + leaderWid = wid; + } + + xcb_change_property( + conn, // c + XCB_PROP_MODE_REPLACE, // mode + wid, // window + atoms->wm_client_leader, // property + XCB_ATOM_WINDOW, // type + 32, // format + 1, // data_len + &leaderWid // data + ); + + return wid; +} + +struct XcbConnectionDeleter +{ + void operator()(xcb_connection_t *c) + { + xcb_disconnect(c); + } +}; + +void StackingOrderTest::testGroupTransientIsAboveWindowGroup() +{ + // This test verifies that group transients are always above other + // window group members. + + const QRect geometry = QRect(0, 0, 128, 128); + + std::unique_ptr conn( + xcb_connect(nullptr, nullptr)); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + + // Create the group leader. + xcb_window_t leaderWid = createGroupWindow(conn.get(), geometry); + xcb_map_window(conn.get(), leaderWid); + xcb_flush(conn.get()); + + QVERIFY(windowCreatedSpy.wait()); + X11Window *leader = windowCreatedSpy.first().first().value(); + QVERIFY(leader); + QVERIFY(leader->isActive()); + QCOMPARE(leader->window(), leaderWid); + QVERIFY(!leader->isTransient()); + + QCOMPARE(workspace()->stackingOrder(), (QList{leader})); + + // Create another group member. + windowCreatedSpy.clear(); + xcb_window_t member1Wid = createGroupWindow(conn.get(), geometry, leaderWid); + xcb_map_window(conn.get(), member1Wid); + xcb_flush(conn.get()); + + QVERIFY(windowCreatedSpy.wait()); + X11Window *member1 = windowCreatedSpy.first().first().value(); + QVERIFY(member1); + QVERIFY(member1->isActive()); + QCOMPARE(member1->window(), member1Wid); + QCOMPARE(member1->group(), leader->group()); + QVERIFY(!member1->isTransient()); + + QCOMPARE(workspace()->stackingOrder(), (QList{leader, member1})); + + // Create yet another group member. + windowCreatedSpy.clear(); + xcb_window_t member2Wid = createGroupWindow(conn.get(), geometry, leaderWid); + xcb_map_window(conn.get(), member2Wid); + xcb_flush(conn.get()); + + QVERIFY(windowCreatedSpy.wait()); + X11Window *member2 = windowCreatedSpy.first().first().value(); + QVERIFY(member2); + QVERIFY(member2->isActive()); + QCOMPARE(member2->window(), member2Wid); + QCOMPARE(member2->group(), leader->group()); + QVERIFY(!member2->isTransient()); + + QCOMPARE(workspace()->stackingOrder(), (QList{leader, member1, member2})); + + // Create a group transient. + windowCreatedSpy.clear(); + xcb_window_t transientWid = createGroupWindow(conn.get(), geometry, leaderWid); + xcb_icccm_set_wm_transient_for(conn.get(), transientWid, rootWindow()); + + // Currently, we have some weird bug workaround: if a group transient + // is a non-modal dialog, then it won't be kept above its window group. + // We need to explicitly specify window type, otherwise the window type + // will be deduced to _NET_WM_WINDOW_TYPE_DIALOG because we set transient + // for before (the EWMH spec says to do that). + xcb_atom_t net_wm_window_type = Xcb::Atom( + QByteArrayLiteral("_NET_WM_WINDOW_TYPE"), false, conn.get()); + xcb_atom_t net_wm_window_type_normal = Xcb::Atom( + QByteArrayLiteral("_NET_WM_WINDOW_TYPE_NORMAL"), false, conn.get()); + xcb_change_property( + conn.get(), // c + XCB_PROP_MODE_REPLACE, // mode + transientWid, // window + net_wm_window_type, // property + XCB_ATOM_ATOM, // type + 32, // format + 1, // data_len + &net_wm_window_type_normal // data + ); + + xcb_map_window(conn.get(), transientWid); + xcb_flush(conn.get()); + + QVERIFY(windowCreatedSpy.wait()); + X11Window *transient = windowCreatedSpy.first().first().value(); + QVERIFY(transient); + QVERIFY(transient->isActive()); + QCOMPARE(transient->window(), transientWid); + QCOMPARE(transient->group(), leader->group()); + QVERIFY(transient->isTransient()); + QVERIFY(transient->groupTransient()); + QVERIFY(!transient->isDialog()); // See above why + + QCOMPARE(workspace()->stackingOrder(), (QList{leader, member1, member2, transient})); + + // If we activate any member of the window group, the transient will be above it. + workspace()->activateWindow(leader); + QTRY_VERIFY(leader->isActive()); + QCOMPARE(workspace()->stackingOrder(), (QList{member1, member2, leader, transient})); + + workspace()->activateWindow(member1); + QTRY_VERIFY(member1->isActive()); + QCOMPARE(workspace()->stackingOrder(), (QList{member2, leader, member1, transient})); + + workspace()->activateWindow(member2); + QTRY_VERIFY(member2->isActive()); + QCOMPARE(workspace()->stackingOrder(), (QList{leader, member1, member2, transient})); + + workspace()->activateWindow(transient); + QTRY_VERIFY(transient->isActive()); + QCOMPARE(workspace()->stackingOrder(), (QList{leader, member1, member2, transient})); +} + +void StackingOrderTest::testRaiseGroupTransient() +{ + const QRect geometry = QRect(0, 0, 128, 128); + + std::unique_ptr conn( + xcb_connect(nullptr, nullptr)); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + + // Create the group leader. + xcb_window_t leaderWid = createGroupWindow(conn.get(), geometry); + xcb_map_window(conn.get(), leaderWid); + xcb_flush(conn.get()); + + QVERIFY(windowCreatedSpy.wait()); + X11Window *leader = windowCreatedSpy.first().first().value(); + QVERIFY(leader); + QVERIFY(leader->isActive()); + QCOMPARE(leader->window(), leaderWid); + QVERIFY(!leader->isTransient()); + + QCOMPARE(workspace()->stackingOrder(), (QList{leader})); + + // Create another group member. + windowCreatedSpy.clear(); + xcb_window_t member1Wid = createGroupWindow(conn.get(), geometry, leaderWid); + xcb_map_window(conn.get(), member1Wid); + xcb_flush(conn.get()); + + QVERIFY(windowCreatedSpy.wait()); + X11Window *member1 = windowCreatedSpy.first().first().value(); + QVERIFY(member1); + QVERIFY(member1->isActive()); + QCOMPARE(member1->window(), member1Wid); + QCOMPARE(member1->group(), leader->group()); + QVERIFY(!member1->isTransient()); + + QCOMPARE(workspace()->stackingOrder(), (QList{leader, member1})); + + // Create yet another group member. + windowCreatedSpy.clear(); + xcb_window_t member2Wid = createGroupWindow(conn.get(), geometry, leaderWid); + xcb_map_window(conn.get(), member2Wid); + xcb_flush(conn.get()); + + QVERIFY(windowCreatedSpy.wait()); + X11Window *member2 = windowCreatedSpy.first().first().value(); + QVERIFY(member2); + QVERIFY(member2->isActive()); + QCOMPARE(member2->window(), member2Wid); + QCOMPARE(member2->group(), leader->group()); + QVERIFY(!member2->isTransient()); + + QCOMPARE(workspace()->stackingOrder(), (QList{leader, member1, member2})); + + // Create a group transient. + windowCreatedSpy.clear(); + xcb_window_t transientWid = createGroupWindow(conn.get(), geometry, leaderWid); + xcb_icccm_set_wm_transient_for(conn.get(), transientWid, rootWindow()); + + // Currently, we have some weird bug workaround: if a group transient + // is a non-modal dialog, then it won't be kept above its window group. + // We need to explicitly specify window type, otherwise the window type + // will be deduced to _NET_WM_WINDOW_TYPE_DIALOG because we set transient + // for before (the EWMH spec says to do that). + xcb_atom_t net_wm_window_type = Xcb::Atom( + QByteArrayLiteral("_NET_WM_WINDOW_TYPE"), false, conn.get()); + xcb_atom_t net_wm_window_type_normal = Xcb::Atom( + QByteArrayLiteral("_NET_WM_WINDOW_TYPE_NORMAL"), false, conn.get()); + xcb_change_property( + conn.get(), // c + XCB_PROP_MODE_REPLACE, // mode + transientWid, // window + net_wm_window_type, // property + XCB_ATOM_ATOM, // type + 32, // format + 1, // data_len + &net_wm_window_type_normal // data + ); + + xcb_map_window(conn.get(), transientWid); + xcb_flush(conn.get()); + + QVERIFY(windowCreatedSpy.wait()); + X11Window *transient = windowCreatedSpy.first().first().value(); + QVERIFY(transient); + QVERIFY(transient->isActive()); + QCOMPARE(transient->window(), transientWid); + QCOMPARE(transient->group(), leader->group()); + QVERIFY(transient->isTransient()); + QVERIFY(transient->groupTransient()); + QVERIFY(!transient->isDialog()); // See above why + + QCOMPARE(workspace()->stackingOrder(), (QList{leader, member1, member2, transient})); + + // Create a Wayland window that is not a member of the window group. + std::unique_ptr anotherSurface = Test::createSurface(); + QVERIFY(anotherSurface); + Test::XdgToplevel *anotherShellSurface = Test::createXdgToplevelSurface(anotherSurface.get(), anotherSurface.get()); + QVERIFY(anotherShellSurface); + Window *anotherWindow = Test::renderAndWaitForShown(anotherSurface.get(), QSize(128, 128), Qt::green); + QVERIFY(anotherWindow); + QVERIFY(anotherWindow->isActive()); + QVERIFY(!anotherWindow->isTransient()); + + QCOMPARE(workspace()->stackingOrder(), (QList{leader, member1, member2, transient, anotherWindow})); + + // If we activate the leader, then only it and the transient have to be raised. + workspace()->activateWindow(leader); + QTRY_VERIFY(leader->isActive()); + QCOMPARE(workspace()->stackingOrder(), (QList{member1, member2, anotherWindow, leader, transient})); + + // If another member of the window group is activated, then the transient will + // be above that member and the leader. + workspace()->activateWindow(member2); + QTRY_VERIFY(member2->isActive()); + QCOMPARE(workspace()->stackingOrder(), (QList{member1, anotherWindow, leader, member2, transient})); + + // FIXME: If we activate the transient, only it will be raised. + workspace()->activateWindow(anotherWindow); + QTRY_VERIFY(anotherWindow->isActive()); + QCOMPARE(workspace()->stackingOrder(), (QList{member1, leader, member2, transient, anotherWindow})); + + workspace()->activateWindow(transient); + QTRY_VERIFY(transient->isActive()); + QCOMPARE(workspace()->stackingOrder(), (QList{member1, leader, member2, anotherWindow, transient})); +} + +void StackingOrderTest::testDeletedGroupTransient() +{ + // This test verifies that deleted group transients are kept above their + // old window groups. + + const QRect geometry = QRect(0, 0, 128, 128); + + std::unique_ptr conn( + xcb_connect(nullptr, nullptr)); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + + // Create the group leader. + xcb_window_t leaderWid = createGroupWindow(conn.get(), geometry); + xcb_map_window(conn.get(), leaderWid); + xcb_flush(conn.get()); + + QVERIFY(windowCreatedSpy.wait()); + X11Window *leader = windowCreatedSpy.first().first().value(); + QVERIFY(leader); + QVERIFY(leader->isActive()); + QCOMPARE(leader->window(), leaderWid); + QVERIFY(!leader->isTransient()); + + QCOMPARE(workspace()->stackingOrder(), (QList{leader})); + + // Create another group member. + windowCreatedSpy.clear(); + xcb_window_t member1Wid = createGroupWindow(conn.get(), geometry, leaderWid); + xcb_map_window(conn.get(), member1Wid); + xcb_flush(conn.get()); + + QVERIFY(windowCreatedSpy.wait()); + X11Window *member1 = windowCreatedSpy.first().first().value(); + QVERIFY(member1); + QVERIFY(member1->isActive()); + QCOMPARE(member1->window(), member1Wid); + QCOMPARE(member1->group(), leader->group()); + QVERIFY(!member1->isTransient()); + + QCOMPARE(workspace()->stackingOrder(), (QList{leader, member1})); + + // Create yet another group member. + windowCreatedSpy.clear(); + xcb_window_t member2Wid = createGroupWindow(conn.get(), geometry, leaderWid); + xcb_map_window(conn.get(), member2Wid); + xcb_flush(conn.get()); + + QVERIFY(windowCreatedSpy.wait()); + X11Window *member2 = windowCreatedSpy.first().first().value(); + QVERIFY(member2); + QVERIFY(member2->isActive()); + QCOMPARE(member2->window(), member2Wid); + QCOMPARE(member2->group(), leader->group()); + QVERIFY(!member2->isTransient()); + + QCOMPARE(workspace()->stackingOrder(), (QList{leader, member1, member2})); + + // Create a group transient. + windowCreatedSpy.clear(); + xcb_window_t transientWid = createGroupWindow(conn.get(), geometry, leaderWid); + xcb_icccm_set_wm_transient_for(conn.get(), transientWid, rootWindow()); + + // Currently, we have some weird bug workaround: if a group transient + // is a non-modal dialog, then it won't be kept above its window group. + // We need to explicitly specify window type, otherwise the window type + // will be deduced to _NET_WM_WINDOW_TYPE_DIALOG because we set transient + // for before (the EWMH spec says to do that). + xcb_atom_t net_wm_window_type = Xcb::Atom( + QByteArrayLiteral("_NET_WM_WINDOW_TYPE"), false, conn.get()); + xcb_atom_t net_wm_window_type_normal = Xcb::Atom( + QByteArrayLiteral("_NET_WM_WINDOW_TYPE_NORMAL"), false, conn.get()); + xcb_change_property( + conn.get(), // c + XCB_PROP_MODE_REPLACE, // mode + transientWid, // window + net_wm_window_type, // property + XCB_ATOM_ATOM, // type + 32, // format + 1, // data_len + &net_wm_window_type_normal // data + ); + + xcb_map_window(conn.get(), transientWid); + xcb_flush(conn.get()); + + QVERIFY(windowCreatedSpy.wait()); + X11Window *transient = windowCreatedSpy.first().first().value(); + QVERIFY(transient); + QVERIFY(transient->isActive()); + QCOMPARE(transient->window(), transientWid); + QCOMPARE(transient->group(), leader->group()); + QVERIFY(transient->isTransient()); + QVERIFY(transient->groupTransient()); + QVERIFY(!transient->isDialog()); // See above why + + QCOMPARE(workspace()->stackingOrder(), (QList{leader, member1, member2, transient})); + + // Unmap the transient. + connect(transient, &X11Window::windowClosed, this, [](Window *original, Deleted *deleted) { + Q_UNUSED(original) + deleted->refWindow(); + }); + + QSignalSpy windowClosedSpy(transient, &X11Window::windowClosed); + xcb_unmap_window(conn.get(), transientWid); + xcb_flush(conn.get()); + QVERIFY(windowClosedSpy.wait()); + + std::unique_ptr deletedTransient( + windowClosedSpy.first().at(1).value()); + QVERIFY(deletedTransient.get()); + + // The transient has to be above each member of the window group. + QCOMPARE(workspace()->stackingOrder(), (QList{leader, member1, member2, deletedTransient.get()})); +} + +void StackingOrderTest::testDontKeepAboveNonModalDialogGroupTransients() +{ + // Bug 76026 + + const QRect geometry = QRect(0, 0, 128, 128); + + std::unique_ptr conn( + xcb_connect(nullptr, nullptr)); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + + // Create the group leader. + xcb_window_t leaderWid = createGroupWindow(conn.get(), geometry); + xcb_map_window(conn.get(), leaderWid); + xcb_flush(conn.get()); + + QVERIFY(windowCreatedSpy.wait()); + X11Window *leader = windowCreatedSpy.first().first().value(); + QVERIFY(leader); + QVERIFY(leader->isActive()); + QCOMPARE(leader->window(), leaderWid); + QVERIFY(!leader->isTransient()); + + QCOMPARE(workspace()->stackingOrder(), (QList{leader})); + + // Create another group member. + windowCreatedSpy.clear(); + xcb_window_t member1Wid = createGroupWindow(conn.get(), geometry, leaderWid); + xcb_map_window(conn.get(), member1Wid); + xcb_flush(conn.get()); + + QVERIFY(windowCreatedSpy.wait()); + X11Window *member1 = windowCreatedSpy.first().first().value(); + QVERIFY(member1); + QVERIFY(member1->isActive()); + QCOMPARE(member1->window(), member1Wid); + QCOMPARE(member1->group(), leader->group()); + QVERIFY(!member1->isTransient()); + + QCOMPARE(workspace()->stackingOrder(), (QList{leader, member1})); + + // Create yet another group member. + windowCreatedSpy.clear(); + xcb_window_t member2Wid = createGroupWindow(conn.get(), geometry, leaderWid); + xcb_map_window(conn.get(), member2Wid); + xcb_flush(conn.get()); + + QVERIFY(windowCreatedSpy.wait()); + X11Window *member2 = windowCreatedSpy.first().first().value(); + QVERIFY(member2); + QVERIFY(member2->isActive()); + QCOMPARE(member2->window(), member2Wid); + QCOMPARE(member2->group(), leader->group()); + QVERIFY(!member2->isTransient()); + + QCOMPARE(workspace()->stackingOrder(), (QList{leader, member1, member2})); + + // Create a group transient. + windowCreatedSpy.clear(); + xcb_window_t transientWid = createGroupWindow(conn.get(), geometry, leaderWid); + xcb_icccm_set_wm_transient_for(conn.get(), transientWid, rootWindow()); + xcb_map_window(conn.get(), transientWid); + xcb_flush(conn.get()); + + QVERIFY(windowCreatedSpy.wait()); + X11Window *transient = windowCreatedSpy.first().first().value(); + QVERIFY(transient); + QVERIFY(transient->isActive()); + QCOMPARE(transient->window(), transientWid); + QCOMPARE(transient->group(), leader->group()); + QVERIFY(transient->isTransient()); + QVERIFY(transient->groupTransient()); + QVERIFY(transient->isDialog()); + QVERIFY(!transient->isModal()); + + QCOMPARE(workspace()->stackingOrder(), (QList{leader, member1, member2, transient})); + + workspace()->activateWindow(leader); + QTRY_VERIFY(leader->isActive()); + QCOMPARE(workspace()->stackingOrder(), (QList{member1, member2, transient, leader})); + + workspace()->activateWindow(member1); + QTRY_VERIFY(member1->isActive()); + QCOMPARE(workspace()->stackingOrder(), (QList{member2, transient, leader, member1})); + + workspace()->activateWindow(member2); + QTRY_VERIFY(member2->isActive()); + QCOMPARE(workspace()->stackingOrder(), (QList{transient, leader, member1, member2})); + + workspace()->activateWindow(transient); + QTRY_VERIFY(transient->isActive()); + QCOMPARE(workspace()->stackingOrder(), (QList{leader, member1, member2, transient})); +} + +void StackingOrderTest::testKeepAbove() +{ + // This test verifies that "keep-above" windows are kept above other windows. + + // Create the first window. + std::unique_ptr surface1 = Test::createSurface(); + QVERIFY(surface1); + Test::XdgToplevel *shellSurface1 = Test::createXdgToplevelSurface(surface1.get(), surface1.get()); + QVERIFY(shellSurface1); + Window *window1 = Test::renderAndWaitForShown(surface1.get(), QSize(128, 128), Qt::green); + QVERIFY(window1); + QVERIFY(window1->isActive()); + QVERIFY(!window1->keepAbove()); + + QCOMPARE(workspace()->stackingOrder(), (QList{window1})); + + // Create the second window. + std::unique_ptr surface2 = Test::createSurface(); + QVERIFY(surface2); + Test::XdgToplevel *shellSurface2 = Test::createXdgToplevelSurface(surface2.get(), surface2.get()); + QVERIFY(shellSurface2); + Window *window2 = Test::renderAndWaitForShown(surface2.get(), QSize(128, 128), Qt::green); + QVERIFY(window2); + QVERIFY(window2->isActive()); + QVERIFY(!window2->keepAbove()); + + QCOMPARE(workspace()->stackingOrder(), (QList{window1, window2})); + + // Go to the initial test position. + workspace()->activateWindow(window1); + QTRY_VERIFY(window1->isActive()); + QCOMPARE(workspace()->stackingOrder(), (QList{window2, window1})); + + // Set the "keep-above" flag on the window2, it should go above other windows. + { + StackingUpdatesBlocker blocker(workspace()); + window2->setKeepAbove(true); + } + + QVERIFY(window2->keepAbove()); + QVERIFY(!window2->isActive()); + QCOMPARE(workspace()->stackingOrder(), (QList{window1, window2})); +} + +void StackingOrderTest::testKeepBelow() +{ + // This test verifies that "keep-below" windows are kept below other windows. + + // Create the first window. + std::unique_ptr surface1 = Test::createSurface(); + QVERIFY(surface1); + Test::XdgToplevel *shellSurface1 = Test::createXdgToplevelSurface(surface1.get(), surface1.get()); + QVERIFY(shellSurface1); + Window *window1 = Test::renderAndWaitForShown(surface1.get(), QSize(128, 128), Qt::green); + QVERIFY(window1); + QVERIFY(window1->isActive()); + QVERIFY(!window1->keepBelow()); + + QCOMPARE(workspace()->stackingOrder(), (QList{window1})); + + // Create the second window. + std::unique_ptr surface2 = Test::createSurface(); + QVERIFY(surface2); + Test::XdgToplevel *shellSurface2 = Test::createXdgToplevelSurface(surface2.get(), surface2.get()); + QVERIFY(shellSurface2); + Window *window2 = Test::renderAndWaitForShown(surface2.get(), QSize(128, 128), Qt::green); + QVERIFY(window2); + QVERIFY(window2->isActive()); + QVERIFY(!window2->keepBelow()); + + QCOMPARE(workspace()->stackingOrder(), (QList{window1, window2})); + + // Set the "keep-below" flag on the window2, it should go below other windows. + { + StackingUpdatesBlocker blocker(workspace()); + window2->setKeepBelow(true); + } + + QVERIFY(window2->isActive()); + QVERIFY(window2->keepBelow()); + QCOMPARE(workspace()->stackingOrder(), (QList{window2, window1})); +} + +WAYLANDTEST_MAIN(StackingOrderTest) +#include "stacking_order_test.moc" diff --git a/autotests/integration/struts_test.cpp b/autotests/integration/struts_test.cpp new file mode 100644 index 0000000..eb0a738 --- /dev/null +++ b/autotests/integration/struts_test.cpp @@ -0,0 +1,990 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "deleted.h" +#include "screenedge.h" +#include "virtualdesktops.h" +#include "wayland_server.h" +#include "workspace.h" +#include "x11window.h" +#include + +#include +#include +#include + +#include + +#include +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_struts-0"); + +class StrutsTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testWaylandStruts_data(); + void testWaylandStruts(); + void testMoveWaylandPanel(); + void testWaylandMobilePanel(); + void testX11Struts_data(); + void testX11Struts(); + void test363804(); + void testLeftScreenSmallerBottomAligned(); + void testWindowMoveWithPanelBetweenScreens(); + +private: + KWayland::Client::Compositor *m_compositor = nullptr; + KWayland::Client::PlasmaShell *m_plasmaShell = nullptr; +}; + +void StrutsTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + // set custom config which disables the Outline + KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup group = config->group("Outline"); + group.writeEntry(QStringLiteral("QmlPath"), QString("/does/not/exist.qml")); + group.sync(); + + kwinApp()->setConfig(config); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); + setenv("QT_QPA_PLATFORM", "wayland", true); +} + +void StrutsTest::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::PlasmaShell)); + m_compositor = Test::waylandCompositor(); + m_plasmaShell = Test::waylandPlasmaShell(); + + workspace()->setActiveOutput(QPoint(640, 512)); + Cursors::self()->mouse()->setPos(QPoint(640, 512)); + QVERIFY(waylandServer()->windows().isEmpty()); +} + +void StrutsTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void StrutsTest::testWaylandStruts_data() +{ + QTest::addColumn>("windowGeometries"); + QTest::addColumn("screen0Maximized"); + QTest::addColumn("screen1Maximized"); + QTest::addColumn("workArea"); + QTest::addColumn("restrictedMoveArea"); + + QTest::newRow("bottom/0") << QVector{QRect(0, 992, 1280, 32)} << QRectF(0, 0, 1280, 992) << QRectF(1280, 0, 1280, 1024) << QRectF(0, 0, 2560, 992) << QRegion(0, 992, 1280, 32); + QTest::newRow("bottom/1") << QVector{QRect(1280, 992, 1280, 32)} << QRectF(0, 0, 1280, 1024) << QRectF(1280, 0, 1280, 992) << QRectF(0, 0, 2560, 992) << QRegion(1280, 992, 1280, 32); + QTest::newRow("top/0") << QVector{QRect(0, 0, 1280, 32)} << QRectF(0, 32, 1280, 992) << QRectF(1280, 0, 1280, 1024) << QRectF(0, 32, 2560, 992) << QRegion(0, 0, 1280, 32); + QTest::newRow("top/1") << QVector{QRect(1280, 0, 1280, 32)} << QRectF(0, 0, 1280, 1024) << QRectF(1280, 32, 1280, 992) << QRectF(0, 32, 2560, 992) << QRegion(1280, 0, 1280, 32); + QTest::newRow("left/0") << QVector{QRect(0, 0, 32, 1024)} << QRectF(32, 0, 1248, 1024) << QRectF(1280, 0, 1280, 1024) << QRectF(32, 0, 2528, 1024) << QRegion(0, 0, 32, 1024); + QTest::newRow("left/1") << QVector{QRect(1280, 0, 32, 1024)} << QRectF(0, 0, 1280, 1024) << QRectF(1312, 0, 1248, 1024) << QRectF(0, 0, 2560, 1024) << QRegion(1280, 0, 32, 1024); + QTest::newRow("right/0") << QVector{QRect(1248, 0, 32, 1024)} << QRectF(0, 0, 1248, 1024) << QRectF(1280, 0, 1280, 1024) << QRectF(0, 0, 2560, 1024) << QRegion(1248, 0, 32, 1024); + QTest::newRow("right/1") << QVector{QRect(2528, 0, 32, 1024)} << QRectF(0, 0, 1280, 1024) << QRectF(1280, 0, 1248, 1024) << QRectF(0, 0, 2528, 1024) << QRegion(2528, 0, 32, 1024); + + // same with partial panels not covering the whole area + QTest::newRow("part bottom/0") << QVector{QRect(100, 992, 1080, 32)} << QRectF(0, 0, 1280, 992) << QRectF(1280, 0, 1280, 1024) << QRectF(0, 0, 2560, 992) << QRegion(100, 992, 1080, 32); + QTest::newRow("part bottom/1") << QVector{QRect(1380, 992, 1080, 32)} << QRectF(0, 0, 1280, 1024) << QRectF(1280, 0, 1280, 992) << QRectF(0, 0, 2560, 992) << QRegion(1380, 992, 1080, 32); + QTest::newRow("part top/0") << QVector{QRect(100, 0, 1080, 32)} << QRectF(0, 32, 1280, 992) << QRectF(1280, 0, 1280, 1024) << QRectF(0, 32, 2560, 992) << QRegion(100, 0, 1080, 32); + QTest::newRow("part top/1") << QVector{QRect(1380, 0, 1080, 32)} << QRectF(0, 0, 1280, 1024) << QRectF(1280, 32, 1280, 992) << QRectF(0, 32, 2560, 992) << QRegion(1380, 0, 1080, 32); + QTest::newRow("part left/0") << QVector{QRect(0, 100, 32, 824)} << QRectF(32, 0, 1248, 1024) << QRectF(1280, 0, 1280, 1024) << QRectF(32, 0, 2528, 1024) << QRegion(0, 100, 32, 824); + QTest::newRow("part left/1") << QVector{QRect(1280, 100, 32, 824)} << QRectF(0, 0, 1280, 1024) << QRectF(1312, 0, 1248, 1024) << QRectF(0, 0, 2560, 1024) << QRegion(1280, 100, 32, 824); + QTest::newRow("part right/0") << QVector{QRect(1248, 100, 32, 824)} << QRectF(0, 0, 1248, 1024) << QRectF(1280, 0, 1280, 1024) << QRectF(0, 0, 2560, 1024) << QRegion(1248, 100, 32, 824); + QTest::newRow("part right/1") << QVector{QRect(2528, 100, 32, 824)} << QRectF(0, 0, 1280, 1024) << QRectF(1280, 0, 1248, 1024) << QRectF(0, 0, 2528, 1024) << QRegion(2528, 100, 32, 824); + + // multiple panels + QTest::newRow("two bottom panels") << QVector{QRect(100, 992, 1080, 32), QRect(1380, 984, 1080, 40)} << QRectF(0, 0, 1280, 992) << QRectF(1280, 0, 1280, 984) << QRectF(0, 0, 2560, 984) << QRegion(100, 992, 1080, 32).united(QRegion(1380, 984, 1080, 40)); + QTest::newRow("two left panels") << QVector{QRect(0, 10, 32, 390), QRect(0, 450, 40, 100)} << QRectF(40, 0, 1240, 1024) << QRectF(1280, 0, 1280, 1024) << QRectF(40, 0, 2520, 1024) << QRegion(0, 10, 32, 390).united(QRegion(0, 450, 40, 100)); +} + +void StrutsTest::testWaylandStruts() +{ + // this test verifies that struts on Wayland panels are handled correctly + using namespace KWayland::Client; + + VirtualDesktop *desktop = VirtualDesktopManager::self()->currentDesktop(); + const QList outputs = workspace()->outputs(); + + // no, struts yet + QVERIFY(waylandServer()->windows().isEmpty()); + // first screen + QCOMPARE(workspace()->clientArea(PlacementArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MovementArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeFullArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(FullScreenArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(ScreenArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + // second screen + QCOMPARE(workspace()->clientArea(PlacementArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MovementArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeFullArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(FullScreenArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(ScreenArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + // combined + QCOMPARE(workspace()->clientArea(WorkArea, outputs[0], desktop), QRect(0, 0, 2560, 1024)); + QCOMPARE(workspace()->clientArea(FullArea, outputs[0], desktop), QRect(0, 0, 2560, 1024)); + QCOMPARE(workspace()->restrictedMoveArea(desktop), QRegion()); + + QFETCH(QVector, windowGeometries); + // create the panels + std::map> windows; + for (auto it = windowGeometries.constBegin(), end = windowGeometries.constEnd(); it != end; it++) { + const QRect windowGeometry = *it; + std::unique_ptr surface = Test::createSurface(); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly, surface.get()); + PlasmaShellSurface *plasmaSurface = m_plasmaShell->createSurface(surface.get(), surface.get()); + plasmaSurface->setPosition(windowGeometry.topLeft()); + plasmaSurface->setRole(PlasmaShellSurface::Role::Panel); + + QSignalSpy configureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(configureRequestedSpy.wait()); + + // map the window + shellSurface->xdgSurface()->ack_configure(configureRequestedSpy.last().first().toUInt()); + auto window = Test::renderAndWaitForShown(surface.get(), windowGeometry.size(), Qt::red, QImage::Format_RGB32); + + QVERIFY(window); + QVERIFY(!window->isActive()); + QCOMPARE(window->frameGeometry(), windowGeometry); + QVERIFY(window->isDock()); + QVERIFY(window->hasStrut()); + windows[window] = std::move(surface); + } + + // some props are independent of struts - those first + // screen 0 + QCOMPARE(workspace()->clientArea(MovementArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeFullArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(FullScreenArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(ScreenArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + // screen 1 + QCOMPARE(workspace()->clientArea(MovementArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeFullArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(FullScreenArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(ScreenArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + // combined + QCOMPARE(workspace()->clientArea(FullArea, outputs[0], desktop), QRect(0, 0, 2560, 1024)); + + // now verify the actual updated client areas + QTEST(workspace()->clientArea(PlacementArea, outputs[0], desktop), "screen0Maximized"); + QTEST(workspace()->clientArea(MaximizeArea, outputs[0], desktop), "screen0Maximized"); + QTEST(workspace()->clientArea(PlacementArea, outputs[1], desktop), "screen1Maximized"); + QTEST(workspace()->clientArea(MaximizeArea, outputs[1], desktop), "screen1Maximized"); + QTEST(workspace()->clientArea(WorkArea, outputs[0], desktop), "workArea"); + QTEST(workspace()->restrictedMoveArea(desktop), "restrictedMoveArea"); + + // delete all surfaces + for (auto it = windows.begin(); it != windows.end();) { + auto &[window, surface] = *it; + QSignalSpy destroyedSpy(window, &QObject::destroyed); + it = windows.erase(it); + QVERIFY(destroyedSpy.wait()); + } + QCOMPARE(workspace()->restrictedMoveArea(desktop), QRegion()); +} + +void StrutsTest::testMoveWaylandPanel() +{ + VirtualDesktop *desktop = VirtualDesktopManager::self()->currentDesktop(); + const QList outputs = workspace()->outputs(); + + // this test verifies that repositioning a Wayland panel updates the client area + using namespace KWayland::Client; + const QRect windowGeometry(0, 1000, 1280, 24); + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + std::unique_ptr plasmaSurface(m_plasmaShell->createSurface(surface.get())); + plasmaSurface->setPosition(windowGeometry.topLeft()); + plasmaSurface->setRole(PlasmaShellSurface::Role::Panel); + + QSignalSpy configureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(configureRequestedSpy.wait()); + + // map the window + shellSurface->xdgSurface()->ack_configure(configureRequestedSpy.last().first().toUInt()); + auto window = Test::renderAndWaitForShown(surface.get(), windowGeometry.size(), Qt::red, QImage::Format_RGB32); + QVERIFY(window); + QVERIFY(!window->isActive()); + QCOMPARE(window->frameGeometry(), windowGeometry); + QVERIFY(window->isDock()); + QVERIFY(window->hasStrut()); + QCOMPARE(workspace()->clientArea(PlacementArea, outputs[0], desktop), QRect(0, 0, 1280, 1000)); + QCOMPARE(workspace()->clientArea(MaximizeArea, outputs[0], desktop), QRect(0, 0, 1280, 1000)); + QCOMPARE(workspace()->clientArea(PlacementArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(WorkArea, outputs[0], desktop), QRect(0, 0, 2560, 1000)); + + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + plasmaSurface->setPosition(QPoint(1280, 1000)); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry(), QRect(1280, 1000, 1280, 24)); + QCOMPARE(workspace()->clientArea(PlacementArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(PlacementArea, outputs[1], desktop), QRect(1280, 0, 1280, 1000)); + QCOMPARE(workspace()->clientArea(MaximizeArea, outputs[1], desktop), QRect(1280, 0, 1280, 1000)); + QCOMPARE(workspace()->clientArea(WorkArea, outputs[0], desktop), QRect(0, 0, 2560, 1000)); +} + +void StrutsTest::testWaylandMobilePanel() +{ + using namespace KWayland::Client; + + VirtualDesktop *desktop = VirtualDesktopManager::self()->currentDesktop(); + const QList outputs = workspace()->outputs(); + + // First enable maxmizing policy + KConfigGroup group = kwinApp()->config()->group("Windows"); + group.writeEntry("Placement", "Maximizing"); + group.sync(); + workspace()->slotReconfigure(); + + // create first top panel + const QRect windowGeometry(0, 0, 1280, 60); + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + std::unique_ptr plasmaSurface(m_plasmaShell->createSurface(surface.get())); + plasmaSurface->setPosition(windowGeometry.topLeft()); + plasmaSurface->setRole(PlasmaShellSurface::Role::Panel); + + QSignalSpy configureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(configureRequestedSpy.wait()); + + // map the window + shellSurface->xdgSurface()->ack_configure(configureRequestedSpy.last().first().toUInt()); + auto window = Test::renderAndWaitForShown(surface.get(), windowGeometry.size(), Qt::red, QImage::Format_RGB32); + QVERIFY(window); + QVERIFY(!window->isActive()); + QCOMPARE(window->frameGeometry(), windowGeometry); + QVERIFY(window->isDock()); + QVERIFY(window->hasStrut()); + + QCOMPARE(workspace()->clientArea(PlacementArea, outputs[0], desktop), QRect(0, 60, 1280, 964)); + QCOMPARE(workspace()->clientArea(MaximizeArea, outputs[0], desktop), QRect(0, 60, 1280, 964)); + QCOMPARE(workspace()->clientArea(PlacementArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(WorkArea, outputs[0], desktop), QRect(0, 60, 2560, 964)); + + // create another bottom panel + const QRect windowGeometry2(0, 874, 1280, 150); + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get(), Test::CreationSetup::CreateOnly)); + std::unique_ptr plasmaSurface2(m_plasmaShell->createSurface(surface2.get())); + plasmaSurface2->setPosition(windowGeometry2.topLeft()); + plasmaSurface2->setRole(PlasmaShellSurface::Role::Panel); + + QSignalSpy configureRequestedSpy2(shellSurface2->xdgSurface(), &Test::XdgSurface::configureRequested); + surface2->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(configureRequestedSpy2.wait()); + + // map the window + shellSurface2->xdgSurface()->ack_configure(configureRequestedSpy2.last().first().toUInt()); + auto c1 = Test::renderAndWaitForShown(surface2.get(), windowGeometry2.size(), Qt::blue, QImage::Format_RGB32); + + QVERIFY(c1); + QVERIFY(!c1->isActive()); + QCOMPARE(c1->frameGeometry(), windowGeometry2); + QVERIFY(c1->isDock()); + QVERIFY(c1->hasStrut()); + + QCOMPARE(workspace()->clientArea(PlacementArea, outputs[0], desktop), QRect(0, 60, 1280, 814)); + QCOMPARE(workspace()->clientArea(MaximizeArea, outputs[0], desktop), QRect(0, 60, 1280, 814)); + QCOMPARE(workspace()->clientArea(PlacementArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(WorkArea, outputs[0], desktop), QRect(0, 60, 2560, 814)); + + // Destroy test windows. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); + shellSurface2.reset(); + QVERIFY(Test::waitForWindowDestroyed(c1)); +} + +void StrutsTest::testX11Struts_data() +{ + QTest::addColumn("windowGeometry"); + QTest::addColumn("leftStrut"); + QTest::addColumn("rightStrut"); + QTest::addColumn("topStrut"); + QTest::addColumn("bottomStrut"); + QTest::addColumn("leftStrutStart"); + QTest::addColumn("leftStrutEnd"); + QTest::addColumn("rightStrutStart"); + QTest::addColumn("rightStrutEnd"); + QTest::addColumn("topStrutStart"); + QTest::addColumn("topStrutEnd"); + QTest::addColumn("bottomStrutStart"); + QTest::addColumn("bottomStrutEnd"); + QTest::addColumn("screen0Maximized"); + QTest::addColumn("screen1Maximized"); + QTest::addColumn("workArea"); + QTest::addColumn("restrictedMoveArea"); + + QTest::newRow("bottom panel/no strut") << QRect(0, 980, 1280, 44) + << 0 << 0 << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << QRectF(0, 0, 1280, 1024) + << QRectF(1280, 0, 1280, 1024) + << QRectF(0, 0, 2560, 1024) + << QRegion(); + QTest::newRow("bottom panel/strut") << QRect(0, 980, 1280, 44) + << 0 << 0 << 0 << 44 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 1279 + << QRectF(0, 0, 1280, 980) + << QRectF(1280, 0, 1280, 1024) + << QRectF(0, 0, 2560, 980) + << QRegion(0, 980, 1279, 44); + QTest::newRow("top panel/no strut") << QRect(0, 0, 1280, 44) + << 0 << 0 << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << QRectF(0, 0, 1280, 1024) + << QRectF(1280, 0, 1280, 1024) + << QRectF(0, 0, 2560, 1024) + << QRegion(); + QTest::newRow("top panel/strut") << QRect(0, 0, 1280, 44) + << 0 << 0 << 44 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 1279 + << 0 << 0 + << QRectF(0, 44, 1280, 980) + << QRectF(1280, 0, 1280, 1024) + << QRectF(0, 44, 2560, 980) + << QRegion(0, 0, 1279, 44); + QTest::newRow("left panel/no strut") << QRect(0, 0, 60, 1024) + << 0 << 0 << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << QRectF(0, 0, 1280, 1024) + << QRectF(1280, 0, 1280, 1024) + << QRectF(0, 0, 2560, 1024) + << QRegion(); + QTest::newRow("left panel/strut") << QRect(0, 0, 60, 1024) + << 60 << 0 << 0 << 0 + << 0 << 1023 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << QRectF(60, 0, 1220, 1024) + << QRectF(1280, 0, 1280, 1024) + << QRectF(60, 0, 2500, 1024) + << QRegion(0, 0, 60, 1023); + QTest::newRow("right panel/no strut") << QRect(1220, 0, 60, 1024) + << 0 << 0 << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << QRectF(0, 0, 1280, 1024) + << QRectF(1280, 0, 1280, 1024) + << QRectF(0, 0, 2560, 1024) + << QRegion(); + QTest::newRow("right panel/strut") << QRect(1220, 0, 60, 1024) + << 0 << 1340 << 0 << 0 + << 0 << 0 + << 0 << 1023 + << 0 << 0 + << 0 << 0 + << QRectF(0, 0, 1220, 1024) + << QRectF(1280, 0, 1280, 1024) + << QRectF(0, 0, 2560, 1024) + << QRegion(1220, 0, 60, 1023); + // second screen + QTest::newRow("bottom panel 1/no strut") << QRect(1280, 980, 1280, 44) + << 0 << 0 << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << QRectF(0, 0, 1280, 1024) + << QRectF(1280, 0, 1280, 1024) + << QRectF(0, 0, 2560, 1024) + << QRegion(); + QTest::newRow("bottom panel 1/strut") << QRect(1280, 980, 1280, 44) + << 0 << 0 << 0 << 44 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << 1280 << 2559 + << QRectF(0, 0, 1280, 1024) + << QRectF(1280, 0, 1280, 980) + << QRectF(0, 0, 2560, 980) + << QRegion(1280, 980, 1279, 44); + QTest::newRow("top panel 1/no strut") << QRect(1280, 0, 1280, 44) + << 0 << 0 << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << QRectF(0, 0, 1280, 1024) + << QRectF(1280, 0, 1280, 1024) + << QRectF(0, 0, 2560, 1024) + << QRegion(); + QTest::newRow("top panel 1 /strut") << QRect(1280, 0, 1280, 44) + << 0 << 0 << 44 << 0 + << 0 << 0 + << 0 << 0 + << 1280 << 2559 + << 0 << 0 + << QRectF(0, 0, 1280, 1024) + << QRectF(1280, 44, 1280, 980) + << QRectF(0, 44, 2560, 980) + << QRegion(1280, 0, 1279, 44); + QTest::newRow("left panel 1/no strut") << QRect(1280, 0, 60, 1024) + << 0 << 0 << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << QRectF(0, 0, 1280, 1024) + << QRectF(1280, 0, 1280, 1024) + << QRectF(0, 0, 2560, 1024) + << QRegion(); + QTest::newRow("left panel 1/strut") << QRect(1280, 0, 60, 1024) + << 1340 << 0 << 0 << 0 + << 0 << 1023 + << 0 << 0 + << 0 << 0 + << 0 << 0 + << QRectF(0, 0, 1280, 1024) + << QRectF(1340, 0, 1220, 1024) + << QRectF(0, 0, 2560, 1024) + << QRegion(1280, 0, 60, 1023); + // invalid struts + QTest::newRow("bottom panel/ invalid strut") << QRect(0, 980, 1280, 44) + << 1280 << 0 << 0 << 44 + << 980 << 1024 + << 0 << 0 + << 0 << 0 + << 0 << 1279 + << QRectF(0, 0, 1280, 1024) + << QRectF(1280, 0, 1280, 1024) + << QRectF(0, 0, 2560, 1024) + << QRegion(0, 980, 1280, 44); + QTest::newRow("top panel/ invalid strut") << QRect(0, 0, 1280, 44) + << 1280 << 0 << 44 << 0 + << 0 << 44 + << 0 << 0 + << 0 << 1279 + << 0 << 0 + << QRectF(0, 0, 1280, 1024) + << QRectF(1280, 0, 1280, 1024) + << QRectF(0, 0, 2560, 1024) + << QRegion(0, 0, 1280, 44); + QTest::newRow("top panel/invalid strut 2") << QRect(0, 0, 1280, 44) + << 0 << 0 << 1024 << 0 + << 0 << 0 + << 0 << 0 + << 0 << 1279 + << 0 << 0 + << QRectF(0, 0, 1280, 1024) + << QRectF(1280, 0, 1280, 1024) + << QRectF(0, 0, 2560, 1024) + << QRegion(); +} + +struct XcbConnectionDeleter +{ + void operator()(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } +}; + +void StrutsTest::testX11Struts() +{ + // this test verifies that struts are applied correctly for X11 windows + + VirtualDesktop *desktop = VirtualDesktopManager::self()->currentDesktop(); + const QList outputs = workspace()->outputs(); + + // no, struts yet + // first screen + QCOMPARE(workspace()->clientArea(PlacementArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MovementArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeFullArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(FullScreenArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(ScreenArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + // second screen + QCOMPARE(workspace()->clientArea(PlacementArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MovementArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeFullArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(FullScreenArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(ScreenArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + // combined + QCOMPARE(workspace()->clientArea(WorkArea, outputs[0], desktop), QRect(0, 0, 2560, 1024)); + QCOMPARE(workspace()->clientArea(FullArea, outputs[0], desktop), QRect(0, 0, 2560, 1024)); + QCOMPARE(workspace()->restrictedMoveArea(desktop), QRegion()); + + // create an xcb window + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + + xcb_window_t windowId = xcb_generate_id(c.get()); + QFETCH(QRect, windowGeometry); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + NETWinInfo info(c.get(), windowId, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); + info.setWindowType(NET::Dock); + // set the extended strut + QFETCH(int, leftStrut); + QFETCH(int, rightStrut); + QFETCH(int, topStrut); + QFETCH(int, bottomStrut); + QFETCH(int, leftStrutStart); + QFETCH(int, leftStrutEnd); + QFETCH(int, rightStrutStart); + QFETCH(int, rightStrutEnd); + QFETCH(int, topStrutStart); + QFETCH(int, topStrutEnd); + QFETCH(int, bottomStrutStart); + QFETCH(int, bottomStrutEnd); + NETExtendedStrut strut; + strut.left_start = leftStrutStart; + strut.left_end = leftStrutEnd; + strut.left_width = leftStrut; + strut.right_start = rightStrutStart; + strut.right_end = rightStrutEnd; + strut.right_width = rightStrut; + strut.top_start = topStrutStart; + strut.top_end = topStrutEnd; + strut.top_width = topStrut; + strut.bottom_start = bottomStrutStart; + strut.bottom_end = bottomStrutEnd; + strut.bottom_width = bottomStrut; + info.setExtendedStrut(strut); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QVERIFY(!window->isDecorated()); + QCOMPARE(window->windowType(), NET::Dock); + QCOMPARE(window->frameGeometry(), windowGeometry); + + // this should have affected the client area + // some props are independent of struts - those first + // screen 0 + QCOMPARE(workspace()->clientArea(MovementArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeFullArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(FullScreenArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(ScreenArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + // screen 1 + QCOMPARE(workspace()->clientArea(MovementArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeFullArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(FullScreenArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(ScreenArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + // combined + QCOMPARE(workspace()->clientArea(FullArea, outputs[0], desktop), QRect(0, 0, 2560, 1024)); + + // now verify the actual updated client areas + QTEST(workspace()->clientArea(PlacementArea, outputs[0], desktop), "screen0Maximized"); + QTEST(workspace()->clientArea(MaximizeArea, outputs[0], desktop), "screen0Maximized"); + QTEST(workspace()->clientArea(PlacementArea, outputs[1], desktop), "screen1Maximized"); + QTEST(workspace()->clientArea(MaximizeArea, outputs[1], desktop), "screen1Maximized"); + QTEST(workspace()->clientArea(WorkArea, outputs[0], desktop), "workArea"); + QTEST(workspace()->restrictedMoveArea(desktop), "restrictedMoveArea"); + + // and destroy the window again + xcb_unmap_window(c.get(), windowId); + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + c.reset(); + + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + QVERIFY(windowClosedSpy.wait()); + + // now struts should be removed again + QCOMPARE(workspace()->clientArea(PlacementArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MovementArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeFullArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(FullScreenArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(ScreenArea, outputs[0], desktop), QRect(0, 0, 1280, 1024)); + // second screen + QCOMPARE(workspace()->clientArea(PlacementArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MovementArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(MaximizeFullArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(FullScreenArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + QCOMPARE(workspace()->clientArea(ScreenArea, outputs[1], desktop), QRect(1280, 0, 1280, 1024)); + // combined + QCOMPARE(workspace()->clientArea(WorkArea, outputs[0], desktop), QRect(0, 0, 2560, 1024)); + QCOMPARE(workspace()->clientArea(FullArea, outputs[0], desktop), QRect(0, 0, 2560, 1024)); + QCOMPARE(workspace()->restrictedMoveArea(desktop), QRegion()); +} + +void StrutsTest::test363804() +{ + // this test verifies the condition described in BUG 363804 + // two screens in a vertical setup, aligned to right border with panel on the bottom screen + const QVector geometries{QRect(0, 0, 1920, 1080), QRect(554, 1080, 1366, 768)}; + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", + Qt::DirectConnection, + Q_ARG(int, 2), + Q_ARG(QVector, geometries)); + QCOMPARE(workspace()->geometry(), QRect(0, 0, 1920, 1848)); + + VirtualDesktop *desktop = VirtualDesktopManager::self()->currentDesktop(); + const QList outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), geometries[0]); + QCOMPARE(outputs[1]->geometry(), geometries[1]); + + // create an xcb window + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + + xcb_window_t windowId = xcb_generate_id(c.get()); + const QRect windowGeometry(554, 1812, 1366, 36); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + NETWinInfo info(c.get(), windowId, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); + info.setWindowType(NET::Dock); + NETExtendedStrut strut; + strut.left_start = 0; + strut.left_end = 0; + strut.left_width = 0; + strut.right_start = 0; + strut.right_end = 0; + strut.right_width = 0; + strut.top_start = 0; + strut.top_end = 0; + strut.top_width = 0; + strut.bottom_start = 554; + strut.bottom_end = 1919; + strut.bottom_width = 36; + info.setExtendedStrut(strut); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QVERIFY(!window->isDecorated()); + QCOMPARE(window->windowType(), NET::Dock); + QCOMPARE(window->frameGeometry(), windowGeometry); + + // now verify the actual updated client areas + QCOMPARE(workspace()->clientArea(PlacementArea, outputs[0], desktop), geometries.at(0)); + QCOMPARE(workspace()->clientArea(MaximizeArea, outputs[0], desktop), geometries.at(0)); + QCOMPARE(workspace()->clientArea(PlacementArea, outputs[1], desktop), QRect(554, 1080, 1366, 732)); + QCOMPARE(workspace()->clientArea(MaximizeArea, outputs[1], desktop), QRect(554, 1080, 1366, 732)); + QCOMPARE(workspace()->clientArea(WorkArea, outputs[0], desktop), QRect(0, 0, 1920, 1812)); + + // and destroy the window again + xcb_unmap_window(c.get(), windowId); + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + c.reset(); + + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + QVERIFY(windowClosedSpy.wait()); +} + +void StrutsTest::testLeftScreenSmallerBottomAligned() +{ + // this test verifies a two screen setup with the left screen smaller than the right and bottom aligned + // the panel is on the top of the left screen, thus not at 0/0 + // what this test in addition tests is whether a window larger than the left screen is not placed into + // the dead area + const QVector geometries{QRect(0, 282, 1366, 768), QRect(1366, 0, 1680, 1050)}; + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", + Qt::DirectConnection, + Q_ARG(int, 2), + Q_ARG(QVector, geometries)); + QCOMPARE(workspace()->geometry(), QRect(0, 0, 3046, 1050)); + + const QList outputs = workspace()->outputs(); + QCOMPARE(outputs[0]->geometry(), geometries.at(0)); + QCOMPARE(outputs[1]->geometry(), geometries.at(1)); + + // the test window will be on the current desktop + VirtualDesktop *desktop = VirtualDesktopManager::self()->currentDesktop(); + + // create the panel + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + + xcb_window_t windowId = xcb_generate_id(c.get()); + const QRect windowGeometry(0, 282, 1366, 24); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + NETWinInfo info(c.get(), windowId, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); + info.setWindowType(NET::Dock); + NETExtendedStrut strut; + strut.left_start = 0; + strut.left_end = 0; + strut.left_width = 0; + strut.right_start = 0; + strut.right_end = 0; + strut.right_width = 0; + strut.top_start = 0; + strut.top_end = 1365; + strut.top_width = 306; + strut.bottom_start = 0; + strut.bottom_end = 0; + strut.bottom_width = 0; + info.setExtendedStrut(strut); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QVERIFY(!window->isDecorated()); + QCOMPARE(window->windowType(), NET::Dock); + QCOMPARE(window->frameGeometry(), windowGeometry); + + // now verify the actual updated client areas + QCOMPARE(workspace()->clientArea(PlacementArea, outputs[0], desktop), QRect(0, 306, 1366, 744)); + QCOMPARE(workspace()->clientArea(MaximizeArea, outputs[0], desktop), QRect(0, 306, 1366, 744)); + QCOMPARE(workspace()->clientArea(PlacementArea, outputs[1], desktop), geometries.at(1)); + QCOMPARE(workspace()->clientArea(MaximizeArea, outputs[1], desktop), geometries.at(1)); + QCOMPARE(workspace()->clientArea(WorkArea, outputs[0], desktop), QRect(0, 0, 3046, 1050)); + + // now create a window which is larger than screen 0 + + xcb_window_t w2 = xcb_generate_id(c.get()); + const QRect windowGeometry2(0, 26, 1280, 774); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, w2, rootWindow(), + windowGeometry2.x(), + windowGeometry2.y(), + windowGeometry2.width(), + windowGeometry2.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints2; + memset(&hints2, 0, sizeof(hints2)); + xcb_icccm_size_hints_set_min_size(&hints2, 868, 431); + xcb_icccm_set_wm_normal_hints(c.get(), w2, &hints2); + xcb_map_window(c.get(), w2); + xcb_flush(c.get()); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window2 = windowCreatedSpy.last().first().value(); + QVERIFY(window2); + QVERIFY(window2 != window); + QVERIFY(window2->isDecorated()); + QCOMPARE(window2->frameGeometry(), QRect(0, 306, 1366, 744)); + QCOMPARE(window2->maximizeMode(), KWin::MaximizeFull); + // destroy window again + QSignalSpy normalWindowClosedSpy(window2, &X11Window::windowClosed); + xcb_unmap_window(c.get(), w2); + xcb_destroy_window(c.get(), w2); + xcb_flush(c.get()); + QVERIFY(normalWindowClosedSpy.wait()); + + // and destroy the window again + xcb_unmap_window(c.get(), windowId); + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + c.reset(); + + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + QVERIFY(windowClosedSpy.wait()); +} + +void StrutsTest::testWindowMoveWithPanelBetweenScreens() +{ + // this test verifies the condition of BUG + // when moving a window with decorations in a restricted way it should pass from one screen + // to the other even if there is a panel in between. + + // left screen must be smaller than right screen + const QVector geometries{QRect(0, 282, 1366, 768), QRect(1366, 0, 1680, 1050)}; + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", + Qt::DirectConnection, + Q_ARG(int, 2), + Q_ARG(QVector, geometries)); + QCOMPARE(workspace()->geometry(), QRect(0, 0, 3046, 1050)); + + const QList outputs = workspace()->outputs(); + QCOMPARE(outputs[0]->geometry(), geometries.at(0)); + QCOMPARE(outputs[1]->geometry(), geometries.at(1)); + + // all windows will be placed on the current desktop + VirtualDesktop *desktop = VirtualDesktopManager::self()->currentDesktop(); + + // create the panel on the right screen, left edge + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + + xcb_window_t windowId = xcb_generate_id(c.get()); + const QRect windowGeometry(1366, 0, 24, 1050); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + NETWinInfo info(c.get(), windowId, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); + info.setWindowType(NET::Dock); + NETExtendedStrut strut; + strut.left_start = 0; + strut.left_end = 1050; + strut.left_width = 1366 + 24; + strut.right_start = 0; + strut.right_end = 0; + strut.right_width = 0; + strut.top_start = 0; + strut.top_end = 0; + strut.top_width = 0; + strut.bottom_start = 0; + strut.bottom_end = 0; + strut.bottom_width = 0; + info.setExtendedStrut(strut); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QVERIFY(!window->isDecorated()); + QCOMPARE(window->windowType(), NET::Dock); + QCOMPARE(window->frameGeometry(), windowGeometry); + + // now verify the actual updated client areas + QCOMPARE(workspace()->clientArea(PlacementArea, outputs[0], desktop), QRect(0, 282, 1366, 768)); + QCOMPARE(workspace()->clientArea(MaximizeArea, outputs[0], desktop), QRect(0, 282, 1366, 768)); + QCOMPARE(workspace()->clientArea(PlacementArea, outputs[1], desktop), QRect(1390, 0, 1656, 1050)); + QCOMPARE(workspace()->clientArea(MaximizeArea, outputs[1], desktop), QRect(1390, 0, 1656, 1050)); + QCOMPARE(workspace()->clientArea(WorkArea, outputs[0], desktop), QRect(0, 0, 3046, 1050)); + QCOMPARE(workspace()->restrictedMoveArea(desktop), QRegion(1366, 0, 24, 1050)); + + // create another window and try to move it + + xcb_window_t w2 = xcb_generate_id(c.get()); + const QRect windowGeometry2(1500, 400, 200, 300); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, w2, rootWindow(), + windowGeometry2.x(), + windowGeometry2.y(), + windowGeometry2.width(), + windowGeometry2.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints2; + memset(&hints2, 0, sizeof(hints2)); + xcb_icccm_size_hints_set_position(&hints2, 1, windowGeometry2.x(), windowGeometry2.y()); + xcb_icccm_size_hints_set_min_size(&hints2, 200, 300); + xcb_icccm_set_wm_normal_hints(c.get(), w2, &hints2); + xcb_map_window(c.get(), w2); + xcb_flush(c.get()); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window2 = windowCreatedSpy.last().first().value(); + QVERIFY(window2); + QVERIFY(window2 != window); + QVERIFY(window2->isDecorated()); + QCOMPARE(window2->clientSize(), QSize(200, 300)); + QCOMPARE(window2->pos(), QPoint(1500, 400)); + + const QRectF origGeo = window2->frameGeometry(); + Cursors::self()->mouse()->setPos(origGeo.center()); + workspace()->performWindowOperation(window2, Options::MoveOp); + QTRY_COMPARE(workspace()->moveResizeWindow(), window2); + QVERIFY(window2->isInteractiveMove()); + // move to next screen - step is 8 pixel, so 800 pixel + for (int i = 0; i < 100; i++) { + window2->keyPressEvent(Qt::Key_Left); + QTest::qWait(50); + } + window2->keyPressEvent(Qt::Key_Enter); + QCOMPARE(window2->isInteractiveMove(), false); + QVERIFY(workspace()->moveResizeWindow() == nullptr); + QCOMPARE(window2->frameGeometry(), QRectF(origGeo.translated(-800, 0))); +} + +} + +WAYLANDTEST_MAIN(KWin::StrutsTest) +#include "struts_test.moc" diff --git a/autotests/integration/tabbox_test.cpp b/autotests/integration/tabbox_test.cpp new file mode 100644 index 0000000..1509e1e --- /dev/null +++ b/autotests/integration/tabbox_test.cpp @@ -0,0 +1,236 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/platform.h" +#include "cursor.h" +#include "input.h" +#include "tabbox/tabbox.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include +#include + +#include + +using namespace KWin; +using namespace KWayland::Client; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_tabbox-0"); + +class TabBoxTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testMoveForward(); + void testMoveBackward(); + void testCapsLock(); +}; + +void TabBoxTest::initTestCase() +{ + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + KSharedConfigPtr c = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + c->group("TabBox").writeEntry("ShowTabBox", false); + c->sync(); + kwinApp()->setConfig(c); + qputenv("KWIN_XKB_DEFAULT_KEYMAP", "1"); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); +} + +void TabBoxTest::init() +{ + QVERIFY(Test::setupWaylandConnection()); + workspace()->setActiveOutput(QPoint(640, 512)); + KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void TabBoxTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void TabBoxTest::testCapsLock() +{ + // this test verifies that Alt+tab works correctly also when Capslock is on + // bug 368590 + + // first create three windows + std::unique_ptr surface1(Test::createSurface()); + std::unique_ptr shellSurface1(Test::createXdgToplevelSurface(surface1.get())); + auto c1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::blue); + QVERIFY(c1); + QVERIFY(c1->isActive()); + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + auto c2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::red); + QVERIFY(c2); + QVERIFY(c2->isActive()); + std::unique_ptr surface3(Test::createSurface()); + std::unique_ptr shellSurface3(Test::createXdgToplevelSurface(surface3.get())); + auto c3 = Test::renderAndWaitForShown(surface3.get(), QSize(100, 50), Qt::red); + QVERIFY(c3); + QVERIFY(c3->isActive()); + + // Setup tabbox signal spies + QSignalSpy tabboxAddedSpy(workspace()->tabbox(), &TabBox::TabBox::tabBoxAdded); + QSignalSpy tabboxClosedSpy(workspace()->tabbox(), &TabBox::TabBox::tabBoxClosed); + + // enable capslock + quint32 timestamp = 0; + Test::keyboardKeyPressed(KEY_CAPSLOCK, timestamp++); + Test::keyboardKeyReleased(KEY_CAPSLOCK, timestamp++); + QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier); + + // press alt+tab + Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); + QCOMPARE(input()->keyboardModifiers(), Qt::ShiftModifier | Qt::AltModifier); + Test::keyboardKeyPressed(KEY_TAB, timestamp++); + Test::keyboardKeyReleased(KEY_TAB, timestamp++); + + QVERIFY(tabboxAddedSpy.wait()); + QVERIFY(workspace()->tabbox()->isGrabbed()); + + // release alt + Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); + QCOMPARE(tabboxClosedSpy.count(), 1); + QCOMPARE(workspace()->tabbox()->isGrabbed(), false); + + // release caps lock + Test::keyboardKeyPressed(KEY_CAPSLOCK, timestamp++); + Test::keyboardKeyReleased(KEY_CAPSLOCK, timestamp++); + QCOMPARE(input()->keyboardModifiers(), Qt::NoModifier); + QCOMPARE(tabboxClosedSpy.count(), 1); + QCOMPARE(workspace()->tabbox()->isGrabbed(), false); + QCOMPARE(workspace()->activeWindow(), c2); + + surface3.reset(); + QVERIFY(Test::waitForWindowDestroyed(c3)); + surface2.reset(); + QVERIFY(Test::waitForWindowDestroyed(c2)); + surface1.reset(); + QVERIFY(Test::waitForWindowDestroyed(c1)); +} + +void TabBoxTest::testMoveForward() +{ + // this test verifies that Alt+tab works correctly moving forward + + // first create three windows + std::unique_ptr surface1(Test::createSurface()); + std::unique_ptr shellSurface1(Test::createXdgToplevelSurface(surface1.get())); + auto c1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::blue); + QVERIFY(c1); + QVERIFY(c1->isActive()); + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + auto c2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::red); + QVERIFY(c2); + QVERIFY(c2->isActive()); + std::unique_ptr surface3(Test::createSurface()); + std::unique_ptr shellSurface3(Test::createXdgToplevelSurface(surface3.get())); + auto c3 = Test::renderAndWaitForShown(surface3.get(), QSize(100, 50), Qt::red); + QVERIFY(c3); + QVERIFY(c3->isActive()); + + // Setup tabbox signal spies + QSignalSpy tabboxAddedSpy(workspace()->tabbox(), &TabBox::TabBox::tabBoxAdded); + QSignalSpy tabboxClosedSpy(workspace()->tabbox(), &TabBox::TabBox::tabBoxClosed); + + // press alt+tab + quint32 timestamp = 0; + Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); + QCOMPARE(input()->keyboardModifiers(), Qt::AltModifier); + Test::keyboardKeyPressed(KEY_TAB, timestamp++); + Test::keyboardKeyReleased(KEY_TAB, timestamp++); + + QVERIFY(tabboxAddedSpy.wait()); + QVERIFY(workspace()->tabbox()->isGrabbed()); + + // release alt + Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); + QCOMPARE(tabboxClosedSpy.count(), 1); + QCOMPARE(workspace()->tabbox()->isGrabbed(), false); + QCOMPARE(workspace()->activeWindow(), c2); + + surface3.reset(); + QVERIFY(Test::waitForWindowDestroyed(c3)); + surface2.reset(); + QVERIFY(Test::waitForWindowDestroyed(c2)); + surface1.reset(); + QVERIFY(Test::waitForWindowDestroyed(c1)); +} + +void TabBoxTest::testMoveBackward() +{ + // this test verifies that Alt+Shift+tab works correctly moving backward + + // first create three windows + std::unique_ptr surface1(Test::createSurface()); + std::unique_ptr shellSurface1(Test::createXdgToplevelSurface(surface1.get())); + auto c1 = Test::renderAndWaitForShown(surface1.get(), QSize(100, 50), Qt::blue); + QVERIFY(c1); + QVERIFY(c1->isActive()); + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + auto c2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::red); + QVERIFY(c2); + QVERIFY(c2->isActive()); + std::unique_ptr surface3(Test::createSurface()); + std::unique_ptr shellSurface3(Test::createXdgToplevelSurface(surface3.get())); + auto c3 = Test::renderAndWaitForShown(surface3.get(), QSize(100, 50), Qt::red); + QVERIFY(c3); + QVERIFY(c3->isActive()); + + // Setup tabbox signal spies + QSignalSpy tabboxAddedSpy(workspace()->tabbox(), &TabBox::TabBox::tabBoxAdded); + QSignalSpy tabboxClosedSpy(workspace()->tabbox(), &TabBox::TabBox::tabBoxClosed); + + // press alt+shift+tab + quint32 timestamp = 0; + Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); + QCOMPARE(input()->keyboardModifiers(), Qt::AltModifier); + Test::keyboardKeyPressed(KEY_LEFTSHIFT, timestamp++); + QCOMPARE(input()->keyboardModifiers(), Qt::AltModifier | Qt::ShiftModifier); + Test::keyboardKeyPressed(KEY_TAB, timestamp++); + Test::keyboardKeyReleased(KEY_TAB, timestamp++); + + QVERIFY(tabboxAddedSpy.wait()); + QVERIFY(workspace()->tabbox()->isGrabbed()); + + // release alt + Test::keyboardKeyReleased(KEY_LEFTSHIFT, timestamp++); + QCOMPARE(tabboxClosedSpy.count(), 0); + Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); + QCOMPARE(tabboxClosedSpy.count(), 1); + QCOMPARE(workspace()->tabbox()->isGrabbed(), false); + QCOMPARE(workspace()->activeWindow(), c1); + + surface3.reset(); + QVERIFY(Test::waitForWindowDestroyed(c3)); + surface2.reset(); + QVERIFY(Test::waitForWindowDestroyed(c2)); + surface1.reset(); + QVERIFY(Test::waitForWindowDestroyed(c1)); +} + +WAYLANDTEST_MAIN(TabBoxTest) +#include "tabbox_test.moc" diff --git a/autotests/integration/test_helpers.cpp b/autotests/integration/test_helpers.cpp new file mode 100644 index 0000000..d78140d --- /dev/null +++ b/autotests/integration/test_helpers.cpp @@ -0,0 +1,1433 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2015 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include + +#include "kwin_wayland_test.h" + +#if KWIN_BUILD_SCREENLOCKER +#include "screenlockerwatcher.h" +#endif +#include "inputmethod.h" +#include "wayland/display.h" +#include "wayland_server.h" +#include "workspace.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// screenlocker +#if KWIN_BUILD_SCREENLOCKER +#include +#endif + +#include + +// system +#include +#include +#include + +using namespace KWayland::Client; + +namespace KWin +{ +namespace Test +{ + +LayerShellV1::~LayerShellV1() +{ + destroy(); +} + +LayerSurfaceV1::~LayerSurfaceV1() +{ + destroy(); +} + +void LayerSurfaceV1::zwlr_layer_surface_v1_configure(uint32_t serial, uint32_t width, uint32_t height) +{ + Q_EMIT configureRequested(serial, QSize(width, height)); +} + +void LayerSurfaceV1::zwlr_layer_surface_v1_closed() +{ + Q_EMIT closeRequested(); +} + +XdgShell::~XdgShell() +{ + destroy(); +} + +XdgSurface::XdgSurface(XdgShell *shell, KWayland::Client::Surface *surface, QObject *parent) + : QObject(parent) + , QtWayland::xdg_surface(shell->get_xdg_surface(*surface)) + , m_surface(surface) +{ +} + +XdgSurface::~XdgSurface() +{ + destroy(); +} + +KWayland::Client::Surface *XdgSurface::surface() const +{ + return m_surface; +} + +void XdgSurface::xdg_surface_configure(uint32_t serial) +{ + Q_EMIT configureRequested(serial); +} + +XdgToplevel::XdgToplevel(XdgSurface *surface, QObject *parent) + : QObject(parent) + , QtWayland::xdg_toplevel(surface->get_toplevel()) + , m_xdgSurface(surface) +{ +} + +XdgToplevel::~XdgToplevel() +{ + destroy(); +} + +XdgSurface *XdgToplevel::xdgSurface() const +{ + return m_xdgSurface.get(); +} + +void XdgToplevel::xdg_toplevel_configure(int32_t width, int32_t height, wl_array *states) +{ + States requestedStates; + + const uint32_t *stateData = static_cast(states->data); + const size_t stateCount = states->size / sizeof(uint32_t); + + for (size_t i = 0; i < stateCount; ++i) { + switch (stateData[i]) { + case QtWayland::xdg_toplevel::state_maximized: + requestedStates |= State::Maximized; + break; + case QtWayland::xdg_toplevel::state_fullscreen: + requestedStates |= State::Fullscreen; + break; + case QtWayland::xdg_toplevel::state_resizing: + requestedStates |= State::Resizing; + break; + case QtWayland::xdg_toplevel::state_activated: + requestedStates |= State::Activated; + break; + } + } + + Q_EMIT configureRequested(QSize(width, height), requestedStates); +} + +void XdgToplevel::xdg_toplevel_close() +{ + Q_EMIT closeRequested(); +} + +XdgPositioner::XdgPositioner(XdgShell *shell) + : QtWayland::xdg_positioner(shell->create_positioner()) +{ +} + +XdgPositioner::~XdgPositioner() +{ + destroy(); +} + +XdgPopup::XdgPopup(XdgSurface *surface, XdgSurface *parentSurface, XdgPositioner *positioner, QObject *parent) + : QObject(parent) + , QtWayland::xdg_popup(surface->get_popup(parentSurface->object(), positioner->object())) + , m_xdgSurface(surface) +{ +} + +XdgPopup::~XdgPopup() +{ + destroy(); +} + +XdgSurface *XdgPopup::xdgSurface() const +{ + return m_xdgSurface.get(); +} + +void XdgPopup::xdg_popup_configure(int32_t x, int32_t y, int32_t width, int32_t height) +{ + Q_EMIT configureRequested(QRect(x, y, width, height)); +} + +void XdgPopup::xdg_popup_popup_done() +{ + Q_EMIT doneReceived(); +} + +XdgDecorationManagerV1::~XdgDecorationManagerV1() +{ + destroy(); +} + +XdgToplevelDecorationV1::XdgToplevelDecorationV1(XdgDecorationManagerV1 *manager, + XdgToplevel *toplevel, QObject *parent) + : QObject(parent) + , QtWayland::zxdg_toplevel_decoration_v1(manager->get_toplevel_decoration(toplevel->object())) +{ +} + +XdgToplevelDecorationV1::~XdgToplevelDecorationV1() +{ + destroy(); +} + +void XdgToplevelDecorationV1::zxdg_toplevel_decoration_v1_configure(uint32_t m) +{ + Q_EMIT configureRequested(mode(m)); +} + +IdleInhibitManagerV1::~IdleInhibitManagerV1() +{ + destroy(); +} + +IdleInhibitorV1::IdleInhibitorV1(IdleInhibitManagerV1 *manager, KWayland::Client::Surface *surface) + : QtWayland::zwp_idle_inhibitor_v1(manager->create_inhibitor(*surface)) +{ +} + +IdleInhibitorV1::~IdleInhibitorV1() +{ + destroy(); +} + +static struct +{ + ConnectionThread *connection = nullptr; + EventQueue *queue = nullptr; + KWayland::Client::Compositor *compositor = nullptr; + SubCompositor *subCompositor = nullptr; + ServerSideDecorationManager *decoration = nullptr; + ShadowManager *shadowManager = nullptr; + XdgShell *xdgShell = nullptr; + ShmPool *shm = nullptr; + Seat *seat = nullptr; + PlasmaShell *plasmaShell = nullptr; + PlasmaWindowManagement *windowManagement = nullptr; + PointerConstraints *pointerConstraints = nullptr; + Registry *registry = nullptr; + WaylandOutputManagementV2 *outputManagementV2 = nullptr; + QThread *thread = nullptr; + QVector outputs; + QVector outputDevicesV2; + IdleInhibitManagerV1 *idleInhibitManagerV1 = nullptr; + AppMenuManager *appMenu = nullptr; + XdgDecorationManagerV1 *xdgDecorationManagerV1 = nullptr; + TextInputManager *textInputManager = nullptr; + QtWayland::zwp_input_panel_v1 *inputPanelV1 = nullptr; + MockInputMethod *inputMethodV1 = nullptr; + QtWayland::zwp_input_method_context_v1 *inputMethodContextV1 = nullptr; + LayerShellV1 *layerShellV1 = nullptr; + TextInputManagerV3 *textInputManagerV3 = nullptr; +} s_waylandConnection; + +MockInputMethod *inputMethod() +{ + return s_waylandConnection.inputMethodV1; +} + +KWayland::Client::Surface *inputPanelSurface() +{ + return s_waylandConnection.inputMethodV1->inputPanelSurface(); +} + +MockInputMethod::MockInputMethod(struct wl_registry *registry, int id, int version) + : QtWayland::zwp_input_method_v1(registry, id, version) +{ +} +MockInputMethod::~MockInputMethod() +{ +} + +void MockInputMethod::zwp_input_method_v1_activate(struct ::zwp_input_method_context_v1 *context) +{ + Q_UNUSED(context) + if (!m_inputSurface) { + m_inputSurface = Test::createSurface(); + m_inputMethodSurface = Test::createInputPanelSurfaceV1(m_inputSurface.get(), s_waylandConnection.outputs.first()); + } + m_context = context; + Test::render(m_inputSurface.get(), QSize(1280, 400), Qt::blue); + + Q_EMIT activate(); +} + +void MockInputMethod::zwp_input_method_v1_deactivate(struct ::zwp_input_method_context_v1 *context) +{ + QCOMPARE(context, m_context); + zwp_input_method_context_v1_destroy(context); + m_context = nullptr; + + if (m_inputSurface) { + m_inputSurface->release(); + m_inputSurface->destroy(); + m_inputSurface.reset(); + delete m_inputMethodSurface; + m_inputMethodSurface = nullptr; + } +} + +bool setupWaylandConnection(AdditionalWaylandInterfaces flags) +{ + if (s_waylandConnection.connection) { + return false; + } + + int sx[2]; + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { + return false; + } + KWin::waylandServer()->display()->createClient(sx[0]); + // setup connection + s_waylandConnection.connection = new ConnectionThread; + QSignalSpy connectedSpy(s_waylandConnection.connection, &ConnectionThread::connected); + if (!connectedSpy.isValid()) { + return false; + } + s_waylandConnection.connection->setSocketFd(sx[1]); + + s_waylandConnection.thread = new QThread(kwinApp()); + s_waylandConnection.connection->moveToThread(s_waylandConnection.thread); + s_waylandConnection.thread->start(); + + s_waylandConnection.connection->initConnection(); + if (!connectedSpy.wait()) { + return false; + } + + s_waylandConnection.queue = new EventQueue; + s_waylandConnection.queue->setup(s_waylandConnection.connection); + if (!s_waylandConnection.queue->isValid()) { + return false; + } + + Registry *registry = new Registry; + s_waylandConnection.registry = registry; + registry->setEventQueue(s_waylandConnection.queue); + + QObject::connect(registry, &Registry::outputAnnounced, [=](quint32 name, quint32 version) { + KWayland::Client::Output *output = registry->createOutput(name, version, s_waylandConnection.registry); + s_waylandConnection.outputs << output; + QObject::connect(output, &KWayland::Client::Output::removed, [=]() { + output->deleteLater(); + s_waylandConnection.outputs.removeOne(output); + }); + QObject::connect(output, &KWayland::Client::Output::destroyed, [=]() { + s_waylandConnection.outputs.removeOne(output); + }); + }); + + QObject::connect(registry, &Registry::interfaceAnnounced, [=](const QByteArray &interface, quint32 name, quint32 version) { + if (flags & AdditionalWaylandInterface::InputMethodV1) { + if (interface == QByteArrayLiteral("zwp_input_method_v1")) { + s_waylandConnection.inputMethodV1 = new MockInputMethod(*registry, name, version); + } else if (interface == QByteArrayLiteral("zwp_input_panel_v1")) { + s_waylandConnection.inputPanelV1 = new QtWayland::zwp_input_panel_v1(*registry, name, version); + } + } + if (flags & AdditionalWaylandInterface::LayerShellV1) { + if (interface == QByteArrayLiteral("zwlr_layer_shell_v1")) { + s_waylandConnection.layerShellV1 = new LayerShellV1(); + s_waylandConnection.layerShellV1->init(*registry, name, version); + } + } + if (flags & AdditionalWaylandInterface::TextInputManagerV3) { + // do something + if (interface == QByteArrayLiteral("zwp_text_input_manager_v3")) { + s_waylandConnection.textInputManagerV3 = new TextInputManagerV3(); + s_waylandConnection.textInputManagerV3->init(*registry, name, version); + } + } + if (interface == QByteArrayLiteral("xdg_wm_base")) { + s_waylandConnection.xdgShell = new XdgShell(); + s_waylandConnection.xdgShell->init(*registry, name, version); + } + if (flags & AdditionalWaylandInterface::XdgDecorationV1) { + if (interface == zxdg_decoration_manager_v1_interface.name) { + s_waylandConnection.xdgDecorationManagerV1 = new XdgDecorationManagerV1(); + s_waylandConnection.xdgDecorationManagerV1->init(*registry, name, version); + return; + } + } + if (flags & AdditionalWaylandInterface::IdleInhibitV1) { + if (interface == zwp_idle_inhibit_manager_v1_interface.name) { + s_waylandConnection.idleInhibitManagerV1 = new IdleInhibitManagerV1(); + s_waylandConnection.idleInhibitManagerV1->init(*registry, name, version); + return; + } + } + if (flags & AdditionalWaylandInterface::OutputDeviceV2) { + if (interface == kde_output_device_v2_interface.name) { + WaylandOutputDeviceV2 *device = new WaylandOutputDeviceV2(name); + device->init(*registry, name, version); + + s_waylandConnection.outputDevicesV2 << device; + + QObject::connect(device, &WaylandOutputDeviceV2::destroyed, [=]() { + s_waylandConnection.outputDevicesV2.removeOne(device); + device->deleteLater(); + }); + + QObject::connect(registry, &KWayland::Client::Registry::interfaceRemoved, device, [name, device](const quint32 &interfaceName) { + if (name == interfaceName) { + s_waylandConnection.outputDevicesV2.removeOne(device); + device->deleteLater(); + } + }); + + return; + } + } + if (flags & AdditionalWaylandInterface::OutputManagementV2) { + if (interface == kde_output_management_v2_interface.name) { + s_waylandConnection.outputManagementV2 = new WaylandOutputManagementV2(*registry, name, version); + return; + } + } + }); + + QSignalSpy allAnnounced(registry, &Registry::interfacesAnnounced); + if (!allAnnounced.isValid()) { + return false; + } + registry->create(s_waylandConnection.connection); + if (!registry->isValid()) { + return false; + } + registry->setup(); + if (!allAnnounced.wait()) { + return false; + } + + s_waylandConnection.compositor = registry->createCompositor(registry->interface(Registry::Interface::Compositor).name, registry->interface(Registry::Interface::Compositor).version); + if (!s_waylandConnection.compositor->isValid()) { + return false; + } + s_waylandConnection.subCompositor = registry->createSubCompositor(registry->interface(Registry::Interface::SubCompositor).name, registry->interface(Registry::Interface::SubCompositor).version); + if (!s_waylandConnection.subCompositor->isValid()) { + return false; + } + s_waylandConnection.shm = registry->createShmPool(registry->interface(Registry::Interface::Shm).name, registry->interface(Registry::Interface::Shm).version); + if (!s_waylandConnection.shm->isValid()) { + return false; + } + if (flags.testFlag(AdditionalWaylandInterface::Seat)) { + s_waylandConnection.seat = registry->createSeat(registry->interface(Registry::Interface::Seat).name, registry->interface(Registry::Interface::Seat).version); + if (!s_waylandConnection.seat->isValid()) { + return false; + } + } + if (flags.testFlag(AdditionalWaylandInterface::ShadowManager)) { + s_waylandConnection.shadowManager = registry->createShadowManager(registry->interface(Registry::Interface::Shadow).name, + registry->interface(Registry::Interface::Shadow).version); + if (!s_waylandConnection.shadowManager->isValid()) { + return false; + } + } + if (flags.testFlag(AdditionalWaylandInterface::Decoration)) { + s_waylandConnection.decoration = registry->createServerSideDecorationManager(registry->interface(Registry::Interface::ServerSideDecorationManager).name, + registry->interface(Registry::Interface::ServerSideDecorationManager).version); + if (!s_waylandConnection.decoration->isValid()) { + return false; + } + } + if (flags.testFlag(AdditionalWaylandInterface::PlasmaShell)) { + s_waylandConnection.plasmaShell = registry->createPlasmaShell(registry->interface(Registry::Interface::PlasmaShell).name, + registry->interface(Registry::Interface::PlasmaShell).version); + if (!s_waylandConnection.plasmaShell->isValid()) { + return false; + } + } + if (flags.testFlag(AdditionalWaylandInterface::WindowManagement)) { + s_waylandConnection.windowManagement = registry->createPlasmaWindowManagement(registry->interface(Registry::Interface::PlasmaWindowManagement).name, + registry->interface(Registry::Interface::PlasmaWindowManagement).version); + if (!s_waylandConnection.windowManagement->isValid()) { + return false; + } + } + if (flags.testFlag(AdditionalWaylandInterface::PointerConstraints)) { + s_waylandConnection.pointerConstraints = registry->createPointerConstraints(registry->interface(Registry::Interface::PointerConstraintsUnstableV1).name, + registry->interface(Registry::Interface::PointerConstraintsUnstableV1).version); + if (!s_waylandConnection.pointerConstraints->isValid()) { + return false; + } + } + if (flags.testFlag(AdditionalWaylandInterface::AppMenu)) { + s_waylandConnection.appMenu = registry->createAppMenuManager(registry->interface(Registry::Interface::AppMenu).name, registry->interface(Registry::Interface::AppMenu).version); + if (!s_waylandConnection.appMenu->isValid()) { + return false; + } + } + if (flags.testFlag(AdditionalWaylandInterface::TextInputManagerV2)) { + s_waylandConnection.textInputManager = registry->createTextInputManager(registry->interface(Registry::Interface::TextInputManagerUnstableV2).name, registry->interface(Registry::Interface::TextInputManagerUnstableV2).version); + if (!s_waylandConnection.textInputManager->isValid()) { + return false; + } + } + + return true; +} + +void destroyWaylandConnection() +{ + delete s_waylandConnection.compositor; + s_waylandConnection.compositor = nullptr; + delete s_waylandConnection.subCompositor; + s_waylandConnection.subCompositor = nullptr; + delete s_waylandConnection.windowManagement; + s_waylandConnection.windowManagement = nullptr; + delete s_waylandConnection.plasmaShell; + s_waylandConnection.plasmaShell = nullptr; + delete s_waylandConnection.decoration; + s_waylandConnection.decoration = nullptr; + delete s_waylandConnection.decoration; + s_waylandConnection.decoration = nullptr; + delete s_waylandConnection.seat; + s_waylandConnection.seat = nullptr; + delete s_waylandConnection.pointerConstraints; + s_waylandConnection.pointerConstraints = nullptr; + delete s_waylandConnection.xdgShell; + s_waylandConnection.xdgShell = nullptr; + delete s_waylandConnection.shadowManager; + s_waylandConnection.shadowManager = nullptr; + delete s_waylandConnection.idleInhibitManagerV1; + s_waylandConnection.idleInhibitManagerV1 = nullptr; + delete s_waylandConnection.shm; + s_waylandConnection.shm = nullptr; + delete s_waylandConnection.registry; + s_waylandConnection.registry = nullptr; + delete s_waylandConnection.appMenu; + s_waylandConnection.appMenu = nullptr; + delete s_waylandConnection.xdgDecorationManagerV1; + s_waylandConnection.xdgDecorationManagerV1 = nullptr; + delete s_waylandConnection.textInputManager; + s_waylandConnection.textInputManager = nullptr; + delete s_waylandConnection.inputPanelV1; + s_waylandConnection.inputPanelV1 = nullptr; + delete s_waylandConnection.layerShellV1; + s_waylandConnection.layerShellV1 = nullptr; + delete s_waylandConnection.outputManagementV2; + s_waylandConnection.outputManagementV2 = nullptr; + + delete s_waylandConnection.queue; // Must be destroyed last + s_waylandConnection.queue = nullptr; + + if (s_waylandConnection.thread) { + s_waylandConnection.connection->deleteLater(); + s_waylandConnection.thread->quit(); + s_waylandConnection.thread->wait(); + delete s_waylandConnection.thread; + s_waylandConnection.thread = nullptr; + s_waylandConnection.connection = nullptr; + } + s_waylandConnection.outputs.clear(); + s_waylandConnection.outputDevicesV2.clear(); +} + +ConnectionThread *waylandConnection() +{ + return s_waylandConnection.connection; +} + +KWayland::Client::Compositor *waylandCompositor() +{ + return s_waylandConnection.compositor; +} + +SubCompositor *waylandSubCompositor() +{ + return s_waylandConnection.subCompositor; +} + +ShadowManager *waylandShadowManager() +{ + return s_waylandConnection.shadowManager; +} + +ShmPool *waylandShmPool() +{ + return s_waylandConnection.shm; +} + +Seat *waylandSeat() +{ + return s_waylandConnection.seat; +} + +ServerSideDecorationManager *waylandServerSideDecoration() +{ + return s_waylandConnection.decoration; +} + +PlasmaShell *waylandPlasmaShell() +{ + return s_waylandConnection.plasmaShell; +} + +PlasmaWindowManagement *waylandWindowManagement() +{ + return s_waylandConnection.windowManagement; +} + +PointerConstraints *waylandPointerConstraints() +{ + return s_waylandConnection.pointerConstraints; +} + +AppMenuManager *waylandAppMenuManager() +{ + return s_waylandConnection.appMenu; +} + +KWin::Test::WaylandOutputManagementV2 *waylandOutputManagementV2() +{ + return s_waylandConnection.outputManagementV2; +} + +TextInputManager *waylandTextInputManager() +{ + return s_waylandConnection.textInputManager; +} + +TextInputManagerV3 *waylandTextInputManagerV3() +{ + return s_waylandConnection.textInputManagerV3; +} + +QVector waylandOutputs() +{ + return s_waylandConnection.outputs; +} + +KWayland::Client::Output *waylandOutput(const QString &name) +{ + for (KWayland::Client::Output *output : std::as_const(s_waylandConnection.outputs)) { + if (output->name() == name) { + return output; + } + } + return nullptr; +} + +QVector waylandOutputDevicesV2() +{ + return s_waylandConnection.outputDevicesV2; +} + +bool waitForWaylandSurface(Window *window) +{ + if (window->surface()) { + return true; + } + QSignalSpy surfaceChangedSpy(window, &Window::surfaceChanged); + return surfaceChangedSpy.wait(); +} + +bool waitForWaylandPointer() +{ + if (!s_waylandConnection.seat) { + return false; + } + QSignalSpy hasPointerSpy(s_waylandConnection.seat, &Seat::hasPointerChanged); + if (!hasPointerSpy.isValid()) { + return false; + } + return hasPointerSpy.wait(); +} + +bool waitForWaylandTouch() +{ + if (!s_waylandConnection.seat) { + return false; + } + QSignalSpy hasTouchSpy(s_waylandConnection.seat, &Seat::hasTouchChanged); + if (!hasTouchSpy.isValid()) { + return false; + } + return hasTouchSpy.wait(); +} + +bool waitForWaylandKeyboard() +{ + if (!s_waylandConnection.seat) { + return false; + } + QSignalSpy hasKeyboardSpy(s_waylandConnection.seat, &Seat::hasKeyboardChanged); + if (!hasKeyboardSpy.isValid()) { + return false; + } + return hasKeyboardSpy.wait(); +} + +void render(KWayland::Client::Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format) +{ + QImage img(size, format); + img.fill(color); + render(surface, img); +} + +void render(KWayland::Client::Surface *surface, const QImage &img) +{ + surface->attachBuffer(s_waylandConnection.shm->createBuffer(img)); + surface->damage(QRect(QPoint(0, 0), img.size())); + surface->commit(KWayland::Client::Surface::CommitFlag::None); +} + +Window *waitForWaylandWindowShown(int timeout) +{ + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + if (!windowAddedSpy.isValid()) { + return nullptr; + } + if (!windowAddedSpy.wait(timeout)) { + return nullptr; + } + return windowAddedSpy.first().first().value(); +} + +Window *renderAndWaitForShown(KWayland::Client::Surface *surface, const QSize &size, const QColor &color, const QImage::Format &format, int timeout) +{ + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + if (!windowAddedSpy.isValid()) { + return nullptr; + } + render(surface, size, color, format); + flushWaylandConnection(); + if (!windowAddedSpy.wait(timeout)) { + return nullptr; + } + return windowAddedSpy.first().first().value(); +} + +void flushWaylandConnection() +{ + if (s_waylandConnection.connection) { + s_waylandConnection.connection->flush(); + } +} + +std::unique_ptr createSurface() +{ + if (!s_waylandConnection.compositor) { + return nullptr; + } + std::unique_ptr s{s_waylandConnection.compositor->createSurface()}; + return s->isValid() ? std::move(s) : nullptr; +} + +SubSurface *createSubSurface(KWayland::Client::Surface *surface, KWayland::Client::Surface *parentSurface, QObject *parent) +{ + if (!s_waylandConnection.subCompositor) { + return nullptr; + } + auto s = s_waylandConnection.subCompositor->createSubSurface(surface, parentSurface, parent); + if (!s->isValid()) { + delete s; + return nullptr; + } + return s; +} + +LayerSurfaceV1 *createLayerSurfaceV1(KWayland::Client::Surface *surface, const QString &scope, KWayland::Client::Output *output, LayerShellV1::layer layer) +{ + LayerShellV1 *shell = s_waylandConnection.layerShellV1; + if (!shell) { + qWarning() << "Could not create a layer surface because the layer shell global is not bound"; + return nullptr; + } + + struct ::wl_output *nativeOutput = nullptr; + if (output) { + nativeOutput = *output; + } + + LayerSurfaceV1 *shellSurface = new LayerSurfaceV1(); + shellSurface->init(shell->get_layer_surface(*surface, nativeOutput, layer, scope)); + + return shellSurface; +} + +QtWayland::zwp_input_panel_surface_v1 *createInputPanelSurfaceV1(KWayland::Client::Surface *surface, KWayland::Client::Output *output) +{ + if (!s_waylandConnection.inputPanelV1) { + qWarning() << "Unable to create the input panel surface. The interface input_panel global is not bound"; + return nullptr; + } + QtWayland::zwp_input_panel_surface_v1 *s = new QtWayland::zwp_input_panel_surface_v1(s_waylandConnection.inputPanelV1->get_input_panel_surface(*surface)); + + if (!s->isInitialized()) { + delete s; + return nullptr; + } + + s->set_toplevel(output->output(), QtWayland::zwp_input_panel_surface_v1::position_center_bottom); + + return s; +} + +static void waitForConfigured(XdgSurface *shellSurface) +{ + QSignalSpy surfaceConfigureRequestedSpy(shellSurface, &XdgSurface::configureRequested); + + shellSurface->surface()->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + + shellSurface->ack_configure(surfaceConfigureRequestedSpy.last().first().toUInt()); +} + +XdgToplevel *createXdgToplevelSurface(KWayland::Client::Surface *surface, QObject *parent) +{ + return createXdgToplevelSurface(surface, CreationSetup::CreateAndConfigure, parent); +} + +XdgToplevel *createXdgToplevelSurface(KWayland::Client::Surface *surface, CreationSetup configureMode, QObject *parent) +{ + XdgShell *shell = s_waylandConnection.xdgShell; + + if (!shell) { + qWarning() << "Could not create an xdg_toplevel surface because xdg_wm_base global is not bound"; + return nullptr; + } + + XdgSurface *xdgSurface = new XdgSurface(shell, surface); + XdgToplevel *xdgToplevel = new XdgToplevel(xdgSurface, parent); + + if (configureMode == CreationSetup::CreateAndConfigure) { + waitForConfigured(xdgSurface); + } + + return xdgToplevel; +} + +XdgPositioner *createXdgPositioner() +{ + XdgShell *shell = s_waylandConnection.xdgShell; + + if (!shell) { + qWarning() << "Could not create an xdg_positioner object because xdg_wm_base global is not bound"; + return nullptr; + } + + return new XdgPositioner(shell); +} + +XdgPopup *createXdgPopupSurface(KWayland::Client::Surface *surface, XdgSurface *parentSurface, XdgPositioner *positioner, + CreationSetup configureMode, QObject *parent) +{ + XdgShell *shell = s_waylandConnection.xdgShell; + + if (!shell) { + qWarning() << "Could not create an xdg_popup surface because xdg_wm_base global is not bound"; + return nullptr; + } + + XdgSurface *xdgSurface = new XdgSurface(shell, surface); + XdgPopup *xdgPopup = new XdgPopup(xdgSurface, parentSurface, positioner, parent); + + if (configureMode == CreationSetup::CreateAndConfigure) { + waitForConfigured(xdgSurface); + } + + return xdgPopup; +} + +XdgToplevelDecorationV1 *createXdgToplevelDecorationV1(XdgToplevel *toplevel, QObject *parent) +{ + XdgDecorationManagerV1 *manager = s_waylandConnection.xdgDecorationManagerV1; + + if (!manager) { + qWarning() << "Could not create an xdg_toplevel_decoration_v1 because xdg_decoration_manager_v1 global is not bound"; + return nullptr; + } + + return new XdgToplevelDecorationV1(manager, toplevel, parent); +} + +IdleInhibitorV1 *createIdleInhibitorV1(KWayland::Client::Surface *surface) +{ + IdleInhibitManagerV1 *manager = s_waylandConnection.idleInhibitManagerV1; + if (!manager) { + qWarning() << "Could not create an idle_inhibitor_v1 because idle_inhibit_manager_v1 global is not bound"; + return nullptr; + } + + return new IdleInhibitorV1(manager, surface); +} + +bool waitForWindowDestroyed(Window *window) +{ + QSignalSpy destroyedSpy(window, &QObject::destroyed); + if (!destroyedSpy.isValid()) { + return false; + } + return destroyedSpy.wait(); +} + +#if KWIN_BUILD_SCREENLOCKER +bool lockScreen() +{ + if (waylandServer()->isScreenLocked()) { + return false; + } + QSignalSpy lockStateChangedSpy(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged); + if (!lockStateChangedSpy.isValid()) { + return false; + } + ScreenLocker::KSldApp::self()->lock(ScreenLocker::EstablishLock::Immediate); + if (lockStateChangedSpy.count() != 1) { + return false; + } + if (!waylandServer()->isScreenLocked()) { + return false; + } + if (!kwinApp()->screenLockerWatcher()->isLocked()) { + QSignalSpy lockedSpy(kwinApp()->screenLockerWatcher(), &ScreenLockerWatcher::locked); + if (!lockedSpy.isValid()) { + return false; + } + if (!lockedSpy.wait()) { + return false; + } + if (!kwinApp()->screenLockerWatcher()->isLocked()) { + return false; + } + } + return true; +} + +bool unlockScreen() +{ + QSignalSpy lockStateChangedSpy(ScreenLocker::KSldApp::self(), &ScreenLocker::KSldApp::lockStateChanged); + if (!lockStateChangedSpy.isValid()) { + return false; + } + using namespace ScreenLocker; + const auto children = KSldApp::self()->children(); + for (auto it = children.begin(); it != children.end(); ++it) { + if (qstrcmp((*it)->metaObject()->className(), "LogindIntegration") != 0) { + continue; + } + QMetaObject::invokeMethod(*it, "requestUnlock"); + break; + } + if (waylandServer()->isScreenLocked()) { + lockStateChangedSpy.wait(); + } + if (waylandServer()->isScreenLocked()) { + return true; + } + if (kwinApp()->screenLockerWatcher()->isLocked()) { + QSignalSpy lockedSpy(kwinApp()->screenLockerWatcher(), &ScreenLockerWatcher::locked); + if (!lockedSpy.isValid()) { + return false; + } + if (!lockedSpy.wait()) { + return false; + } + if (kwinApp()->screenLockerWatcher()->isLocked()) { + return false; + } + } + return true; +} +#endif // KWIN_BUILD_LOCKSCREEN + +WaylandOutputManagementV2::WaylandOutputManagementV2(struct ::wl_registry *registry, int id, int version) + : QObject() + , QtWayland::kde_output_management_v2() +{ + init(registry, id, version); +} + +WaylandOutputConfigurationV2 *WaylandOutputManagementV2::createConfiguration() +{ + return new WaylandOutputConfigurationV2(create_configuration()); +} + +WaylandOutputConfigurationV2::WaylandOutputConfigurationV2(struct ::kde_output_configuration_v2 *object) + : QObject() + , QtWayland::kde_output_configuration_v2() +{ + init(object); +} + +void WaylandOutputConfigurationV2::kde_output_configuration_v2_applied() +{ + Q_EMIT applied(); +} +void WaylandOutputConfigurationV2::kde_output_configuration_v2_failed() +{ + Q_EMIT failed(); +} + +WaylandOutputDeviceV2Mode::WaylandOutputDeviceV2Mode(struct ::kde_output_device_mode_v2 *object) + : QtWayland::kde_output_device_mode_v2(object) +{ +} + +WaylandOutputDeviceV2Mode::~WaylandOutputDeviceV2Mode() +{ + kde_output_device_mode_v2_destroy(object()); +} + +void WaylandOutputDeviceV2Mode::kde_output_device_mode_v2_size(int32_t width, int32_t height) +{ + m_size = QSize(width, height); +} + +void WaylandOutputDeviceV2Mode::kde_output_device_mode_v2_refresh(int32_t refresh) +{ + m_refreshRate = refresh; +} + +void WaylandOutputDeviceV2Mode::kde_output_device_mode_v2_preferred() +{ + m_preferred = true; +} + +void WaylandOutputDeviceV2Mode::kde_output_device_mode_v2_removed() +{ + Q_EMIT removed(); +} + +int WaylandOutputDeviceV2Mode::refreshRate() const +{ + return m_refreshRate; +} + +QSize WaylandOutputDeviceV2Mode::size() const +{ + return m_size; +} + +bool WaylandOutputDeviceV2Mode::preferred() const +{ + return m_preferred; +} + +bool WaylandOutputDeviceV2Mode::operator==(const WaylandOutputDeviceV2Mode &other) +{ + return m_size == other.m_size && m_refreshRate == other.m_refreshRate && m_preferred == other.m_preferred; +} + +WaylandOutputDeviceV2Mode *WaylandOutputDeviceV2Mode::get(struct ::kde_output_device_mode_v2 *object) +{ + auto mode = QtWayland::kde_output_device_mode_v2::fromObject(object); + return static_cast(mode); +} + +WaylandOutputDeviceV2::WaylandOutputDeviceV2(int id) + : QObject() + , kde_output_device_v2() + , m_id(id) +{ +} + +WaylandOutputDeviceV2::~WaylandOutputDeviceV2() +{ + qDeleteAll(m_modes); + + kde_output_device_v2_destroy(object()); +} + +void WaylandOutputDeviceV2::kde_output_device_v2_geometry(int32_t x, + int32_t y, + int32_t physical_width, + int32_t physical_height, + int32_t subpixel, + const QString &make, + const QString &model, + int32_t transform) +{ + m_pos = QPoint(x, y); + m_physicalSize = QSize(physical_width, physical_height); + m_subpixel = subpixel; + m_manufacturer = make; + m_model = model; + m_transform = transform; +} + +void WaylandOutputDeviceV2::kde_output_device_v2_current_mode(struct ::kde_output_device_mode_v2 *mode) +{ + auto m = WaylandOutputDeviceV2Mode::get(mode); + + if (*m == *m_mode) { + // unchanged + return; + } + m_mode = m; +} + +void WaylandOutputDeviceV2::kde_output_device_v2_mode(struct ::kde_output_device_mode_v2 *mode) +{ + WaylandOutputDeviceV2Mode *m = new WaylandOutputDeviceV2Mode(mode); + // last mode sent is the current one + m_mode = m; + m_modes.append(m); + + connect(m, &WaylandOutputDeviceV2Mode::removed, this, [this, m]() { + m_modes.removeOne(m); + if (m_mode == m) { + if (!m_modes.isEmpty()) { + m_mode = m_modes.first(); + } else { + // was last mode + qFatal("KWaylandBackend: no output modes available anymore, this seems like a compositor bug"); + } + } + + delete m; + }); +} + +QString WaylandOutputDeviceV2::modeId() const +{ + return QString::number(m_modes.indexOf(m_mode)); +} + +WaylandOutputDeviceV2Mode *WaylandOutputDeviceV2::deviceModeFromId(const int modeId) const +{ + return m_modes.at(modeId); +} + +QString WaylandOutputDeviceV2::modeName(const WaylandOutputDeviceV2Mode *m) const +{ + return QString::number(m->size().width()) + QLatin1Char('x') + QString::number(m->size().height()) + QLatin1Char('@') + + QString::number(qRound(m->refreshRate() / 1000.0)); +} + +QString WaylandOutputDeviceV2::name() const +{ + return QStringLiteral("%1 %2").arg(m_manufacturer, m_model); +} + +QDebug operator<<(QDebug dbg, const WaylandOutputDeviceV2 *output) +{ + dbg << "WaylandOutput(Id:" << output->id() << ", Name:" << QString(output->manufacturer() + QLatin1Char(' ') + output->model()) << ")"; + return dbg; +} + +void WaylandOutputDeviceV2::kde_output_device_v2_done() +{ + Q_EMIT done(); +} + +void WaylandOutputDeviceV2::kde_output_device_v2_scale(wl_fixed_t factor) +{ + m_factor = wl_fixed_to_double(factor); +} + +void WaylandOutputDeviceV2::kde_output_device_v2_edid(const QString &edid) +{ + m_edid = QByteArray::fromBase64(edid.toUtf8()); +} + +void WaylandOutputDeviceV2::kde_output_device_v2_enabled(int32_t enabled) +{ + if (m_enabled != enabled) { + m_enabled = enabled; + Q_EMIT enabledChanged(); + } +} + +void WaylandOutputDeviceV2::kde_output_device_v2_uuid(const QString &uuid) +{ + m_uuid = uuid; +} + +void WaylandOutputDeviceV2::kde_output_device_v2_serial_number(const QString &serialNumber) +{ + m_serialNumber = serialNumber; +} + +void WaylandOutputDeviceV2::kde_output_device_v2_eisa_id(const QString &eisaId) +{ + m_eisaId = eisaId; +} + +void WaylandOutputDeviceV2::kde_output_device_v2_capabilities(uint32_t flags) +{ + m_flags = flags; +} + +void WaylandOutputDeviceV2::kde_output_device_v2_overscan(uint32_t overscan) +{ + m_overscan = overscan; +} + +void WaylandOutputDeviceV2::kde_output_device_v2_vrr_policy(uint32_t vrr_policy) +{ + m_vrr_policy = vrr_policy; +} + +void WaylandOutputDeviceV2::kde_output_device_v2_rgb_range(uint32_t rgb_range) +{ + m_rgbRange = rgb_range; +} + +QByteArray WaylandOutputDeviceV2::edid() const +{ + return m_edid; +} + +bool WaylandOutputDeviceV2::enabled() const +{ + return m_enabled; +} + +int WaylandOutputDeviceV2::id() const +{ + return m_id; +} + +qreal WaylandOutputDeviceV2::scale() const +{ + return m_factor; +} + +QString WaylandOutputDeviceV2::manufacturer() const +{ + return m_manufacturer; +} + +QString WaylandOutputDeviceV2::model() const +{ + return m_model; +} + +QPoint WaylandOutputDeviceV2::globalPosition() const +{ + return m_pos; +} + +QSize WaylandOutputDeviceV2::pixelSize() const +{ + return m_mode->size(); +} + +int WaylandOutputDeviceV2::refreshRate() const +{ + return m_mode->refreshRate(); +} + +uint32_t WaylandOutputDeviceV2::vrrPolicy() const +{ + return m_vrr_policy; +} + +uint32_t WaylandOutputDeviceV2::overscan() const +{ + return m_overscan; +} + +uint32_t WaylandOutputDeviceV2::capabilities() const +{ + return m_flags; +} + +uint32_t WaylandOutputDeviceV2::rgbRange() const +{ + return m_rgbRange; +} + +VirtualInputDevice::VirtualInputDevice(QObject *parent) + : InputDevice(parent) +{ +} + +void VirtualInputDevice::setPointer(bool set) +{ + m_pointer = set; +} + +void VirtualInputDevice::setKeyboard(bool set) +{ + m_keyboard = set; +} + +void VirtualInputDevice::setTouch(bool set) +{ + m_touch = set; +} + +void VirtualInputDevice::setName(const QString &name) +{ + m_name = name; +} + +QString VirtualInputDevice::sysName() const +{ + return QString(); +} + +QString VirtualInputDevice::name() const +{ + return m_name; +} + +bool VirtualInputDevice::isEnabled() const +{ + return true; +} + +void VirtualInputDevice::setEnabled(bool enabled) +{ + Q_UNUSED(enabled) +} + +LEDs VirtualInputDevice::leds() const +{ + return LEDs(); +} + +void VirtualInputDevice::setLeds(LEDs leds) +{ + Q_UNUSED(leds) +} + +bool VirtualInputDevice::isKeyboard() const +{ + return m_keyboard; +} + +bool VirtualInputDevice::isAlphaNumericKeyboard() const +{ + return m_keyboard; +} + +bool VirtualInputDevice::isPointer() const +{ + return m_pointer; +} + +bool VirtualInputDevice::isTouchpad() const +{ + return false; +} + +bool VirtualInputDevice::isTouch() const +{ + return m_touch; +} + +bool VirtualInputDevice::isTabletTool() const +{ + return false; +} + +bool VirtualInputDevice::isTabletPad() const +{ + return false; +} + +bool VirtualInputDevice::isTabletModeSwitch() const +{ + return false; +} + +bool VirtualInputDevice::isLidSwitch() const +{ + return false; +} + +void keyboardKeyPressed(quint32 key, quint32 time) +{ + auto virtualKeyboard = static_cast(kwinApp())->virtualKeyboard(); + Q_EMIT virtualKeyboard->keyChanged(key, InputRedirection::KeyboardKeyState::KeyboardKeyPressed, time, virtualKeyboard); +} + +void keyboardKeyReleased(quint32 key, quint32 time) +{ + auto virtualKeyboard = static_cast(kwinApp())->virtualKeyboard(); + Q_EMIT virtualKeyboard->keyChanged(key, InputRedirection::KeyboardKeyState::KeyboardKeyReleased, time, virtualKeyboard); +} + +void pointerAxisHorizontal(qreal delta, quint32 time, qint32 discreteDelta, InputRedirection::PointerAxisSource source) +{ + auto virtualPointer = static_cast(kwinApp())->virtualPointer(); + Q_EMIT virtualPointer->pointerAxisChanged(InputRedirection::PointerAxis::PointerAxisHorizontal, delta, discreteDelta, source, time, virtualPointer); +} + +void pointerAxisVertical(qreal delta, quint32 time, qint32 discreteDelta, InputRedirection::PointerAxisSource source) +{ + auto virtualPointer = static_cast(kwinApp())->virtualPointer(); + Q_EMIT virtualPointer->pointerAxisChanged(InputRedirection::PointerAxis::PointerAxisVertical, delta, discreteDelta, source, time, virtualPointer); +} + +void pointerButtonPressed(quint32 button, quint32 time) +{ + auto virtualPointer = static_cast(kwinApp())->virtualPointer(); + Q_EMIT virtualPointer->pointerButtonChanged(button, InputRedirection::PointerButtonState::PointerButtonPressed, time, virtualPointer); +} + +void pointerButtonReleased(quint32 button, quint32 time) +{ + auto virtualPointer = static_cast(kwinApp())->virtualPointer(); + Q_EMIT virtualPointer->pointerButtonChanged(button, InputRedirection::PointerButtonState::PointerButtonReleased, time, virtualPointer); +} + +void pointerMotion(const QPointF &position, quint32 time) +{ + auto virtualPointer = static_cast(kwinApp())->virtualPointer(); + Q_EMIT virtualPointer->pointerMotionAbsolute(position, time, virtualPointer); +} + +void touchCancel() +{ + auto virtualTouch = static_cast(kwinApp())->virtualTouch(); + Q_EMIT virtualTouch->touchCanceled(virtualTouch); +} + +void touchDown(qint32 id, const QPointF &pos, quint32 time) +{ + auto virtualTouch = static_cast(kwinApp())->virtualTouch(); + Q_EMIT virtualTouch->touchDown(id, pos, time, virtualTouch); +} + +void touchMotion(qint32 id, const QPointF &pos, quint32 time) +{ + auto virtualTouch = static_cast(kwinApp())->virtualTouch(); + Q_EMIT virtualTouch->touchMotion(id, pos, time, virtualTouch); +} + +void touchUp(qint32 id, quint32 time) +{ + auto virtualTouch = static_cast(kwinApp())->virtualTouch(); + Q_EMIT virtualTouch->touchUp(id, time, virtualTouch); +} +} +} diff --git a/autotests/integration/test_virtualkeyboard_dbus.cpp b/autotests/integration/test_virtualkeyboard_dbus.cpp new file mode 100644 index 0000000..79849e5 --- /dev/null +++ b/autotests/integration/test_virtualkeyboard_dbus.cpp @@ -0,0 +1,125 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2017 Martin Flöser + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "kwin_wayland_test.h" + +#include "core/platform.h" +#include "main.h" +#include "virtualkeyboard_dbus.h" +#include "wayland_server.h" + +#include +#include +#include +#include +#include + +#include + +using KWin::VirtualKeyboardDBus; +using namespace KWin; +using namespace KWin::Test; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_virtualkeyboarddbus-0"); + +class VirtualKeyboardDBusTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void testEnabled(); + void testRequestEnabled_data(); + void testRequestEnabled(); + void init(); + void cleanup(); +}; + +void VirtualKeyboardDBusTest::initTestCase() +{ + QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.kwin.testvirtualkeyboard")); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + static_cast(kwinApp())->setInputMethodServerToStart("internal"); + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + + QVERIFY(setupWaylandConnection(AdditionalWaylandInterface::Seat | AdditionalWaylandInterface::InputMethodV1 | AdditionalWaylandInterface::TextInputManagerV2 | AdditionalWaylandInterface::TextInputManagerV3)); +} + +void VirtualKeyboardDBusTest::init() +{ + kwinApp()->inputMethod()->setEnabled(false); +} + +void VirtualKeyboardDBusTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void VirtualKeyboardDBusTest::testEnabled() +{ + VirtualKeyboardDBus dbus(KWin::kwinApp()->inputMethod()); + OrgKdeKwinVirtualKeyboardInterface iface(QStringLiteral("org.kde.kwin.testvirtualkeyboard"), QStringLiteral("/VirtualKeyboard"), QDBusConnection::sessionBus()); + QSignalSpy helperChangedSpy(&iface, &OrgKdeKwinVirtualKeyboardInterface::enabledChanged); + + QCOMPARE(dbus.isEnabled(), false); + QCOMPARE(dbus.property("enabled").toBool(), false); + QSignalSpy enabledChangedSpy(&dbus, &VirtualKeyboardDBus::enabledChanged); + + QVERIFY(iface.isValid()); + QCOMPARE(iface.enabled(), false); + + dbus.setEnabled(true); + QCOMPARE(enabledChangedSpy.count(), 1); + QVERIFY(helperChangedSpy.wait()); + QCOMPARE(helperChangedSpy.count(), 1); + QCOMPARE(dbus.isEnabled(), true); + QCOMPARE(dbus.property("enabled").toBool(), true); + QCOMPARE(iface.enabled(), true); + + // setting again to enabled should not change anything + dbus.setEnabled(true); + QCOMPARE(enabledChangedSpy.count(), 1); + + // back to false + dbus.setEnabled(false); + QCOMPARE(enabledChangedSpy.count(), 2); + QVERIFY(helperChangedSpy.wait()); + QCOMPARE(helperChangedSpy.count(), 2); + QCOMPARE(dbus.isEnabled(), false); + QCOMPARE(dbus.property("enabled").toBool(), false); + QCOMPARE(iface.enabled(), false); +} + +void VirtualKeyboardDBusTest::testRequestEnabled_data() +{ + QTest::addColumn("method"); + QTest::addColumn("expectedResult"); + + QTest::newRow("enable") << QStringLiteral("setEnabled") << true; + QTest::newRow("disable") << QStringLiteral("setEnabled") << false; +} + +void VirtualKeyboardDBusTest::testRequestEnabled() +{ + QFETCH(QString, method); + QFETCH(bool, expectedResult); + + VirtualKeyboardDBus dbus(KWin::kwinApp()->inputMethod()); + OrgKdeKwinVirtualKeyboardInterface iface(QStringLiteral("org.kde.kwin.testvirtualkeyboard"), QStringLiteral("/VirtualKeyboard"), QDBusConnection::sessionBus()); + + iface.setEnabled(expectedResult); + QTRY_COMPARE(iface.enabled(), expectedResult); +} + +WAYLANDTEST_MAIN(VirtualKeyboardDBusTest) +#include "test_virtualkeyboard_dbus.moc" diff --git a/autotests/integration/touch_input_test.cpp b/autotests/integration/touch_input_test.cpp new file mode 100644 index 0000000..87b3c90 --- /dev/null +++ b/autotests/integration/touch_input_test.cpp @@ -0,0 +1,429 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "touch_input.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include +#include +#include +#include +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_touch_input-0"); + +class TouchInputTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testTouchHidesCursor(); + void testMultipleTouchPoints_data(); + void testMultipleTouchPoints(); + void testCancel(); + void testTouchMouseAction(); + void testTouchPointCount(); + void testUpdateFocusOnDecorationDestroy(); + void testGestureDetection(); + +private: + std::pair> showWindow(bool decorated = false); + KWayland::Client::Touch *m_touch = nullptr; +}; + +void TouchInputTest::initTestCase() +{ + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); +} + +void TouchInputTest::init() +{ + using namespace KWayland::Client; + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::XdgDecorationV1)); + QVERIFY(Test::waitForWaylandTouch()); + m_touch = Test::waylandSeat()->createTouch(Test::waylandSeat()); + QVERIFY(m_touch); + QVERIFY(m_touch->isValid()); + + workspace()->setActiveOutput(QPoint(640, 512)); + Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void TouchInputTest::cleanup() +{ + delete m_touch; + m_touch = nullptr; + Test::destroyWaylandConnection(); +} + +std::pair> TouchInputTest::showWindow(bool decorated) +{ + using namespace KWayland::Client; +#define VERIFY(statement) \ + if (!QTest::qVerify((statement), #statement, "", __FILE__, __LINE__)) \ + return {nullptr, nullptr}; +#define COMPARE(actual, expected) \ + if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)) \ + return {nullptr, nullptr}; + + std::unique_ptr surface = Test::createSurface(); + VERIFY(surface.get()); + Test::XdgToplevel *shellSurface = Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly, surface.get()); + VERIFY(shellSurface); + if (decorated) { + auto decoration = Test::createXdgToplevelDecorationV1(shellSurface, shellSurface); + decoration->set_mode(Test::XdgToplevelDecorationV1::mode_server_side); + } + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + VERIFY(surfaceConfigureRequestedSpy.wait()); + // let's render + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + + VERIFY(window); + COMPARE(workspace()->activeWindow(), window); + +#undef VERIFY +#undef COMPARE + + return {window, std::move(surface)}; +} + +void TouchInputTest::testTouchHidesCursor() +{ + QCOMPARE(Cursors::self()->isCursorHidden(), false); + quint32 timestamp = 1; + Test::touchDown(1, QPointF(125, 125), timestamp++); + QCOMPARE(Cursors::self()->isCursorHidden(), true); + Test::touchDown(2, QPointF(130, 125), timestamp++); + Test::touchUp(2, timestamp++); + Test::touchUp(1, timestamp++); + + // now a mouse event should show the cursor again + Test::pointerMotion(QPointF(0, 0), timestamp++); + QCOMPARE(Cursors::self()->isCursorHidden(), false); + + // touch should hide again + Test::touchDown(1, QPointF(125, 125), timestamp++); + Test::touchUp(1, timestamp++); + QCOMPARE(Cursors::self()->isCursorHidden(), true); + + // wheel should also show + Test::pointerAxisVertical(1.0, timestamp++); + QCOMPARE(Cursors::self()->isCursorHidden(), false); +} + +void TouchInputTest::testMultipleTouchPoints_data() +{ + QTest::addColumn("decorated"); + + QTest::newRow("undecorated") << false; + QTest::newRow("decorated") << true; +} + +void TouchInputTest::testMultipleTouchPoints() +{ + using namespace KWayland::Client; + QFETCH(bool, decorated); + auto [window, surface] = showWindow(decorated); + QCOMPARE(window->isDecorated(), decorated); + window->move(QPoint(100, 100)); + QVERIFY(window); + QSignalSpy sequenceStartedSpy(m_touch, &Touch::sequenceStarted); + QSignalSpy pointAddedSpy(m_touch, &Touch::pointAdded); + QSignalSpy pointMovedSpy(m_touch, &Touch::pointMoved); + QSignalSpy pointRemovedSpy(m_touch, &Touch::pointRemoved); + QSignalSpy endedSpy(m_touch, &Touch::sequenceEnded); + + quint32 timestamp = 1; + Test::touchDown(1, QPointF(125, 125) + window->clientPos(), timestamp++); + QVERIFY(sequenceStartedSpy.wait()); + QCOMPARE(sequenceStartedSpy.count(), 1); + QCOMPARE(m_touch->sequence().count(), 1); + QCOMPARE(m_touch->sequence().first()->isDown(), true); + QCOMPARE(m_touch->sequence().first()->position(), QPointF(25, 25)); + QCOMPARE(pointAddedSpy.count(), 0); + QCOMPARE(pointMovedSpy.count(), 0); + + // a point outside the window + Test::touchDown(2, QPointF(0, 0) + window->clientPos(), timestamp++); + QVERIFY(pointAddedSpy.wait()); + QCOMPARE(pointAddedSpy.count(), 1); + QCOMPARE(m_touch->sequence().count(), 2); + QCOMPARE(m_touch->sequence().at(1)->isDown(), true); + QCOMPARE(m_touch->sequence().at(1)->position(), QPointF(-100, -100)); + QCOMPARE(pointMovedSpy.count(), 0); + + // let's move that one + Test::touchMotion(2, QPointF(100, 100) + window->clientPos(), timestamp++); + QVERIFY(pointMovedSpy.wait()); + QCOMPARE(pointMovedSpy.count(), 1); + QCOMPARE(m_touch->sequence().count(), 2); + QCOMPARE(m_touch->sequence().at(1)->isDown(), true); + QCOMPARE(m_touch->sequence().at(1)->position(), QPointF(0, 0)); + + Test::touchUp(1, timestamp++); + QVERIFY(pointRemovedSpy.wait()); + QCOMPARE(pointRemovedSpy.count(), 1); + QCOMPARE(m_touch->sequence().count(), 2); + QCOMPARE(m_touch->sequence().first()->isDown(), false); + QCOMPARE(endedSpy.count(), 0); + + Test::touchUp(2, timestamp++); + QVERIFY(pointRemovedSpy.wait()); + QCOMPARE(pointRemovedSpy.count(), 2); + QCOMPARE(m_touch->sequence().count(), 2); + QCOMPARE(m_touch->sequence().first()->isDown(), false); + QCOMPARE(m_touch->sequence().at(1)->isDown(), false); + QCOMPARE(endedSpy.count(), 1); +} + +void TouchInputTest::testCancel() +{ + using namespace KWayland::Client; + auto [window, surface] = showWindow(); + window->move(QPoint(100, 100)); + QVERIFY(window); + QSignalSpy sequenceStartedSpy(m_touch, &Touch::sequenceStarted); + QSignalSpy cancelSpy(m_touch, &Touch::sequenceCanceled); + QSignalSpy pointRemovedSpy(m_touch, &Touch::pointRemoved); + + quint32 timestamp = 1; + Test::touchDown(1, QPointF(125, 125), timestamp++); + QVERIFY(sequenceStartedSpy.wait()); + QCOMPARE(sequenceStartedSpy.count(), 1); + + // cancel + Test::touchCancel(); + QVERIFY(cancelSpy.wait()); + QCOMPARE(cancelSpy.count(), 1); +} + +void TouchInputTest::testTouchMouseAction() +{ + // this test verifies that a touch down on an inactive window will activate it + using namespace KWayland::Client; + // create two windows + auto [c1, surface] = showWindow(); + QVERIFY(c1); + auto [c2, surface2] = showWindow(); + QVERIFY(c2); + + QVERIFY(!c1->isActive()); + QVERIFY(c2->isActive()); + + // also create a sequence started spy as the touch event should be passed through + QSignalSpy sequenceStartedSpy(m_touch, &Touch::sequenceStarted); + + quint32 timestamp = 1; + Test::touchDown(1, c1->frameGeometry().center(), timestamp++); + QVERIFY(c1->isActive()); + + QVERIFY(sequenceStartedSpy.wait()); + QCOMPARE(sequenceStartedSpy.count(), 1); + + // cleanup + input()->touch()->cancel(); +} + +void TouchInputTest::testTouchPointCount() +{ + QCOMPARE(input()->touch()->touchPointCount(), 0); + quint32 timestamp = 1; + Test::touchDown(0, QPointF(125, 125), timestamp++); + Test::touchDown(1, QPointF(125, 125), timestamp++); + Test::touchDown(2, QPointF(125, 125), timestamp++); + QCOMPARE(input()->touch()->touchPointCount(), 3); + + Test::touchUp(1, timestamp++); + QCOMPARE(input()->touch()->touchPointCount(), 2); + + input()->touch()->cancel(); + QCOMPARE(input()->touch()->touchPointCount(), 0); +} + +void TouchInputTest::testUpdateFocusOnDecorationDestroy() +{ + // This test verifies that a maximized window gets it's touch focus + // if decoration was focused and then destroyed on maximize with BorderlessMaximizedWindows option. + + QSignalSpy sequenceEndedSpy(m_touch, &KWayland::Client::Touch::sequenceEnded); + + // Enable the borderless maximized windows option. + auto group = kwinApp()->config()->group("Windows"); + group.writeEntry("BorderlessMaximizedWindows", true); + group.sync(); + Workspace::self()->slotReconfigure(); + QCOMPARE(options->borderlessMaximizedWindows(), true); + + // Create the test window. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + std::unique_ptr decoration(Test::createXdgToplevelDecorationV1(shellSurface.get())); + + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + QSignalSpy decorationConfigureRequestedSpy(decoration.get(), &Test::XdgToplevelDecorationV1::configureRequested); + decoration->set_mode(Test::XdgToplevelDecorationV1::mode_server_side); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // Wait for the initial configure event. + Test::XdgToplevel::States states; + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(0, 0)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Map the window. + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(window->isActive()); + QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(window->isDecorated(), true); + + // We should receive a configure event when the window becomes active. + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Simulate decoration hover + quint32 timestamp = 0; + Test::touchDown(1, window->frameGeometry().topLeft(), timestamp++); + QVERIFY(input()->touch()->decoration()); + + // Maximize when on decoration + workspace()->slotWindowMaximize(); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); + + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(1280, 1024), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(window->maximizeMode(), MaximizeFull); + QCOMPARE(window->requestedMaximizeMode(), MaximizeFull); + QCOMPARE(window->isDecorated(), false); + + // Window should have focus + QVERIFY(!input()->touch()->decoration()); + Test::touchUp(1, timestamp++); + QVERIFY(!sequenceEndedSpy.wait(100)); + Test::touchDown(2, window->frameGeometry().center(), timestamp++); + Test::touchUp(2, timestamp++); + QVERIFY(sequenceEndedSpy.wait()); + + // Destroy the window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void TouchInputTest::testGestureDetection() +{ + bool callbackTriggered = false; + const auto callback = [&callbackTriggered](float progress) { + Q_UNUSED(progress); + callbackTriggered = true; + qWarning() << "progress callback!" << progress; + }; + QAction action; + input()->forceRegisterTouchscreenSwipeShortcut(SwipeDirection::Right, 3, &action, callback); + + // verify that gestures are detected + + quint32 timestamp = 1; + Test::touchDown(0, QPointF(500, 125), timestamp++); + Test::touchDown(1, QPointF(500, 125), timestamp++); + Test::touchDown(2, QPointF(500, 125), timestamp++); + + Test::touchMotion(0, QPointF(100, 125), timestamp++); + QVERIFY(callbackTriggered); + + // verify that gestures are canceled properly + QSignalSpy gestureCancelled(&action, &QAction::triggered); + Test::touchUp(0, timestamp++); + QVERIFY(gestureCancelled.wait()); + + Test::touchUp(1, timestamp++); + Test::touchUp(2, timestamp++); + + callbackTriggered = false; + + // verify that touch points too far apart don't trigger a gesture + Test::touchDown(0, QPointF(125, 125), timestamp++); + Test::touchDown(1, QPointF(10000, 125), timestamp++); + Test::touchDown(2, QPointF(125, 125), timestamp++); + QVERIFY(!callbackTriggered); + + Test::touchUp(0, timestamp++); + Test::touchUp(1, timestamp++); + Test::touchUp(2, timestamp++); + + // verify that touch points triggered too slow don't trigger a gesture + Test::touchDown(0, QPointF(125, 125), timestamp++); + timestamp += 1000; + Test::touchDown(1, QPointF(125, 125), timestamp++); + Test::touchDown(2, QPointF(125, 125), timestamp++); + QVERIFY(!callbackTriggered); + + Test::touchUp(0, timestamp++); + Test::touchUp(1, timestamp++); + Test::touchUp(2, timestamp++); + + // verify that after a gesture has been canceled but never initiated, gestures still work + Test::touchDown(0, QPointF(500, 125), timestamp++); + Test::touchDown(1, QPointF(500, 125), timestamp++); + Test::touchDown(2, QPointF(500, 125), timestamp++); + + Test::touchMotion(0, QPointF(100, 125), timestamp++); + Test::touchMotion(1, QPointF(100, 125), timestamp++); + Test::touchMotion(2, QPointF(100, 125), timestamp++); + QVERIFY(callbackTriggered); + + Test::touchUp(0, timestamp++); + Test::touchUp(1, timestamp++); + Test::touchUp(2, timestamp++); +} +} + +WAYLANDTEST_MAIN(KWin::TouchInputTest) +#include "touch_input_test.moc" diff --git a/autotests/integration/transient_placement.cpp b/autotests/integration/transient_placement.cpp new file mode 100644 index 0000000..912733d --- /dev/null +++ b/autotests/integration/transient_placement.cpp @@ -0,0 +1,555 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "wayland/seat_interface.h" +#include "wayland/surface_interface.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct PopupLayout +{ + QRect anchorRect; + QSize size; + quint32 anchor = 0; + quint32 gravity = 0; + quint32 constraint = 0; +}; +Q_DECLARE_METATYPE(PopupLayout) + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_transient_placement-0"); + +class TransientPlacementTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testXdgPopup_data(); + void testXdgPopup(); + void testXdgPopupWithPanel(); +}; + +void TransientPlacementTest::initTestCase() +{ + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); + setenv("QT_QPA_PLATFORM", "wayland", true); +} + +void TransientPlacementTest::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Decoration | Test::AdditionalWaylandInterface::PlasmaShell)); + + workspace()->setActiveOutput(QPoint(640, 512)); + Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void TransientPlacementTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void TransientPlacementTest::testXdgPopup_data() +{ + using namespace KWayland::Client; + + QTest::addColumn("parentSize"); + QTest::addColumn("parentPosition"); + QTest::addColumn("layout"); + QTest::addColumn("expectedGeometry"); + + // parent window is 500,500, starting at 300,300, anchorRect is therefore between 350->750 in both dirs + + // ---------------------------------------------------------------- + // window in the middle, plenty of room either side: Changing anchor + + const PopupLayout layoutAnchorCenter{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_none, + .gravity = Test::XdgPositioner::gravity_bottom_right, + }; + QTest::newRow("anchorCentre") << QSize(500, 500) << QPoint(300, 300) << layoutAnchorCenter << QRect(550, 550, 200, 200); + + const PopupLayout layoutAnchorTopLeft{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_top_left, + .gravity = Test::XdgPositioner::gravity_bottom_right, + }; + QTest::newRow("anchorTopLeft") << QSize(500, 500) << QPoint(300, 300) << layoutAnchorTopLeft << QRect(350, 350, 200, 200); + + const PopupLayout layoutAnchorTop{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_top, + .gravity = Test::XdgPositioner::gravity_bottom_right, + }; + QTest::newRow("anchorTop") << QSize(500, 500) << QPoint(300, 300) << layoutAnchorTop << QRect(550, 350, 200, 200); + + const PopupLayout layoutAnchorTopRight{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_top_right, + .gravity = Test::XdgPositioner::gravity_bottom_right, + }; + QTest::newRow("anchorTopRight") << QSize(500, 500) << QPoint(300, 300) << layoutAnchorTopRight << QRect(750, 350, 200, 200); + + const PopupLayout layoutAnchorRight{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_right, + .gravity = Test::XdgPositioner::gravity_bottom_right, + }; + QTest::newRow("anchorRight") << QSize(500, 500) << QPoint(300, 300) << layoutAnchorRight << QRect(750, 550, 200, 200); + + const PopupLayout layoutAnchorBottomRight{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_bottom_right, + .gravity = Test::XdgPositioner::gravity_bottom_right, + }; + QTest::newRow("anchorBottomRight") << QSize(500, 500) << QPoint(300, 300) << layoutAnchorBottomRight << QRect(750, 750, 200, 200); + + const PopupLayout layoutAnchorBottom{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_bottom, + .gravity = Test::XdgPositioner::gravity_bottom_right, + }; + QTest::newRow("anchorBottom") << QSize(500, 500) << QPoint(300, 300) << layoutAnchorBottom << QRect(550, 750, 200, 200); + + const PopupLayout layoutAnchorBottomLeft{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_bottom_left, + .gravity = Test::XdgPositioner::gravity_bottom_right, + }; + QTest::newRow("anchorBottomLeft") << QSize(500, 500) << QPoint(300, 300) << layoutAnchorBottomLeft << QRect(350, 750, 200, 200); + + const PopupLayout layoutAnchorLeft{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_left, + .gravity = Test::XdgPositioner::gravity_bottom_right, + }; + QTest::newRow("anchorLeft") << QSize(500, 500) << QPoint(300, 300) << layoutAnchorLeft << QRect(350, 550, 200, 200); + + // ---------------------------------------------------------------- + // window in the middle, plenty of room either side: Changing gravity around the bottom right anchor + + const PopupLayout layoutGravityCenter{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_bottom_right, + .gravity = Test::XdgPositioner::gravity_none, + }; + QTest::newRow("gravityCentre") << QSize(500, 500) << QPoint(300, 300) << layoutGravityCenter << QRect(650, 650, 200, 200); + + const PopupLayout layoutGravityTopLeft{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_bottom_right, + .gravity = Test::XdgPositioner::gravity_top_left, + }; + QTest::newRow("gravityTopLeft") << QSize(500, 500) << QPoint(300, 300) << layoutGravityTopLeft << QRect(550, 550, 200, 200); + + const PopupLayout layoutGravityTop{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_bottom_right, + .gravity = Test::XdgPositioner::gravity_top, + }; + QTest::newRow("gravityTop") << QSize(500, 500) << QPoint(300, 300) << layoutGravityTop << QRect(650, 550, 200, 200); + + const PopupLayout layoutGravityTopRight{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_bottom_right, + .gravity = Test::XdgPositioner::gravity_top_right, + }; + QTest::newRow("gravityTopRight") << QSize(500, 500) << QPoint(300, 300) << layoutGravityTopRight << QRect(750, 550, 200, 200); + + const PopupLayout layoutGravityRight{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_bottom_right, + .gravity = Test::XdgPositioner::gravity_right, + }; + QTest::newRow("gravityRight") << QSize(500, 500) << QPoint(300, 300) << layoutGravityRight << QRect(750, 650, 200, 200); + + const PopupLayout layoutGravityBottomRight{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_bottom_right, + .gravity = Test::XdgPositioner::gravity_bottom_right, + }; + QTest::newRow("gravityBottomRight") << QSize(500, 500) << QPoint(300, 300) << layoutGravityBottomRight << QRect(750, 750, 200, 200); + + const PopupLayout layoutGravityBottom{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_bottom_right, + .gravity = Test::XdgPositioner::gravity_bottom, + }; + QTest::newRow("gravityBottom") << QSize(500, 500) << QPoint(300, 300) << layoutGravityBottom << QRect(650, 750, 200, 200); + + const PopupLayout layoutGravityBottomLeft{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_bottom_right, + .gravity = Test::XdgPositioner::gravity_bottom_left, + }; + QTest::newRow("gravityBottomLeft") << QSize(500, 500) << QPoint(300, 300) << layoutGravityBottomLeft << QRect(550, 750, 200, 200); + + const PopupLayout layoutGravityLeft{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_bottom_right, + .gravity = Test::XdgPositioner::gravity_left, + }; + QTest::newRow("gravityLeft") << QSize(500, 500) << QPoint(300, 300) << layoutGravityLeft << QRect(550, 650, 200, 200); + + // ---------------------------------------------------------------- + // constrain and slide + // popup is still 200,200. window moved near edge of screen, popup always comes out towards the screen edge + + const PopupLayout layoutSlideTop{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_top, + .gravity = Test::XdgPositioner::gravity_top, + .constraint = Test::XdgPositioner::constraint_adjustment_slide_x | Test::XdgPositioner::constraint_adjustment_slide_y, + }; + QTest::newRow("constraintSlideTop") << QSize(500, 500) << QPoint(80, 80) << layoutSlideTop << QRect(80 + 250 - 100, 0, 200, 200); + + const PopupLayout layoutSlideLeft{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_left, + .gravity = Test::XdgPositioner::gravity_left, + .constraint = Test::XdgPositioner::constraint_adjustment_slide_x | Test::XdgPositioner::constraint_adjustment_slide_y, + }; + QTest::newRow("constraintSlideLeft") << QSize(500, 500) << QPoint(80, 80) << layoutSlideLeft << QRect(0, 80 + 250 - 100, 200, 200); + + const PopupLayout layoutSlideRight{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_right, + .gravity = Test::XdgPositioner::gravity_right, + .constraint = Test::XdgPositioner::constraint_adjustment_slide_x | Test::XdgPositioner::constraint_adjustment_slide_y, + }; + QTest::newRow("constraintSlideRight") << QSize(500, 500) << QPoint(700, 80) << layoutSlideRight << QRect(1280 - 200, 80 + 250 - 100, 200, 200); + + const PopupLayout layoutSlideBottom{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_bottom, + .gravity = Test::XdgPositioner::gravity_bottom, + .constraint = Test::XdgPositioner::constraint_adjustment_slide_x | Test::XdgPositioner::constraint_adjustment_slide_y, + }; + QTest::newRow("constraintSlideBottom") << QSize(500, 500) << QPoint(80, 500) << layoutSlideBottom << QRect(80 + 250 - 100, 1024 - 200, 200, 200); + + const PopupLayout layoutSlideBottomRight{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_bottom_right, + .gravity = Test::XdgPositioner::gravity_bottom_right, + .constraint = Test::XdgPositioner::constraint_adjustment_slide_x | Test::XdgPositioner::constraint_adjustment_slide_y, + }; + QTest::newRow("constraintSlideBottomRight") << QSize(500, 500) << QPoint(700, 1000) << layoutSlideBottomRight << QRect(1280 - 200, 1024 - 200, 200, 200); + + // ---------------------------------------------------------------- + // constrain and flip + + const PopupLayout layoutFlipTop{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_top, + .gravity = Test::XdgPositioner::gravity_top, + .constraint = Test::XdgPositioner::constraint_adjustment_flip_x | Test::XdgPositioner::constraint_adjustment_flip_y, + }; + QTest::newRow("constraintFlipTop") << QSize(500, 500) << QPoint(80, 80) << layoutFlipTop << QRect(230, 80 + 500 - 50, 200, 200); + + const PopupLayout layoutFlipLeft{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_left, + .gravity = Test::XdgPositioner::gravity_left, + .constraint = Test::XdgPositioner::constraint_adjustment_flip_x | Test::XdgPositioner::constraint_adjustment_flip_y, + }; + QTest::newRow("constraintFlipLeft") << QSize(500, 500) << QPoint(80, 80) << layoutFlipLeft << QRect(80 + 500 - 50, 230, 200, 200); + + const PopupLayout layoutFlipRight{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_right, + .gravity = Test::XdgPositioner::gravity_right, + .constraint = Test::XdgPositioner::constraint_adjustment_flip_x | Test::XdgPositioner::constraint_adjustment_flip_y, + }; + QTest::newRow("constraintFlipRight") << QSize(500, 500) << QPoint(700, 80) << layoutFlipRight << QRect(700 + 50 - 200, 230, 200, 200); + + const PopupLayout layoutFlipBottom{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_bottom, + .gravity = Test::XdgPositioner::gravity_bottom, + .constraint = Test::XdgPositioner::constraint_adjustment_flip_x | Test::XdgPositioner::constraint_adjustment_flip_y, + }; + QTest::newRow("constraintFlipBottom") << QSize(500, 500) << QPoint(80, 500) << layoutFlipBottom << QRect(230, 500 + 50 - 200, 200, 200); + + const PopupLayout layoutFlipBottomRight{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_bottom_right, + .gravity = Test::XdgPositioner::gravity_bottom_right, + .constraint = Test::XdgPositioner::constraint_adjustment_flip_x | Test::XdgPositioner::constraint_adjustment_flip_y, + }; + QTest::newRow("constraintFlipBottomRight") << QSize(500, 500) << QPoint(700, 500) << layoutFlipBottomRight << QRect(700 + 50 - 200, 500 + 50 - 200, 200, 200); + + const PopupLayout layoutFlipRightNoAnchor{ + .anchorRect = QRect(50, 50, 400, 400), + // as popup is positioned in the middle of the parent we need a massive popup to be able to overflow + .size = QSize(400, 400), + .anchor = Test::XdgPositioner::anchor_top, + .gravity = Test::XdgPositioner::gravity_right, + .constraint = Test::XdgPositioner::constraint_adjustment_flip_x | Test::XdgPositioner::constraint_adjustment_flip_y, + }; + QTest::newRow("constraintFlipRightNoAnchor") << QSize(500, 500) << QPoint(700, 80) << layoutFlipRightNoAnchor << QRect(700 + 250 - 400, 330, 400, 400); + + const PopupLayout layoutFlipRightNoGravity{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(300, 200), + .anchor = Test::XdgPositioner::anchor_right, + .gravity = Test::XdgPositioner::gravity_top, + .constraint = Test::XdgPositioner::constraint_adjustment_flip_x | Test::XdgPositioner::constraint_adjustment_flip_y, + }; + QTest::newRow("constraintFlipRightNoGravity") << QSize(500, 500) << QPoint(700, 80) << layoutFlipRightNoGravity << QRect(700 + 50 - 150, 130, 300, 200); + + // ---------------------------------------------------------------- + // resize + + const PopupLayout layoutResizeTop{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_top, + .gravity = Test::XdgPositioner::gravity_top, + .constraint = Test::XdgPositioner::constraint_adjustment_resize_x | Test::XdgPositioner::constraint_adjustment_resize_y, + }; + QTest::newRow("resizeTop") << QSize(500, 500) << QPoint(80, 80) << layoutResizeTop << QRect(80 + 250 - 100, 0, 200, 130); + + const PopupLayout layoutResizeLeft{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_left, + .gravity = Test::XdgPositioner::gravity_left, + .constraint = Test::XdgPositioner::constraint_adjustment_resize_x | Test::XdgPositioner::constraint_adjustment_resize_y, + }; + QTest::newRow("resizeLeft") << QSize(500, 500) << QPoint(80, 80) << layoutResizeLeft << QRect(0, 80 + 250 - 100, 130, 200); + + const PopupLayout layoutResizeRight{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_right, + .gravity = Test::XdgPositioner::gravity_right, + .constraint = Test::XdgPositioner::constraint_adjustment_resize_x | Test::XdgPositioner::constraint_adjustment_resize_y, + }; + QTest::newRow("resizeRight") << QSize(500, 500) << QPoint(700, 80) << layoutResizeRight << QRect(700 + 50 + 400, 80 + 250 - 100, 130, 200); + + const PopupLayout layoutResizeBottom{ + .anchorRect = QRect(50, 50, 400, 400), + .size = QSize(200, 200), + .anchor = Test::XdgPositioner::anchor_bottom, + .gravity = Test::XdgPositioner::gravity_bottom, + .constraint = Test::XdgPositioner::constraint_adjustment_resize_x | Test::XdgPositioner::constraint_adjustment_resize_y, + }; + QTest::newRow("resizeBottom") << QSize(500, 500) << QPoint(80, 500) << layoutResizeBottom << QRect(80 + 250 - 100, 500 + 50 + 400, 200, 74); +} + +void TransientPlacementTest::testXdgPopup() +{ + using namespace KWayland::Client; + + // this test verifies that the position of a transient window is taken from the passed position + // there are no further constraints like window too large to fit screen, cascading transients, etc + // some test cases also verify that the transient fits on the screen + QFETCH(QSize, parentSize); + QFETCH(QPoint, parentPosition); + QFETCH(QRect, expectedGeometry); + const QRect expectedRelativeGeometry = expectedGeometry.translated(-parentPosition); + + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + auto parentShellSurface = Test::createXdgToplevelSurface(surface.get(), Test::waylandCompositor()); + QVERIFY(parentShellSurface); + auto parent = Test::renderAndWaitForShown(surface.get(), parentSize, Qt::blue); + QVERIFY(parent); + + QVERIFY(!parent->isDecorated()); + parent->move(parentPosition); + QCOMPARE(parent->frameGeometry(), QRect(parentPosition, parentSize)); + + // create popup + QFETCH(PopupLayout, layout); + + std::unique_ptr transientSurface = Test::createSurface(); + QVERIFY(transientSurface); + + std::unique_ptr positioner(Test::createXdgPositioner()); + positioner->set_anchor_rect(layout.anchorRect.x(), layout.anchorRect.y(), layout.anchorRect.width(), layout.anchorRect.height()); + positioner->set_size(layout.size.width(), layout.size.height()); + positioner->set_anchor(layout.anchor); + positioner->set_gravity(layout.gravity); + positioner->set_constraint_adjustment(layout.constraint); + std::unique_ptr popup(Test::createXdgPopupSurface(transientSurface.get(), parentShellSurface->xdgSurface(), positioner.get(), Test::CreationSetup::CreateOnly)); + QSignalSpy popupConfigureRequestedSpy(popup.get(), &Test::XdgPopup::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(popup->xdgSurface(), &Test::XdgSurface::configureRequested); + transientSurface->commit(KWayland::Client::Surface::CommitFlag::None); + + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + QCOMPARE(popupConfigureRequestedSpy.last()[0].value(), expectedRelativeGeometry); + popup->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last()[0].toUInt()); + + auto transient = Test::renderAndWaitForShown(transientSurface.get(), expectedRelativeGeometry.size(), Qt::red); + QVERIFY(transient); + + QVERIFY(!transient->isDecorated()); + QVERIFY(transient->hasTransientPlacementHint()); + QCOMPARE(transient->frameGeometry(), expectedGeometry); + + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); // check that we did not get reconfigured +} + +void TransientPlacementTest::testXdgPopupWithPanel() +{ + using namespace KWayland::Client; + + const Output *output = workspace()->activeOutput(); + + std::unique_ptr surface{Test::createSurface()}; + QVERIFY(surface != nullptr); + std::unique_ptr dockShellSurface{Test::createXdgToplevelSurface(surface.get())}; + QVERIFY(dockShellSurface != nullptr); + std::unique_ptr plasmaSurface(Test::waylandPlasmaShell()->createSurface(surface.get())); + QVERIFY(plasmaSurface != nullptr); + plasmaSurface->setRole(PlasmaShellSurface::Role::Panel); + plasmaSurface->setPosition(QPoint(0, output->geometry().height() - 50)); + plasmaSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::AlwaysVisible); + + // now render and map the window + auto dock = Test::renderAndWaitForShown(surface.get(), QSize(1280, 50), Qt::blue); + QVERIFY(dock); + QCOMPARE(dock->windowType(), NET::Dock); + QVERIFY(dock->isDock()); + QCOMPARE(dock->frameGeometry(), QRect(0, output->geometry().height() - 50, 1280, 50)); + QCOMPARE(dock->hasStrut(), true); + QCOMPARE(workspace()->clientArea(PlacementArea, dock), QRect(0, 0, 1280, 1024 - 50)); + QCOMPARE(workspace()->clientArea(FullScreenArea, dock), QRect(0, 0, 1280, 1024)); + + // create parent + std::unique_ptr parentSurface(Test::createSurface()); + QVERIFY(parentSurface); + auto parentShellSurface = Test::createXdgToplevelSurface(parentSurface.get()); + QVERIFY(parentShellSurface); + auto parent = Test::renderAndWaitForShown(parentSurface.get(), {800, 600}, Qt::blue); + QVERIFY(parent); + + QVERIFY(!parent->isDecorated()); + parent->move(QPointF(0, output->geometry().height() - 600)); + parent->keepInArea(workspace()->clientArea(PlacementArea, parent)); + QCOMPARE(parent->frameGeometry(), QRect(0, output->geometry().height() - 600 - 50, 800, 600)); + + std::unique_ptr transientSurface(Test::createSurface()); + QVERIFY(transientSurface); + + std::unique_ptr positioner(Test::createXdgPositioner()); + positioner->set_size(200, 200); + positioner->set_anchor_rect(50, 500, 200, 200); + + std::unique_ptr transientShellSurface(Test::createXdgPopupSurface(transientSurface.get(), parentShellSurface->xdgSurface(), positioner.get())); + auto transient = Test::renderAndWaitForShown(transientSurface.get(), QSize(200, 200), Qt::red); + QVERIFY(transient); + + QVERIFY(!transient->isDecorated()); + QVERIFY(transient->hasTransientPlacementHint()); + + QCOMPARE(transient->frameGeometry(), QRect(50, output->geometry().height() - 200 - 50, 200, 200)); + + transientShellSurface.reset(); + transientSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(transient)); + + // now parent to fullscreen - on fullscreen the panel is ignored + QSignalSpy toplevelConfigureRequestedSpy(parentShellSurface, &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(parentShellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + parent->setFullScreen(true); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + parentShellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + QSignalSpy frameGeometryChangedSpy{parent, &Window::frameGeometryChanged}; + Test::render(parentSurface.get(), toplevelConfigureRequestedSpy.last().at(0).toSize(), Qt::red); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(parent->frameGeometry(), output->geometry()); + QVERIFY(parent->isFullScreen()); + + // another transient, with same hints as before from bottom of window + transientSurface = Test::createSurface(); + QVERIFY(transientSurface); + + const QRect anchorRect2(50, output->geometry().height() - 100, 200, 200); + std::unique_ptr positioner2(Test::createXdgPositioner()); + positioner2->set_size(200, 200); + positioner2->set_anchor_rect(anchorRect2.x(), anchorRect2.y(), anchorRect2.width(), anchorRect2.height()); + transientShellSurface.reset(Test::createXdgPopupSurface(transientSurface.get(), parentShellSurface->xdgSurface(), positioner2.get())); + transient = Test::renderAndWaitForShown(transientSurface.get(), QSize(200, 200), Qt::red); + QVERIFY(transient); + + QVERIFY(!transient->isDecorated()); + QVERIFY(transient->hasTransientPlacementHint()); + + QCOMPARE(transient->frameGeometry(), QRect(50, output->geometry().height() - 200, 200, 200)); +} + +} + +WAYLANDTEST_MAIN(KWin::TransientPlacementTest) +#include "transient_placement.moc" diff --git a/autotests/integration/virtual_desktop_test.cpp b/autotests/integration/virtual_desktop_test.cpp new file mode 100644 index 0000000..e01fad1 --- /dev/null +++ b/autotests/integration/virtual_desktop_test.cpp @@ -0,0 +1,283 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2017 Martin Flöser + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/platform.h" +#include "main.h" +#include "virtualdesktops.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include + +using namespace KWin; +using namespace KWayland::Client; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_virtualdesktop-0"); + +class VirtualDesktopTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testNetCurrentDesktop(); + void testLastDesktopRemoved(); + void testWindowOnMultipleDesktops(); + void testRemoveDesktopWithWindow(); +}; + +void VirtualDesktopTest::initTestCase() +{ + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + + kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); + qputenv("KWIN_XKB_DEFAULT_KEYMAP", "1"); + qputenv("XKB_DEFAULT_RULES", "evdev"); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + + if (kwinApp()->x11Connection()) { + // verify the current desktop x11 property on startup, see BUG: 391034 + Xcb::Atom currentDesktopAtom("_NET_CURRENT_DESKTOP"); + QVERIFY(currentDesktopAtom.isValid()); + Xcb::Property currentDesktop(0, kwinApp()->x11RootWindow(), currentDesktopAtom, XCB_ATOM_CARDINAL, 0, 1); + bool ok = true; + QCOMPARE(currentDesktop.value(0, &ok), 0); + QVERIFY(ok); + } +} + +void VirtualDesktopTest::init() +{ + QVERIFY(Test::setupWaylandConnection()); + workspace()->setActiveOutput(QPoint(640, 512)); + VirtualDesktopManager::self()->setCount(1); +} + +void VirtualDesktopTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void VirtualDesktopTest::testNetCurrentDesktop() +{ + if (!kwinApp()->x11Connection()) { + QSKIP("Skipped on Wayland only"); + } + QCOMPARE(VirtualDesktopManager::self()->count(), 1u); + VirtualDesktopManager::self()->setCount(4); + QCOMPARE(VirtualDesktopManager::self()->count(), 4u); + + Xcb::Atom currentDesktopAtom("_NET_CURRENT_DESKTOP"); + QVERIFY(currentDesktopAtom.isValid()); + Xcb::Property currentDesktop(0, kwinApp()->x11RootWindow(), currentDesktopAtom, XCB_ATOM_CARDINAL, 0, 1); + bool ok = true; + QCOMPARE(currentDesktop.value(0, &ok), 0); + QVERIFY(ok); + + // go to desktop 2 + VirtualDesktopManager::self()->setCurrent(2); + currentDesktop = Xcb::Property(0, kwinApp()->x11RootWindow(), currentDesktopAtom, XCB_ATOM_CARDINAL, 0, 1); + QCOMPARE(currentDesktop.value(0, &ok), 1); + QVERIFY(ok); + + // go to desktop 3 + VirtualDesktopManager::self()->setCurrent(3); + currentDesktop = Xcb::Property(0, kwinApp()->x11RootWindow(), currentDesktopAtom, XCB_ATOM_CARDINAL, 0, 1); + QCOMPARE(currentDesktop.value(0, &ok), 2); + QVERIFY(ok); + + // go to desktop 4 + VirtualDesktopManager::self()->setCurrent(4); + currentDesktop = Xcb::Property(0, kwinApp()->x11RootWindow(), currentDesktopAtom, XCB_ATOM_CARDINAL, 0, 1); + QCOMPARE(currentDesktop.value(0, &ok), 3); + QVERIFY(ok); + + // and back to first + VirtualDesktopManager::self()->setCurrent(1); + currentDesktop = Xcb::Property(0, kwinApp()->x11RootWindow(), currentDesktopAtom, XCB_ATOM_CARDINAL, 0, 1); + QCOMPARE(currentDesktop.value(0, &ok), 0); + QVERIFY(ok); +} + +void VirtualDesktopTest::testLastDesktopRemoved() +{ + // first create a new desktop + QCOMPARE(VirtualDesktopManager::self()->count(), 1u); + VirtualDesktopManager::self()->setCount(2); + QCOMPARE(VirtualDesktopManager::self()->count(), 2u); + + // switch to last desktop + VirtualDesktopManager::self()->setCurrent(VirtualDesktopManager::self()->desktops().last()); + QCOMPARE(VirtualDesktopManager::self()->current(), 2u); + + // now create a window on this desktop + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + + QVERIFY(window); + QCOMPARE(window->desktop(), 2); + QSignalSpy desktopPresenceChangedSpy(window, &Window::desktopPresenceChanged); + + QCOMPARE(window->desktops().count(), 1u); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), window->desktops().first()); + + // and remove last desktop + VirtualDesktopManager::self()->setCount(1); + QCOMPARE(VirtualDesktopManager::self()->count(), 1u); + // now the window should be moved as well + QTRY_COMPARE(desktopPresenceChangedSpy.count(), 1); + QCOMPARE(window->desktop(), 1); + + QCOMPARE(window->desktops().count(), 1u); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), window->desktops().first()); +} + +void VirtualDesktopTest::testWindowOnMultipleDesktops() +{ + // first create two new desktops + QCOMPARE(VirtualDesktopManager::self()->count(), 1u); + VirtualDesktopManager::self()->setCount(3); + QCOMPARE(VirtualDesktopManager::self()->count(), 3u); + + // switch to last desktop + VirtualDesktopManager::self()->setCurrent(VirtualDesktopManager::self()->desktops().last()); + QCOMPARE(VirtualDesktopManager::self()->current(), 3u); + + // now create a window on this desktop + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + + QVERIFY(window); + QCOMPARE(window->desktop(), 3u); + QSignalSpy desktopPresenceChangedSpy(window, &Window::desktopPresenceChanged); + + QCOMPARE(window->desktops().count(), 1u); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), window->desktops().first()); + + // Set the window on desktop 2 as well + window->enterDesktop(VirtualDesktopManager::self()->desktopForX11Id(2)); + QCOMPARE(window->desktops().count(), 2u); + QCOMPARE(VirtualDesktopManager::self()->desktops()[2], window->desktops()[0]); + QCOMPARE(VirtualDesktopManager::self()->desktops()[1], window->desktops()[1]); + QVERIFY(window->isOnDesktop(2)); + QVERIFY(window->isOnDesktop(3)); + + // leave desktop 3 + window->leaveDesktop(VirtualDesktopManager::self()->desktopForX11Id(3)); + QCOMPARE(window->desktops().count(), 1u); + // leave desktop 2 + window->leaveDesktop(VirtualDesktopManager::self()->desktopForX11Id(2)); + QCOMPARE(window->desktops().count(), 0u); + // we should be on all desktops now + QVERIFY(window->isOnAllDesktops()); + // put on desktop 1 + window->enterDesktop(VirtualDesktopManager::self()->desktopForX11Id(1)); + QVERIFY(window->isOnDesktop(1)); + QVERIFY(!window->isOnDesktop(2)); + QVERIFY(!window->isOnDesktop(3)); + QCOMPARE(window->desktops().count(), 1u); + // put on desktop 2 + window->enterDesktop(VirtualDesktopManager::self()->desktopForX11Id(2)); + QVERIFY(window->isOnDesktop(1)); + QVERIFY(window->isOnDesktop(2)); + QVERIFY(!window->isOnDesktop(3)); + QCOMPARE(window->desktops().count(), 2u); + // put on desktop 3 + window->enterDesktop(VirtualDesktopManager::self()->desktopForX11Id(3)); + QVERIFY(window->isOnDesktop(1)); + QVERIFY(window->isOnDesktop(2)); + QVERIFY(window->isOnDesktop(3)); + QCOMPARE(window->desktops().count(), 3u); + + // entering twice dooes nothing + window->enterDesktop(VirtualDesktopManager::self()->desktopForX11Id(3)); + QCOMPARE(window->desktops().count(), 3u); + + // adding to "all desktops" results in just that one desktop + window->setOnAllDesktops(true); + QCOMPARE(window->desktops().count(), 0u); + window->enterDesktop(VirtualDesktopManager::self()->desktopForX11Id(3)); + QVERIFY(window->isOnDesktop(3)); + QCOMPARE(window->desktops().count(), 1u); + + // leaving a desktop on "all desktops" puts on everything else + window->setOnAllDesktops(true); + QCOMPARE(window->desktops().count(), 0u); + window->leaveDesktop(VirtualDesktopManager::self()->desktopForX11Id(3)); + QVERIFY(window->isOnDesktop(1)); + QVERIFY(window->isOnDesktop(2)); + QCOMPARE(window->desktops().count(), 2u); +} + +void VirtualDesktopTest::testRemoveDesktopWithWindow() +{ + // first create two new desktops + QCOMPARE(VirtualDesktopManager::self()->count(), 1u); + VirtualDesktopManager::self()->setCount(3); + QCOMPARE(VirtualDesktopManager::self()->count(), 3u); + + // switch to last desktop + VirtualDesktopManager::self()->setCurrent(VirtualDesktopManager::self()->desktops().last()); + QCOMPARE(VirtualDesktopManager::self()->current(), 3u); + + // now create a window on this desktop + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + + QVERIFY(window); + QCOMPARE(window->desktop(), 3u); + QSignalSpy desktopPresenceChangedSpy(window, &Window::desktopPresenceChanged); + + QCOMPARE(window->desktops().count(), 1u); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), window->desktops().first()); + + // Set the window on desktop 2 as well + window->enterDesktop(VirtualDesktopManager::self()->desktops()[1]); + QCOMPARE(window->desktops().count(), 2u); + QCOMPARE(VirtualDesktopManager::self()->desktops()[2], window->desktops()[0]); + QCOMPARE(VirtualDesktopManager::self()->desktops()[1], window->desktops()[1]); + QVERIFY(window->isOnDesktop(2)); + QVERIFY(window->isOnDesktop(3)); + + // remove desktop 3 + VirtualDesktopManager::self()->setCount(2); + QCOMPARE(window->desktops().count(), 1u); + // window is only on desktop 2 + QCOMPARE(VirtualDesktopManager::self()->desktops()[1], window->desktops()[0]); + + // Again 3 desktops + VirtualDesktopManager::self()->setCount(3); + // move window to be only on desktop 3 + window->enterDesktop(VirtualDesktopManager::self()->desktops()[2]); + window->leaveDesktop(VirtualDesktopManager::self()->desktops()[1]); + QCOMPARE(window->desktops().count(), 1u); + // window is only on desktop 3 + QCOMPARE(VirtualDesktopManager::self()->desktops()[2], window->desktops()[0]); + + // remove desktop 3 + VirtualDesktopManager::self()->setCount(2); + QCOMPARE(window->desktops().count(), 1u); + // window is only on desktop 2 + QCOMPARE(VirtualDesktopManager::self()->desktops()[1], window->desktops()[0]); +} + +WAYLANDTEST_MAIN(VirtualDesktopTest) +#include "virtual_desktop_test.moc" diff --git a/autotests/integration/window_rules_test.cpp b/autotests/integration/window_rules_test.cpp new file mode 100644 index 0000000..d4406c0 --- /dev/null +++ b/autotests/integration/window_rules_test.cpp @@ -0,0 +1,220 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "atoms.h" +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "deleted.h" +#include "rules.h" +#include "wayland_server.h" +#include "workspace.h" +#include "x11window.h" + +#include +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_window_rules-0"); + +class WindowRuleTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testApplyInitialMaximizeVert_data(); + void testApplyInitialMaximizeVert(); + void testWindowClassChange(); +}; + +void WindowRuleTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); + setenv("QT_QPA_PLATFORM", "wayland", true); +} + +void WindowRuleTest::init() +{ + workspace()->setActiveOutput(QPoint(640, 512)); + Cursors::self()->mouse()->setPos(QPoint(640, 512)); + QVERIFY(waylandServer()->windows().isEmpty()); +} + +void WindowRuleTest::cleanup() +{ + // discards old rules + workspace()->rulebook()->load(); +} + +struct XcbConnectionDeleter +{ + void operator()(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } +}; + +void WindowRuleTest::testApplyInitialMaximizeVert_data() +{ + QTest::addColumn("role"); + + QTest::newRow("lowercase") << QByteArrayLiteral("mainwindow"); + QTest::newRow("CamelCase") << QByteArrayLiteral("MainWindow"); +} + +void WindowRuleTest::testApplyInitialMaximizeVert() +{ + // this test creates the situation of BUG 367554: creates a window and initial apply maximize vertical + // the window is matched by class and role + // load the rule + QFile ruleFile(QFINDTESTDATA("./data/rules/maximize-vert-apply-initial")); + QVERIFY(ruleFile.open(QIODevice::ReadOnly | QIODevice::Text)); + QMetaObject::invokeMethod(workspace()->rulebook(), "temporaryRulesMessage", Q_ARG(QString, QString::fromUtf8(ruleFile.readAll()))); + + // create the test window + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + + xcb_window_t windowId = xcb_generate_id(c.get()); + const QRect windowGeometry = QRect(0, 0, 10, 20); + const uint32_t values[] = { + XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW}; + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + xcb_icccm_set_wm_class(c.get(), windowId, 9, "kpat\0kpat"); + + QFETCH(QByteArray, role); + xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_window_role, XCB_ATOM_STRING, 8, role.length(), role.constData()); + + NETWinInfo info(c.get(), windowId, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); + info.setWindowType(NET::Normal); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.last().first().value(); + QVERIFY(window); + QVERIFY(window->isDecorated()); + QVERIFY(!window->hasStrut()); + QVERIFY(!window->isHiddenInternal()); + QVERIFY(!window->readyForPainting()); + QMetaObject::invokeMethod(window, "setReadyForPainting"); + QVERIFY(window->readyForPainting()); + QVERIFY(Test::waitForWaylandSurface(window)); + QCOMPARE(window->maximizeMode(), MaximizeVertical); + + // destroy window again + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + xcb_unmap_window(c.get(), windowId); + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + QVERIFY(windowClosedSpy.wait()); +} + +void WindowRuleTest::testWindowClassChange() +{ + KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + config->group("General").writeEntry("count", 1); + + auto group = config->group("1"); + group.writeEntry("above", true); + group.writeEntry("aboverule", 2); + group.writeEntry("wmclass", "org.kde.foo"); + group.writeEntry("wmclasscomplete", false); + group.writeEntry("wmclassmatch", 1); + group.sync(); + + workspace()->rulebook()->setConfig(config); + workspace()->slotReconfigure(); + + // create the test window + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + + xcb_window_t windowId = xcb_generate_id(c.get()); + const QRect windowGeometry = QRect(0, 0, 10, 20); + const uint32_t values[] = { + XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW}; + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + xcb_icccm_set_wm_class(c.get(), windowId, 23, "org.kde.bar\0org.kde.bar"); + + NETWinInfo info(c.get(), windowId, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); + info.setWindowType(NET::Normal); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.last().first().value(); + QVERIFY(window); + QVERIFY(window->isDecorated()); + QVERIFY(!window->hasStrut()); + QVERIFY(!window->isHiddenInternal()); + QVERIFY(!window->readyForPainting()); + QMetaObject::invokeMethod(window, "setReadyForPainting"); + QVERIFY(window->readyForPainting()); + QVERIFY(Test::waitForWaylandSurface(window)); + QCOMPARE(window->keepAbove(), false); + + // now change class + QSignalSpy windowClassChangedSpy{window, &X11Window::windowClassChanged}; + xcb_icccm_set_wm_class(c.get(), windowId, 23, "org.kde.foo\0org.kde.foo"); + xcb_flush(c.get()); + QVERIFY(windowClassChangedSpy.wait()); + QCOMPARE(window->keepAbove(), true); + + // destroy window + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + xcb_unmap_window(c.get(), windowId); + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + QVERIFY(windowClosedSpy.wait()); +} + +} + +WAYLANDTEST_MAIN(KWin::WindowRuleTest) +#include "window_rules_test.moc" diff --git a/autotests/integration/window_selection_test.cpp b/autotests/integration/window_selection_test.cpp new file mode 100644 index 0000000..baf9f6d --- /dev/null +++ b/autotests/integration/window_selection_test.cpp @@ -0,0 +1,524 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "keyboard_input.h" +#include "pointer_input.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace KWin; +using namespace KWayland::Client; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_window_selection-0"); + +class TestWindowSelection : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testSelectOnWindowPointer(); + void testSelectOnWindowKeyboard_data(); + void testSelectOnWindowKeyboard(); + void testSelectOnWindowTouch(); + void testCancelOnWindowPointer(); + void testCancelOnWindowKeyboard(); + + void testSelectPointPointer(); + void testSelectPointTouch(); +}; + +void TestWindowSelection::initTestCase() +{ + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + qputenv("XKB_DEFAULT_RULES", "evdev"); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); +} + +void TestWindowSelection::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat)); + QVERIFY(Test::waitForWaylandPointer()); + + workspace()->setActiveOutput(QPoint(640, 512)); + KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void TestWindowSelection::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void TestWindowSelection::testSelectOnWindowPointer() +{ + // this test verifies window selection through pointer works + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + std::unique_ptr pointer(Test::waylandSeat()->createPointer()); + std::unique_ptr keyboard(Test::waylandSeat()->createKeyboard()); + QSignalSpy pointerEnteredSpy(pointer.get(), &Pointer::entered); + QSignalSpy pointerLeftSpy(pointer.get(), &Pointer::left); + QSignalSpy keyboardEnteredSpy(keyboard.get(), &Keyboard::entered); + QSignalSpy keyboardLeftSpy(keyboard.get(), &Keyboard::left); + + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(keyboardEnteredSpy.wait()); + KWin::Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + QCOMPARE(input()->pointer()->focus(), window); + QVERIFY(pointerEnteredSpy.wait()); + + Window *selectedWindow = nullptr; + auto callback = [&selectedWindow](Window *t) { + selectedWindow = t; + }; + + // start the interaction + QCOMPARE(input()->isSelectingWindow(), false); + kwinApp()->platform()->startInteractiveWindowSelection(callback); + QCOMPARE(input()->isSelectingWindow(), true); + QVERIFY(!selectedWindow); + QCOMPARE(keyboardLeftSpy.count(), 0); + QVERIFY(pointerLeftSpy.wait()); + if (keyboardLeftSpy.isEmpty()) { + QVERIFY(keyboardLeftSpy.wait()); + } + QCOMPARE(pointerLeftSpy.count(), 1); + QCOMPARE(keyboardLeftSpy.count(), 1); + + // simulate left button press + quint32 timestamp = 0; + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + // should not have ended the mode + QCOMPARE(input()->isSelectingWindow(), true); + QVERIFY(!selectedWindow); + QVERIFY(!input()->pointer()->focus()); + + // updating the pointer should not change anything + input()->pointer()->update(); + QVERIFY(!input()->pointer()->focus()); + // updating keyboard should also not change + input()->keyboard()->update(); + + // perform a right button click + Test::pointerButtonPressed(BTN_RIGHT, timestamp++); + Test::pointerButtonReleased(BTN_RIGHT, timestamp++); + // should not have ended the mode + QCOMPARE(input()->isSelectingWindow(), true); + QVERIFY(!selectedWindow); + // now release + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + QCOMPARE(input()->isSelectingWindow(), false); + QCOMPARE(selectedWindow, window); + QCOMPARE(input()->pointer()->focus(), window); + // should give back keyboard and pointer + QVERIFY(pointerEnteredSpy.wait()); + if (keyboardEnteredSpy.count() != 2) { + QVERIFY(keyboardEnteredSpy.wait()); + } + QCOMPARE(pointerLeftSpy.count(), 1); + QCOMPARE(keyboardLeftSpy.count(), 1); + QCOMPARE(pointerEnteredSpy.count(), 2); + QCOMPARE(keyboardEnteredSpy.count(), 2); +} + +void TestWindowSelection::testSelectOnWindowKeyboard_data() +{ + QTest::addColumn("key"); + + QTest::newRow("enter") << KEY_ENTER; + QTest::newRow("keypad enter") << KEY_KPENTER; + QTest::newRow("space") << KEY_SPACE; +} + +void TestWindowSelection::testSelectOnWindowKeyboard() +{ + // this test verifies window selection through keyboard key + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + std::unique_ptr pointer(Test::waylandSeat()->createPointer()); + std::unique_ptr keyboard(Test::waylandSeat()->createKeyboard()); + QSignalSpy pointerEnteredSpy(pointer.get(), &Pointer::entered); + QSignalSpy pointerLeftSpy(pointer.get(), &Pointer::left); + QSignalSpy keyboardEnteredSpy(keyboard.get(), &Keyboard::entered); + QSignalSpy keyboardLeftSpy(keyboard.get(), &Keyboard::left); + + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(keyboardEnteredSpy.wait()); + QVERIFY(!window->frameGeometry().contains(KWin::Cursors::self()->mouse()->pos())); + + Window *selectedWindow = nullptr; + auto callback = [&selectedWindow](Window *t) { + selectedWindow = t; + }; + + // start the interaction + QCOMPARE(input()->isSelectingWindow(), false); + kwinApp()->platform()->startInteractiveWindowSelection(callback); + QCOMPARE(input()->isSelectingWindow(), true); + QVERIFY(!selectedWindow); + QCOMPARE(keyboardLeftSpy.count(), 0); + QVERIFY(keyboardLeftSpy.wait()); + QCOMPARE(pointerLeftSpy.count(), 0); + QCOMPARE(keyboardLeftSpy.count(), 1); + + // simulate key press + quint32 timestamp = 0; + // move cursor through keys + auto keyPress = [×tamp](qint32 key) { + Test::keyboardKeyPressed(key, timestamp++); + Test::keyboardKeyReleased(key, timestamp++); + }; + while (KWin::Cursors::self()->mouse()->pos().x() >= window->frameGeometry().x() + window->frameGeometry().width()) { + keyPress(KEY_LEFT); + } + while (KWin::Cursors::self()->mouse()->pos().x() <= window->frameGeometry().x()) { + keyPress(KEY_RIGHT); + } + while (KWin::Cursors::self()->mouse()->pos().y() <= window->frameGeometry().y()) { + keyPress(KEY_DOWN); + } + while (KWin::Cursors::self()->mouse()->pos().y() >= window->frameGeometry().y() + window->frameGeometry().height()) { + keyPress(KEY_UP); + } + QFETCH(qint32, key); + Test::keyboardKeyPressed(key, timestamp++); + QCOMPARE(input()->isSelectingWindow(), false); + QCOMPARE(selectedWindow, window); + QCOMPARE(input()->pointer()->focus(), window); + // should give back keyboard and pointer + QVERIFY(pointerEnteredSpy.wait()); + if (keyboardEnteredSpy.count() != 2) { + QVERIFY(keyboardEnteredSpy.wait()); + } + QCOMPARE(pointerLeftSpy.count(), 0); + QCOMPARE(keyboardLeftSpy.count(), 1); + QCOMPARE(pointerEnteredSpy.count(), 1); + QCOMPARE(keyboardEnteredSpy.count(), 2); + Test::keyboardKeyReleased(key, timestamp++); +} + +void TestWindowSelection::testSelectOnWindowTouch() +{ + // this test verifies window selection through touch + std::unique_ptr touch(Test::waylandSeat()->createTouch()); + QSignalSpy touchStartedSpy(touch.get(), &Touch::sequenceStarted); + QSignalSpy touchCanceledSpy(touch.get(), &Touch::sequenceCanceled); + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + Window *selectedWindow = nullptr; + auto callback = [&selectedWindow](Window *t) { + selectedWindow = t; + }; + + // start the interaction + QCOMPARE(input()->isSelectingWindow(), false); + kwinApp()->platform()->startInteractiveWindowSelection(callback); + QCOMPARE(input()->isSelectingWindow(), true); + QVERIFY(!selectedWindow); + + // simulate touch down + quint32 timestamp = 0; + Test::touchDown(0, window->frameGeometry().center(), timestamp++); + QVERIFY(!selectedWindow); + Test::touchUp(0, timestamp++); + QCOMPARE(input()->isSelectingWindow(), false); + QCOMPARE(selectedWindow, window); + + // with movement + selectedWindow = nullptr; + kwinApp()->platform()->startInteractiveWindowSelection(callback); + Test::touchDown(0, window->frameGeometry().bottomRight() + QPoint(20, 20), timestamp++); + QVERIFY(!selectedWindow); + Test::touchMotion(0, window->frameGeometry().bottomRight() - QPoint(1, 1), timestamp++); + QVERIFY(!selectedWindow); + Test::touchUp(0, timestamp++); + QCOMPARE(selectedWindow, window); + QCOMPARE(input()->isSelectingWindow(), false); + + // it cancels active touch sequence on the window + Test::touchDown(0, window->frameGeometry().center(), timestamp++); + QVERIFY(touchStartedSpy.wait()); + selectedWindow = nullptr; + kwinApp()->platform()->startInteractiveWindowSelection(callback); + QCOMPARE(input()->isSelectingWindow(), true); + QVERIFY(touchCanceledSpy.wait()); + QVERIFY(!selectedWindow); + // this touch up does not yet select the window, it was started prior to the selection + Test::touchUp(0, timestamp++); + QVERIFY(!selectedWindow); + Test::touchDown(0, window->frameGeometry().center(), timestamp++); + Test::touchUp(0, timestamp++); + QCOMPARE(selectedWindow, window); + QCOMPARE(input()->isSelectingWindow(), false); + + QCOMPARE(touchStartedSpy.count(), 1); + QCOMPARE(touchCanceledSpy.count(), 1); +} + +void TestWindowSelection::testCancelOnWindowPointer() +{ + // this test verifies that window selection cancels through right button click + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + std::unique_ptr pointer(Test::waylandSeat()->createPointer()); + std::unique_ptr keyboard(Test::waylandSeat()->createKeyboard()); + QSignalSpy pointerEnteredSpy(pointer.get(), &Pointer::entered); + QSignalSpy pointerLeftSpy(pointer.get(), &Pointer::left); + QSignalSpy keyboardEnteredSpy(keyboard.get(), &Keyboard::entered); + QSignalSpy keyboardLeftSpy(keyboard.get(), &Keyboard::left); + + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(keyboardEnteredSpy.wait()); + KWin::Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + QCOMPARE(input()->pointer()->focus(), window); + QVERIFY(pointerEnteredSpy.wait()); + + Window *selectedWindow = nullptr; + auto callback = [&selectedWindow](Window *t) { + selectedWindow = t; + }; + + // start the interaction + QCOMPARE(input()->isSelectingWindow(), false); + kwinApp()->platform()->startInteractiveWindowSelection(callback); + QCOMPARE(input()->isSelectingWindow(), true); + QVERIFY(!selectedWindow); + QCOMPARE(keyboardLeftSpy.count(), 0); + QVERIFY(pointerLeftSpy.wait()); + if (keyboardLeftSpy.isEmpty()) { + QVERIFY(keyboardLeftSpy.wait()); + } + QCOMPARE(pointerLeftSpy.count(), 1); + QCOMPARE(keyboardLeftSpy.count(), 1); + + // simulate left button press + quint32 timestamp = 0; + Test::pointerButtonPressed(BTN_RIGHT, timestamp++); + Test::pointerButtonReleased(BTN_RIGHT, timestamp++); + QCOMPARE(input()->isSelectingWindow(), false); + QVERIFY(!selectedWindow); + QCOMPARE(input()->pointer()->focus(), window); + // should give back keyboard and pointer + QVERIFY(pointerEnteredSpy.wait()); + if (keyboardEnteredSpy.count() != 2) { + QVERIFY(keyboardEnteredSpy.wait()); + } + QCOMPARE(pointerLeftSpy.count(), 1); + QCOMPARE(keyboardLeftSpy.count(), 1); + QCOMPARE(pointerEnteredSpy.count(), 2); + QCOMPARE(keyboardEnteredSpy.count(), 2); +} + +void TestWindowSelection::testCancelOnWindowKeyboard() +{ + // this test verifies that cancel window selection through escape key works + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + std::unique_ptr pointer(Test::waylandSeat()->createPointer()); + std::unique_ptr keyboard(Test::waylandSeat()->createKeyboard()); + QSignalSpy pointerEnteredSpy(pointer.get(), &Pointer::entered); + QSignalSpy pointerLeftSpy(pointer.get(), &Pointer::left); + QSignalSpy keyboardEnteredSpy(keyboard.get(), &Keyboard::entered); + QSignalSpy keyboardLeftSpy(keyboard.get(), &Keyboard::left); + + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(keyboardEnteredSpy.wait()); + KWin::Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + QCOMPARE(input()->pointer()->focus(), window); + QVERIFY(pointerEnteredSpy.wait()); + + Window *selectedWindow = nullptr; + auto callback = [&selectedWindow](Window *t) { + selectedWindow = t; + }; + + // start the interaction + QCOMPARE(input()->isSelectingWindow(), false); + kwinApp()->platform()->startInteractiveWindowSelection(callback); + QCOMPARE(input()->isSelectingWindow(), true); + QVERIFY(!selectedWindow); + QCOMPARE(keyboardLeftSpy.count(), 0); + QVERIFY(pointerLeftSpy.wait()); + if (keyboardLeftSpy.isEmpty()) { + QVERIFY(keyboardLeftSpy.wait()); + } + QCOMPARE(pointerLeftSpy.count(), 1); + QCOMPARE(keyboardLeftSpy.count(), 1); + + // simulate left button press + quint32 timestamp = 0; + Test::keyboardKeyPressed(KEY_ESC, timestamp++); + QCOMPARE(input()->isSelectingWindow(), false); + QVERIFY(!selectedWindow); + QCOMPARE(input()->pointer()->focus(), window); + // should give back keyboard and pointer + QVERIFY(pointerEnteredSpy.wait()); + if (keyboardEnteredSpy.count() != 2) { + QVERIFY(keyboardEnteredSpy.wait()); + } + QCOMPARE(pointerLeftSpy.count(), 1); + QCOMPARE(keyboardLeftSpy.count(), 1); + QCOMPARE(pointerEnteredSpy.count(), 2); + QCOMPARE(keyboardEnteredSpy.count(), 2); + Test::keyboardKeyReleased(KEY_ESC, timestamp++); +} + +void TestWindowSelection::testSelectPointPointer() +{ + // this test verifies point selection through pointer works + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + std::unique_ptr pointer(Test::waylandSeat()->createPointer()); + std::unique_ptr keyboard(Test::waylandSeat()->createKeyboard()); + QSignalSpy pointerEnteredSpy(pointer.get(), &Pointer::entered); + QSignalSpy pointerLeftSpy(pointer.get(), &Pointer::left); + QSignalSpy keyboardEnteredSpy(keyboard.get(), &Keyboard::entered); + QSignalSpy keyboardLeftSpy(keyboard.get(), &Keyboard::left); + + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(keyboardEnteredSpy.wait()); + KWin::Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + QCOMPARE(input()->pointer()->focus(), window); + QVERIFY(pointerEnteredSpy.wait()); + + QPoint point; + auto callback = [&point](const QPoint &p) { + point = p; + }; + + // start the interaction + QCOMPARE(input()->isSelectingWindow(), false); + kwinApp()->platform()->startInteractivePositionSelection(callback); + QCOMPARE(input()->isSelectingWindow(), true); + QCOMPARE(point, QPoint()); + QCOMPARE(keyboardLeftSpy.count(), 0); + QVERIFY(pointerLeftSpy.wait()); + if (keyboardLeftSpy.isEmpty()) { + QVERIFY(keyboardLeftSpy.wait()); + } + QCOMPARE(pointerLeftSpy.count(), 1); + QCOMPARE(keyboardLeftSpy.count(), 1); + + // trying again should not be allowed + QPoint point2; + kwinApp()->platform()->startInteractivePositionSelection([&point2](const QPoint &p) { + point2 = p; + }); + QCOMPARE(point2, QPoint(-1, -1)); + + // simulate left button press + quint32 timestamp = 0; + Test::pointerButtonPressed(BTN_LEFT, timestamp++); + // should not have ended the mode + QCOMPARE(input()->isSelectingWindow(), true); + QCOMPARE(point, QPoint()); + QVERIFY(!input()->pointer()->focus()); + + // updating the pointer should not change anything + input()->pointer()->update(); + QVERIFY(!input()->pointer()->focus()); + // updating keyboard should also not change + input()->keyboard()->update(); + + // perform a right button click + Test::pointerButtonPressed(BTN_RIGHT, timestamp++); + Test::pointerButtonReleased(BTN_RIGHT, timestamp++); + // should not have ended the mode + QCOMPARE(input()->isSelectingWindow(), true); + QCOMPARE(point, QPoint()); + // now release + Test::pointerButtonReleased(BTN_LEFT, timestamp++); + QCOMPARE(input()->isSelectingWindow(), false); + QCOMPARE(point, input()->globalPointer().toPoint()); + QCOMPARE(input()->pointer()->focus(), window); + // should give back keyboard and pointer + QVERIFY(pointerEnteredSpy.wait()); + if (keyboardEnteredSpy.count() != 2) { + QVERIFY(keyboardEnteredSpy.wait()); + } + QCOMPARE(pointerLeftSpy.count(), 1); + QCOMPARE(keyboardLeftSpy.count(), 1); + QCOMPARE(pointerEnteredSpy.count(), 2); + QCOMPARE(keyboardEnteredSpy.count(), 2); +} + +void TestWindowSelection::testSelectPointTouch() +{ + // this test verifies point selection through touch works + QPoint point; + auto callback = [&point](const QPoint &p) { + point = p; + }; + + // start the interaction + QCOMPARE(input()->isSelectingWindow(), false); + kwinApp()->platform()->startInteractivePositionSelection(callback); + QCOMPARE(input()->isSelectingWindow(), true); + QCOMPARE(point, QPoint()); + + // let's create multiple touch points + quint32 timestamp = 0; + Test::touchDown(0, QPointF(0, 1), timestamp++); + QCOMPARE(input()->isSelectingWindow(), true); + Test::touchDown(1, QPointF(10, 20), timestamp++); + QCOMPARE(input()->isSelectingWindow(), true); + Test::touchDown(2, QPointF(30, 40), timestamp++); + QCOMPARE(input()->isSelectingWindow(), true); + + // let's move our points + Test::touchMotion(0, QPointF(5, 10), timestamp++); + Test::touchMotion(2, QPointF(20, 25), timestamp++); + Test::touchMotion(1, QPointF(25, 35), timestamp++); + QCOMPARE(input()->isSelectingWindow(), true); + Test::touchUp(0, timestamp++); + QCOMPARE(input()->isSelectingWindow(), true); + Test::touchUp(2, timestamp++); + QCOMPARE(input()->isSelectingWindow(), true); + Test::touchUp(1, timestamp++); + QCOMPARE(input()->isSelectingWindow(), false); + QCOMPARE(point, QPoint(25, 35)); +} + +WAYLANDTEST_MAIN(TestWindowSelection) +#include "window_selection_test.moc" diff --git a/autotests/integration/x11_window_test.cpp b/autotests/integration/x11_window_test.cpp new file mode 100644 index 0000000..39be6f5 --- /dev/null +++ b/autotests/integration/x11_window_test.cpp @@ -0,0 +1,1143 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "atoms.h" +#include "composite.h" +#include "core/platform.h" +#include "cursor.h" +#include "deleted.h" +#include "effectloader.h" +#include "effects.h" +#include "wayland_server.h" +#include "workspace.h" +#include "x11window.h" + +#include + +#include +#include + +using namespace KWin; +using namespace KWayland::Client; +static const QString s_socketName = QStringLiteral("wayland_test_x11_window-0"); + +class X11WindowTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase_data(); + void initTestCase(); + void init(); + void cleanup(); + + void testMinimumSize(); + void testMaximumSize(); + void testResizeIncrements(); + void testResizeIncrementsNoBaseSize(); + void testTrimCaption_data(); + void testTrimCaption(); + void testFullscreenLayerWithActiveWaylandWindow(); + void testFocusInWithWaylandLastActiveWindow(); + void testX11WindowId(); + void testCaptionChanges(); + void testCaptionWmName(); + void testCaptionMultipleWindows(); + void testFullscreenWindowGroups(); + void testActivateFocusedWindow(); + void testReentrantMoveResize(); +}; + +void X11WindowTest::initTestCase_data() +{ + QTest::addColumn("scale"); + QTest::newRow("normal") << 1.0; + QTest::newRow("scaled2x") << 2.0; +} + +void X11WindowTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + QVERIFY(KWin::Compositor::self()); +} + +void X11WindowTest::init() +{ + QVERIFY(Test::setupWaylandConnection()); +} + +void X11WindowTest::cleanup() +{ + Test::destroyWaylandConnection(); +} + +struct XcbConnectionDeleter +{ + void operator()(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } +}; + +void X11WindowTest::testMinimumSize() +{ + // This test verifies that the minimum size constraint is correctly applied. + + QFETCH_GLOBAL(qreal, scale); + kwinApp()->setXwaylandScale(scale); + + // Create an xcb window. + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_size_hints_set_min_size(&hints, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.last().first().value(); + QVERIFY(window); + QVERIFY(window->isDecorated()); + + QSignalSpy clientStartMoveResizedSpy(window, &Window::clientStartUserMovedResized); + QSignalSpy clientStepUserMovedResizedSpy(window, &Window::clientStepUserMovedResized); + QSignalSpy clientFinishUserMovedResizedSpy(window, &Window::clientFinishUserMovedResized); + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + + // Begin resize. + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(!window->isInteractiveResize()); + workspace()->slotWindowResize(); + QCOMPARE(workspace()->moveResizeWindow(), window); + QCOMPARE(clientStartMoveResizedSpy.count(), 1); + QVERIFY(window->isInteractiveResize()); + + const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); + + window->keyPressEvent(Qt::Key_Left); + window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 0)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 0); + QVERIFY(!frameGeometryChangedSpy.wait(1000)); + QCOMPARE(window->clientSize().width(), 100 / scale); + + window->keyPressEvent(Qt::Key_Right); + window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 0); + QVERIFY(!frameGeometryChangedSpy.wait(1000)); + QCOMPARE(window->clientSize().width(), 100 / scale); + + window->keyPressEvent(Qt::Key_Right); + window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + QVERIFY(frameGeometryChangedSpy.wait()); + // whilst X11 window size goes through scale, the increment is a logical value kwin side + QCOMPARE(window->clientSize().width(), 100 / scale + 8); + + window->keyPressEvent(Qt::Key_Up); + window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, -8)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + QVERIFY(!frameGeometryChangedSpy.wait(1000)); + QCOMPARE(window->clientSize().height(), 200 / scale); + + window->keyPressEvent(Qt::Key_Down); + window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + QVERIFY(!frameGeometryChangedSpy.wait(1000)); + QCOMPARE(window->clientSize().height(), 200 / scale); + + window->keyPressEvent(Qt::Key_Down); + window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 8)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 2); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->clientSize().height(), 200 / scale + 8); + + // Finish the resize operation. + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0); + window->keyPressEvent(Qt::Key_Enter); + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(!window->isInteractiveResize()); + + // Destroy the window. + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + xcb_unmap_window(c.get(), windowId); + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + QVERIFY(windowClosedSpy.wait()); + c.reset(); +} + +void X11WindowTest::testMaximumSize() +{ + // This test verifies that the maximum size constraint is correctly applied. + QFETCH_GLOBAL(qreal, scale); + kwinApp()->setXwaylandScale(scale); + + // Create an xcb window. + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_size_hints_set_max_size(&hints, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.last().first().value(); + QVERIFY(window); + QVERIFY(window->isDecorated()); + + QSignalSpy clientStartMoveResizedSpy(window, &Window::clientStartUserMovedResized); + QSignalSpy clientStepUserMovedResizedSpy(window, &Window::clientStepUserMovedResized); + QSignalSpy clientFinishUserMovedResizedSpy(window, &Window::clientFinishUserMovedResized); + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + + // Begin resize. + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(!window->isInteractiveResize()); + workspace()->slotWindowResize(); + QCOMPARE(workspace()->moveResizeWindow(), window); + QCOMPARE(clientStartMoveResizedSpy.count(), 1); + QVERIFY(window->isInteractiveResize()); + + const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); + + window->keyPressEvent(Qt::Key_Right); + window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 0); + QVERIFY(!frameGeometryChangedSpy.wait(1000)); + QCOMPARE(window->clientSize().width(), 100 / scale); + + window->keyPressEvent(Qt::Key_Left); + window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos); + QVERIFY(!clientStepUserMovedResizedSpy.wait(1000)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 0); + QCOMPARE(window->clientSize().width(), 100 / scale); + + window->keyPressEvent(Qt::Key_Left); + window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 0)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->clientSize().width(), 100 / scale - 8); + + window->keyPressEvent(Qt::Key_Down); + window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 8)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + QVERIFY(!frameGeometryChangedSpy.wait(1000)); + QCOMPARE(window->clientSize().height(), 200 / scale); + + window->keyPressEvent(Qt::Key_Up); + window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 0)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + QVERIFY(!frameGeometryChangedSpy.wait(1000)); + QCOMPARE(window->clientSize().height(), 200 / scale); + + window->keyPressEvent(Qt::Key_Up); + window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, -8)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 2); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->clientSize().height(), 200 / scale - 8); + + // Finish the resize operation. + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0); + window->keyPressEvent(Qt::Key_Enter); + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(!window->isInteractiveResize()); + + // Destroy the window. + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + xcb_unmap_window(c.get(), windowId); + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + QVERIFY(windowClosedSpy.wait()); + c.reset(); +} + +void X11WindowTest::testResizeIncrements() +{ + // This test verifies that the resize increments constraint is correctly applied. + QFETCH_GLOBAL(qreal, scale); + kwinApp()->setXwaylandScale(scale); + + // Create an xcb window. + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_size_hints_set_base_size(&hints, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_size_hints_set_resize_inc(&hints, 3, 5); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.last().first().value(); + QVERIFY(window); + QVERIFY(window->isDecorated()); + + QSignalSpy clientStartMoveResizedSpy(window, &Window::clientStartUserMovedResized); + QSignalSpy clientStepUserMovedResizedSpy(window, &Window::clientStepUserMovedResized); + QSignalSpy clientFinishUserMovedResizedSpy(window, &Window::clientFinishUserMovedResized); + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + + // Begin resize. + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(!window->isInteractiveResize()); + workspace()->slotWindowResize(); + QCOMPARE(workspace()->moveResizeWindow(), window); + QCOMPARE(clientStartMoveResizedSpy.count(), 1); + QVERIFY(window->isInteractiveResize()); + + const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); + + window->keyPressEvent(Qt::Key_Right); + window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + QVERIFY(frameGeometryChangedSpy.wait()); + + // 100 + 8 logical pixels, rounded to resize increments. This will differ on scale + const qreal horizontalResizeInc = 3 / scale; + const qreal verticalResizeInc = 5 / scale; + const qreal expectedHorizontalResizeInc = std::floor(8. / horizontalResizeInc) * horizontalResizeInc; + const qreal expectedVerticalResizeInc = std::floor(8. / verticalResizeInc) * verticalResizeInc; + + QCOMPARE(window->clientSize(), QSizeF(100, 200) / scale + QSizeF(expectedHorizontalResizeInc, 0)); + + window->keyPressEvent(Qt::Key_Down); + window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 8)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 2); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->clientSize(), QSize(100, 200) / scale + QSizeF(expectedHorizontalResizeInc, expectedVerticalResizeInc)); + + // Finish the resize operation. + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0); + window->keyPressEvent(Qt::Key_Enter); + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(!window->isInteractiveResize()); + + // Destroy the window. + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + xcb_unmap_window(c.get(), windowId); + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + QVERIFY(windowClosedSpy.wait()); + c.reset(); +} + +void X11WindowTest::testResizeIncrementsNoBaseSize() +{ + QFETCH_GLOBAL(qreal, scale); + kwinApp()->setXwaylandScale(scale); + + // Create an xcb window. + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_size_hints_set_min_size(&hints, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_size_hints_set_resize_inc(&hints, 3, 5); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.last().first().value(); + QVERIFY(window); + QVERIFY(window->isDecorated()); + + QSignalSpy clientStartMoveResizedSpy(window, &Window::clientStartUserMovedResized); + QSignalSpy clientStepUserMovedResizedSpy(window, &Window::clientStepUserMovedResized); + QSignalSpy clientFinishUserMovedResizedSpy(window, &Window::clientFinishUserMovedResized); + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + + // Begin resize. + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(!window->isInteractiveResize()); + workspace()->slotWindowResize(); + QCOMPARE(workspace()->moveResizeWindow(), window); + QCOMPARE(clientStartMoveResizedSpy.count(), 1); + QVERIFY(window->isInteractiveResize()); + + const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); + + window->keyPressEvent(Qt::Key_Right); + window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + QVERIFY(frameGeometryChangedSpy.wait()); + + // 100 + 8 pixels, rounded to resize increments. This will differ on scale + const qreal horizontalResizeInc = 3 / scale; + const qreal verticalResizeInc = 5 / scale; + const qreal expectedHorizontalResizeInc = std::floor(8. / horizontalResizeInc) * horizontalResizeInc; + const qreal expectedVerticalResizeInc = std::floor(8. / verticalResizeInc) * verticalResizeInc; + + QCOMPARE(window->clientSize(), QSizeF(100, 200) / scale + QSizeF(expectedHorizontalResizeInc, 0)); + + window->keyPressEvent(Qt::Key_Down); + window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 8)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 2); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->clientSize(), QSizeF(100, 200) / scale + QSizeF(expectedHorizontalResizeInc, expectedVerticalResizeInc)); + + // Finish the resize operation. + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0); + window->keyPressEvent(Qt::Key_Enter); + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(!window->isInteractiveResize()); + + // Destroy the window. + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + xcb_unmap_window(c.get(), windowId); + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + QVERIFY(windowClosedSpy.wait()); + c.reset(); +} + +void X11WindowTest::testTrimCaption_data() +{ + QFETCH_GLOBAL(qreal, scale); + kwinApp()->setXwaylandScale(scale); + + QTest::addColumn("originalTitle"); + QTest::addColumn("expectedTitle"); + + QTest::newRow("simplified") + << QByteArrayLiteral("Was tun, wenn Schüler Autismus haben?\342\200\250\342\200\250\342\200\250 – Marlies Hübner - Mozilla Firefox") + << QByteArrayLiteral("Was tun, wenn Schüler Autismus haben? – Marlies Hübner - Mozilla Firefox"); + + QTest::newRow("with emojis") + << QByteArrayLiteral("\bTesting non\302\255printable:\177, emoij:\360\237\230\203, non-characters:\357\277\276") + << QByteArrayLiteral("Testing nonprintable:, emoij:\360\237\230\203, non-characters:"); +} + +void X11WindowTest::testTrimCaption() +{ + QFETCH_GLOBAL(qreal, scale); + kwinApp()->setXwaylandScale(scale); + + // this test verifies that caption is properly trimmed + + // create an xcb window + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + NETWinInfo winInfo(c.get(), windowId, rootWindow(), NET::Properties(), NET::Properties2()); + QFETCH(QByteArray, originalTitle); + winInfo.setName(originalTitle); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QFETCH(QByteArray, expectedTitle); + QCOMPARE(window->caption(), QString::fromUtf8(expectedTitle)); + + // and destroy the window again + xcb_unmap_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + QVERIFY(windowClosedSpy.wait()); + xcb_destroy_window(c.get(), windowId); + c.reset(); +} + +void X11WindowTest::testFullscreenLayerWithActiveWaylandWindow() +{ + QFETCH_GLOBAL(qreal, scale); + kwinApp()->setXwaylandScale(scale); + + // this test verifies that an X11 fullscreen window does not stay in the active layer + // when a Wayland window is active, see BUG: 375759 + QCOMPARE(workspace()->outputs().count(), 1); + + // first create an X11 window + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QVERIFY(!window->isFullScreen()); + QVERIFY(window->isActive()); + QCOMPARE(window->layer(), NormalLayer); + + workspace()->slotWindowFullScreen(); + QVERIFY(window->isFullScreen()); + QCOMPARE(window->layer(), ActiveLayer); + QCOMPARE(workspace()->stackingOrder().last(), window); + + // now let's open a Wayland window + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto waylandWindow = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(waylandWindow); + QVERIFY(waylandWindow->isActive()); + QCOMPARE(waylandWindow->layer(), NormalLayer); + QCOMPARE(workspace()->stackingOrder().last(), waylandWindow); + QCOMPARE(workspace()->stackingOrder().last(), waylandWindow); + QCOMPARE(window->layer(), NormalLayer); + + // now activate fullscreen again + workspace()->activateWindow(window); + QTRY_VERIFY(window->isActive()); + QCOMPARE(window->layer(), ActiveLayer); + QCOMPARE(workspace()->stackingOrder().last(), window); + QCOMPARE(workspace()->stackingOrder().last(), window); + + // activate wayland window again + workspace()->activateWindow(waylandWindow); + QTRY_VERIFY(waylandWindow->isActive()); + QCOMPARE(workspace()->stackingOrder().last(), waylandWindow); + QCOMPARE(workspace()->stackingOrder().last(), waylandWindow); + + // back to x window + workspace()->activateWindow(window); + QTRY_VERIFY(window->isActive()); + // remove fullscreen + QVERIFY(window->isFullScreen()); + workspace()->slotWindowFullScreen(); + QVERIFY(!window->isFullScreen()); + // and fullscreen again + workspace()->slotWindowFullScreen(); + QVERIFY(window->isFullScreen()); + QCOMPARE(workspace()->stackingOrder().last(), window); + QCOMPARE(workspace()->stackingOrder().last(), window); + + // activate wayland window again + workspace()->activateWindow(waylandWindow); + QTRY_VERIFY(waylandWindow->isActive()); + QCOMPARE(workspace()->stackingOrder().last(), waylandWindow); + QCOMPARE(workspace()->stackingOrder().last(), waylandWindow); + + // back to X11 window + workspace()->activateWindow(window); + QTRY_VERIFY(window->isActive()); + // remove fullscreen + QVERIFY(window->isFullScreen()); + workspace()->slotWindowFullScreen(); + QVERIFY(!window->isFullScreen()); + // and fullscreen through X API + NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2()); + info.setState(NET::FullScreen, NET::FullScreen); + NETRootInfo rootInfo(c.get(), NET::Properties()); + rootInfo.setActiveWindow(windowId, NET::FromApplication, XCB_CURRENT_TIME, XCB_WINDOW_NONE); + xcb_flush(c.get()); + QTRY_VERIFY(window->isFullScreen()); + QCOMPARE(workspace()->stackingOrder().last(), window); + QCOMPARE(workspace()->stackingOrder().last(), window); + + // activate wayland window again + workspace()->activateWindow(waylandWindow); + QTRY_VERIFY(waylandWindow->isActive()); + QCOMPARE(workspace()->stackingOrder().last(), waylandWindow); + QCOMPARE(workspace()->stackingOrder().last(), waylandWindow); + QCOMPARE(window->layer(), NormalLayer); + + // close the window + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(waylandWindow)); + QTRY_VERIFY(window->isActive()); + QCOMPARE(window->layer(), ActiveLayer); + + // and destroy the window again + xcb_unmap_window(c.get(), windowId); + xcb_flush(c.get()); +} + +void X11WindowTest::testFocusInWithWaylandLastActiveWindow() +{ + // this test verifies that Workspace::allowWindowActivation does not crash if last client was a Wayland client + QFETCH_GLOBAL(qreal, scale); + kwinApp()->setXwaylandScale(scale); + + // create an X11 window + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QVERIFY(window->isActive()); + + // create Wayland window + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto waylandWindow = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(waylandWindow); + QVERIFY(waylandWindow->isActive()); + // activate no window + workspace()->setActiveWindow(nullptr); + QVERIFY(!waylandWindow->isActive()); + QVERIFY(!workspace()->activeWindow()); + // and close Wayland window again + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(waylandWindow)); + + // and try to activate the x11 window through X11 api + const auto cookie = xcb_set_input_focus_checked(c.get(), XCB_INPUT_FOCUS_NONE, windowId, XCB_CURRENT_TIME); + auto error = xcb_request_check(c.get(), cookie); + QVERIFY(!error); + // this accesses m_lastActiveWindow on trying to activate + QTRY_VERIFY(window->isActive()); + + // and destroy the window again + xcb_unmap_window(c.get(), windowId); + xcb_flush(c.get()); +} + +void X11WindowTest::testX11WindowId() +{ + QFETCH_GLOBAL(qreal, scale); + kwinApp()->setXwaylandScale(scale); + + // create an X11 window + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QVERIFY(window->isActive()); + QCOMPARE(window->window(), windowId); + QCOMPARE(window->internalId().isNull(), false); + const auto uuid = window->internalId(); + QUuid deletedUuid; + QCOMPARE(deletedUuid.isNull(), true); + + connect(window, &X11Window::windowClosed, this, [&deletedUuid](Window *, Deleted *d) { + deletedUuid = d->internalId(); + }); + + NETRootInfo rootInfo(c.get(), NET::WMAllProperties); + QCOMPARE(rootInfo.activeWindow(), window->window()); + + // activate a wayland window + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto waylandWindow = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(waylandWindow); + QVERIFY(waylandWindow->isActive()); + xcb_flush(kwinApp()->x11Connection()); + + NETRootInfo rootInfo2(c.get(), NET::WMAllProperties); + QCOMPARE(rootInfo2.activeWindow(), 0u); + + // back to X11 window + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(waylandWindow)); + + QTRY_VERIFY(window->isActive()); + NETRootInfo rootInfo3(c.get(), NET::WMAllProperties); + QCOMPARE(rootInfo3.activeWindow(), window->window()); + + // and destroy the window again + xcb_unmap_window(c.get(), windowId); + xcb_flush(c.get()); + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + QVERIFY(windowClosedSpy.wait()); + + QCOMPARE(deletedUuid.isNull(), false); + QCOMPARE(deletedUuid, uuid); +} + +void X11WindowTest::testCaptionChanges() +{ + QFETCH_GLOBAL(qreal, scale); + kwinApp()->setXwaylandScale(scale); + + // verifies that caption is updated correctly when the X11 window updates it + // BUG: 383444 + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2()); + info.setName("foo"); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + // we should get a window for it + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QCOMPARE(window->caption(), QStringLiteral("foo")); + + QSignalSpy captionChangedSpy(window, &X11Window::captionChanged); + info.setName("bar"); + xcb_flush(c.get()); + QVERIFY(captionChangedSpy.wait()); + QCOMPARE(window->caption(), QStringLiteral("bar")); + + // and destroy the window again + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + xcb_unmap_window(c.get(), windowId); + xcb_flush(c.get()); + QVERIFY(windowClosedSpy.wait()); + xcb_destroy_window(c.get(), windowId); + c.reset(); +} + +void X11WindowTest::testCaptionWmName() +{ + QFETCH_GLOBAL(qreal, scale); + kwinApp()->setXwaylandScale(scale); + + // this test verifies that a caption set through WM_NAME is read correctly + + // open glxgears as that one only uses WM_NAME + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + + QProcess glxgears; + glxgears.setProgram(QStringLiteral("glxgears")); + glxgears.start(); + QVERIFY(glxgears.waitForStarted()); + + QVERIFY(windowAddedSpy.wait()); + QCOMPARE(windowAddedSpy.count(), 1); + QCOMPARE(workspace()->clientList().count(), 1); + X11Window *glxgearsWindow = workspace()->clientList().first(); + QCOMPARE(glxgearsWindow->caption(), QStringLiteral("glxgears")); + + glxgears.terminate(); + QVERIFY(glxgears.waitForFinished()); +} + +void X11WindowTest::testCaptionMultipleWindows() +{ + QFETCH_GLOBAL(qreal, scale); + kwinApp()->setXwaylandScale(scale); + + // BUG 384760 + // create first window + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2()); + info.setName("foo"); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QCOMPARE(window->caption(), QStringLiteral("foo")); + + // create second window with same caption + xcb_window_t w2 = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, w2, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_icccm_set_wm_normal_hints(c.get(), w2, &hints); + NETWinInfo info2(c.get(), w2, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2()); + info2.setName("foo"); + info2.setIconName("foo"); + xcb_map_window(c.get(), w2); + xcb_flush(c.get()); + + windowCreatedSpy.clear(); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window2 = windowCreatedSpy.first().first().value(); + QVERIFY(window2); + QCOMPARE(window2->window(), w2); + QCOMPARE(window2->caption(), QStringLiteral("foo <2>\u200E")); + NETWinInfo info3(kwinApp()->x11Connection(), w2, kwinApp()->x11RootWindow(), NET::WMVisibleName | NET::WMVisibleIconName, NET::Properties2()); + QCOMPARE(QByteArray(info3.visibleName()), QByteArrayLiteral("foo <2>\u200E")); + QCOMPARE(QByteArray(info3.visibleIconName()), QByteArrayLiteral("foo <2>\u200E")); + + QSignalSpy captionChangedSpy(window2, &X11Window::captionChanged); + + NETWinInfo info4(c.get(), w2, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2()); + info4.setName("foobar"); + info4.setIconName("foobar"); + xcb_map_window(c.get(), w2); + xcb_flush(c.get()); + + QVERIFY(captionChangedSpy.wait()); + QCOMPARE(window2->caption(), QStringLiteral("foobar")); + NETWinInfo info5(kwinApp()->x11Connection(), w2, kwinApp()->x11RootWindow(), NET::WMVisibleName | NET::WMVisibleIconName, NET::Properties2()); + QCOMPARE(QByteArray(info5.visibleName()), QByteArray()); + QTRY_COMPARE(QByteArray(info5.visibleIconName()), QByteArray()); +} + +void X11WindowTest::testFullscreenWindowGroups() +{ + // this test creates an X11 window and puts it to full screen + // then a second window is created which is in the same window group + // BUG: 388310 + + QFETCH_GLOBAL(qreal, scale); + kwinApp()->setXwaylandScale(scale); + + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QCOMPARE(window->isActive(), true); + + QCOMPARE(window->isFullScreen(), false); + QCOMPARE(window->layer(), NormalLayer); + workspace()->slotWindowFullScreen(); + QCOMPARE(window->isFullScreen(), true); + QCOMPARE(window->layer(), ActiveLayer); + + // now let's create a second window + windowCreatedSpy.clear(); + xcb_window_t w2 = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, w2, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints2; + memset(&hints2, 0, sizeof(hints2)); + xcb_icccm_size_hints_set_position(&hints2, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints2, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), w2, &hints2); + xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, w2, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId); + xcb_map_window(c.get(), w2); + xcb_flush(c.get()); + + QVERIFY(windowCreatedSpy.wait()); + X11Window *window2 = windowCreatedSpy.first().first().value(); + QVERIFY(window2); + QVERIFY(window != window2); + QCOMPARE(window2->window(), w2); + QCOMPARE(window2->isActive(), true); + QCOMPARE(window2->group(), window->group()); + // first window should be moved back to normal layer + QCOMPARE(window->isActive(), false); + QCOMPARE(window->isFullScreen(), true); + QCOMPARE(window->layer(), NormalLayer); + + // activating the fullscreen window again, should move it to active layer + workspace()->activateWindow(window); + QTRY_COMPARE(window->layer(), ActiveLayer); +} + +void X11WindowTest::testActivateFocusedWindow() +{ + // The window manager may call XSetInputFocus() on a window that already has focus, in which + // case no FocusIn event will be generated and the window won't be marked as active. This test + // verifies that we handle that subtle case properly. + + QFETCH_GLOBAL(qreal, scale); + kwinApp()->setXwaylandScale(scale); + + std::unique_ptr connection(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(connection.get())); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + + const QRect windowGeometry(0, 0, 100, 200); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + + // Create the first test window. + const xcb_window_t windowId1 = xcb_generate_id(connection.get()); + xcb_create_window(connection.get(), XCB_COPY_FROM_PARENT, windowId1, rootWindow(), + windowGeometry.x(), windowGeometry.y(), + windowGeometry.width(), windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_icccm_set_wm_normal_hints(connection.get(), windowId1, &hints); + xcb_change_property(connection.get(), XCB_PROP_MODE_REPLACE, windowId1, + atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId1); + xcb_map_window(connection.get(), windowId1); + xcb_flush(connection.get()); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window1 = windowCreatedSpy.first().first().value(); + QVERIFY(window1); + QCOMPARE(window1->window(), windowId1); + QCOMPARE(window1->isActive(), true); + + // Create the second test window. + const xcb_window_t windowId2 = xcb_generate_id(connection.get()); + xcb_create_window(connection.get(), XCB_COPY_FROM_PARENT, windowId2, rootWindow(), + windowGeometry.x(), windowGeometry.y(), + windowGeometry.width(), windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_icccm_set_wm_normal_hints(connection.get(), windowId2, &hints); + xcb_change_property(connection.get(), XCB_PROP_MODE_REPLACE, windowId2, + atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId2); + xcb_map_window(connection.get(), windowId2); + xcb_flush(connection.get()); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window2 = windowCreatedSpy.last().first().value(); + QVERIFY(window2); + QCOMPARE(window2->window(), windowId2); + QCOMPARE(window2->isActive(), true); + + // When the second test window is destroyed, the window manager will attempt to activate the + // next window in the focus chain, which is the first window. + xcb_set_input_focus(connection.get(), XCB_INPUT_FOCUS_POINTER_ROOT, windowId1, XCB_CURRENT_TIME); + xcb_destroy_window(connection.get(), windowId2); + xcb_flush(connection.get()); + QVERIFY(Test::waitForWindowDestroyed(window2)); + QVERIFY(window1->isActive()); + + // Destroy the first test window. + xcb_destroy_window(connection.get(), windowId1); + xcb_flush(connection.get()); + QVERIFY(Test::waitForWindowDestroyed(window1)); +} + +void X11WindowTest::testReentrantMoveResize() +{ + // This test verifies that calling moveResize() from a slot connected directly + // to the frameGeometryChanged() signal won't cause an infinite recursion. + + QFETCH_GLOBAL(qreal, scale); + kwinApp()->setXwaylandScale(scale); + + // Create a test window. + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.first().first().value(); + QVERIFY(window); + QCOMPARE(window->pos(), QPoint(0, 0)); + + // Let's pretend that there is a script that really wants the window to be at (100, 100). + connect(window, &Window::frameGeometryChanged, this, [window]() { + window->moveResize(QRectF(QPointF(100, 100), window->size())); + }); + + // Trigger the lambda above. + window->move(QPoint(40, 50)); + + // Eventually, the window will end up at (100, 100). + QCOMPARE(window->pos(), QPoint(100, 100)); + + // Destroy the test window. + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +WAYLANDTEST_MAIN(X11WindowTest) +#include "x11_window_test.moc" diff --git a/autotests/integration/xdgshellwindow_rules_test.cpp b/autotests/integration/xdgshellwindow_rules_test.cpp new file mode 100644 index 0000000..8651322 --- /dev/null +++ b/autotests/integration/xdgshellwindow_rules_test.cpp @@ -0,0 +1,2970 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2017 Martin Flöser + SPDX-FileCopyrightText: 2019 Vlad Zahorodnii + SPDX-FileCopyrightText: 2022 Ismael Asensio + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/outputconfiguration.h" +#include "core/platform.h" +#include "cursor.h" +#include "rules.h" +#include "virtualdesktops.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include + +#include + +using namespace KWin; +using namespace KWayland::Client; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_xdgshellwindow_rules-0"); + +class TestXdgShellWindowRules : public QObject +{ + Q_OBJECT + + enum ClientFlag { + None = 0, + ClientShouldBeInactive = 1 << 0, // Window should be inactive. Used on Minimize tests + ServerSideDecoration = 1 << 1, // Create window with server side decoration. Used on noBorder tests + ReturnAfterSurfaceConfiguration = 1 << 2, // Do not create the window now, but return after surface configuration. + }; + Q_DECLARE_FLAGS(ClientFlags, ClientFlag) + +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testPositionDontAffect(); + void testPositionApply(); + void testPositionRemember(); + void testPositionForce(); + void testPositionApplyNow(); + void testPositionForceTemporarily(); + + void testSizeDontAffect(); + void testSizeApply(); + void testSizeRemember(); + void testSizeForce(); + void testSizeApplyNow(); + void testSizeForceTemporarily(); + + void testMaximizeDontAffect(); + void testMaximizeApply(); + void testMaximizeRemember(); + void testMaximizeForce(); + void testMaximizeApplyNow(); + void testMaximizeForceTemporarily(); + + void testDesktopsDontAffect(); + void testDesktopsApply(); + void testDesktopsRemember(); + void testDesktopsForce(); + void testDesktopsApplyNow(); + void testDesktopsForceTemporarily(); + + void testMinimizeDontAffect(); + void testMinimizeApply(); + void testMinimizeRemember(); + void testMinimizeForce(); + void testMinimizeApplyNow(); + void testMinimizeForceTemporarily(); + + void testSkipTaskbarDontAffect(); + void testSkipTaskbarApply(); + void testSkipTaskbarRemember(); + void testSkipTaskbarForce(); + void testSkipTaskbarApplyNow(); + void testSkipTaskbarForceTemporarily(); + + void testSkipPagerDontAffect(); + void testSkipPagerApply(); + void testSkipPagerRemember(); + void testSkipPagerForce(); + void testSkipPagerApplyNow(); + void testSkipPagerForceTemporarily(); + + void testSkipSwitcherDontAffect(); + void testSkipSwitcherApply(); + void testSkipSwitcherRemember(); + void testSkipSwitcherForce(); + void testSkipSwitcherApplyNow(); + void testSkipSwitcherForceTemporarily(); + + void testKeepAboveDontAffect(); + void testKeepAboveApply(); + void testKeepAboveRemember(); + void testKeepAboveForce(); + void testKeepAboveApplyNow(); + void testKeepAboveForceTemporarily(); + + void testKeepBelowDontAffect(); + void testKeepBelowApply(); + void testKeepBelowRemember(); + void testKeepBelowForce(); + void testKeepBelowApplyNow(); + void testKeepBelowForceTemporarily(); + + void testShortcutDontAffect(); + void testShortcutApply(); + void testShortcutRemember(); + void testShortcutForce(); + void testShortcutApplyNow(); + void testShortcutForceTemporarily(); + + void testDesktopFileDontAffect(); + void testDesktopFileApply(); + void testDesktopFileRemember(); + void testDesktopFileForce(); + void testDesktopFileApplyNow(); + void testDesktopFileForceTemporarily(); + + void testActiveOpacityDontAffect(); + void testActiveOpacityForce(); + void testActiveOpacityForceTemporarily(); + + void testInactiveOpacityDontAffect(); + void testInactiveOpacityForce(); + void testInactiveOpacityForceTemporarily(); + + void testNoBorderDontAffect(); + void testNoBorderApply(); + void testNoBorderRemember(); + void testNoBorderForce(); + void testNoBorderApplyNow(); + void testNoBorderForceTemporarily(); + + void testScreenDontAffect(); + void testScreenApply(); + void testScreenRemember(); + void testScreenForce(); + void testScreenApplyNow(); + void testScreenForceTemporarily(); + + void testMatchAfterNameChange(); + +private: + void createTestWindow(ClientFlags flags = None); + void mapClientToSurface(QSize clientSize, ClientFlags flags = None); + void destroyTestWindow(); + + template + void setWindowRule(const QString &property, const T &value, int policy); + +private: + KSharedConfig::Ptr m_config; + + Window *m_window; + std::unique_ptr m_surface; + std::unique_ptr m_shellSurface; + + std::unique_ptr m_toplevelConfigureRequestedSpy; + std::unique_ptr m_surfaceConfigureRequestedSpy; +}; + +void TestXdgShellWindowRules::initTestCase() +{ + qRegisterMetaType(); + + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); + + m_config = KSharedConfig::openConfig(QStringLiteral("kwinrulesrc"), KConfig::SimpleConfig); + workspace()->rulebook()->setConfig(m_config); +} + +void TestXdgShellWindowRules::init() +{ + VirtualDesktopManager::self()->setCurrent(VirtualDesktopManager::self()->desktops().first()); + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::XdgDecorationV1)); + + workspace()->setActiveOutput(QPoint(640, 512)); +} + +void TestXdgShellWindowRules::cleanup() +{ + if (m_shellSurface) { + destroyTestWindow(); + } + + Test::destroyWaylandConnection(); + + // Wipe the window rule config clean. + for (const QString &group : m_config->groupList()) { + m_config->deleteGroup(group); + } + workspace()->slotReconfigure(); + + // Restore virtual desktops to the initial state. + VirtualDesktopManager::self()->setCount(1); + QCOMPARE(VirtualDesktopManager::self()->count(), 1u); +} + +void TestXdgShellWindowRules::createTestWindow(ClientFlags flags) +{ + // Apply flags for special windows and rules + const bool createClient = !(flags & ReturnAfterSurfaceConfiguration); + const auto decorationMode = (flags & ServerSideDecoration) ? Test::XdgToplevelDecorationV1::mode_server_side + : Test::XdgToplevelDecorationV1::mode_client_side; + // Create an xdg surface. + m_surface = Test::createSurface(); + m_shellSurface.reset(Test::createXdgToplevelSurface(m_surface.get(), Test::CreationSetup::CreateOnly, m_surface.get())); + Test::XdgToplevelDecorationV1 *decoration = Test::createXdgToplevelDecorationV1(m_shellSurface.get(), m_shellSurface.get()); + + // Add signal watchers + m_toplevelConfigureRequestedSpy.reset(new QSignalSpy(m_shellSurface.get(), &Test::XdgToplevel::configureRequested)); + m_surfaceConfigureRequestedSpy.reset(new QSignalSpy(m_shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested)); + + m_shellSurface->set_app_id(QStringLiteral("org.kde.foo")); + decoration->set_mode(decorationMode); + + // Wait for the initial configure event + m_surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + + if (createClient) { + mapClientToSurface(QSize(100, 50), flags); + } +} + +void TestXdgShellWindowRules::mapClientToSurface(QSize clientSize, ClientFlags flags) +{ + const bool clientShouldBeActive = !(flags & ClientShouldBeInactive); + + QVERIFY(m_surface != nullptr); + QVERIFY(m_shellSurface != nullptr); + QVERIFY(m_surfaceConfigureRequestedSpy != nullptr); + + // Draw content of the surface. + m_shellSurface->xdgSurface()->ack_configure(m_surfaceConfigureRequestedSpy->last().at(0).value()); + + // Create the window + m_window = Test::renderAndWaitForShown(m_surface.get(), clientSize, Qt::blue); + QVERIFY(m_window); + QCOMPARE(m_window->isActive(), clientShouldBeActive); +} + +void TestXdgShellWindowRules::destroyTestWindow() +{ + m_surfaceConfigureRequestedSpy.reset(); + m_toplevelConfigureRequestedSpy.reset(); + m_shellSurface.reset(); + m_surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(m_window)); +} + +template +void TestXdgShellWindowRules::setWindowRule(const QString &property, const T &value, int policy) +{ + // Initialize RuleBook with the test rule. + m_config->group("General").writeEntry("count", 1); + KConfigGroup group = m_config->group("1"); + + group.writeEntry(property, value); + group.writeEntry(QStringLiteral("%1rule").arg(property), policy); + + group.writeEntry("wmclass", "org.kde.foo"); + group.writeEntry("wmclasscomplete", false); + group.writeEntry("wmclassmatch", int(Rules::ExactMatch)); + group.sync(); + + workspace()->slotReconfigure(); +} + +void TestXdgShellWindowRules::testPositionDontAffect() +{ + setWindowRule("position", QPoint(42, 42), int(Rules::DontAffect)); + + createTestWindow(); + + // The position of the window should not be affected by the rule. The default + // placement policy will put the window in the top-left corner of the screen. + QVERIFY(m_window->isMovable()); + QVERIFY(m_window->isMovableAcrossScreens()); + QCOMPARE(m_window->pos(), QPoint(0, 0)); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testPositionApply() +{ + setWindowRule("position", QPoint(42, 42), int(Rules::Apply)); + + createTestWindow(); + + // The window should be moved to the position specified by the rule. + QVERIFY(m_window->isMovable()); + QVERIFY(m_window->isMovableAcrossScreens()); + QCOMPARE(m_window->pos(), QPoint(42, 42)); + + // One should still be able to move the window around. + QSignalSpy clientStartMoveResizedSpy(m_window, &Window::clientStartUserMovedResized); + QSignalSpy clientStepUserMovedResizedSpy(m_window, &Window::clientStepUserMovedResized); + QSignalSpy clientFinishUserMovedResizedSpy(m_window, &Window::clientFinishUserMovedResized); + + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(!m_window->isInteractiveMove()); + QVERIFY(!m_window->isInteractiveResize()); + workspace()->slotWindowMove(); + QCOMPARE(workspace()->moveResizeWindow(), m_window); + QCOMPARE(clientStartMoveResizedSpy.count(), 1); + QVERIFY(m_window->isInteractiveMove()); + QVERIFY(!m_window->isInteractiveResize()); + + const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); + m_window->keyPressEvent(Qt::Key_Right); + m_window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + QCOMPARE(m_window->pos(), QPoint(50, 42)); + + m_window->keyPressEvent(Qt::Key_Enter); + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(!m_window->isInteractiveMove()); + QVERIFY(!m_window->isInteractiveResize()); + QCOMPARE(m_window->pos(), QPoint(50, 42)); + + // The rule should be applied again if the window appears after it's been closed. + destroyTestWindow(); + createTestWindow(); + + QVERIFY(m_window->isMovable()); + QVERIFY(m_window->isMovableAcrossScreens()); + QCOMPARE(m_window->pos(), QPoint(42, 42)); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testPositionRemember() +{ + setWindowRule("position", QPoint(42, 42), int(Rules::Remember)); + createTestWindow(); + + // The window should be moved to the position specified by the rule. + QVERIFY(m_window->isMovable()); + QVERIFY(m_window->isMovableAcrossScreens()); + QCOMPARE(m_window->pos(), QPoint(42, 42)); + + // One should still be able to move the window around. + QSignalSpy clientStartMoveResizedSpy(m_window, &Window::clientStartUserMovedResized); + QSignalSpy clientStepUserMovedResizedSpy(m_window, &Window::clientStepUserMovedResized); + QSignalSpy clientFinishUserMovedResizedSpy(m_window, &Window::clientFinishUserMovedResized); + + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(!m_window->isInteractiveMove()); + QVERIFY(!m_window->isInteractiveResize()); + workspace()->slotWindowMove(); + QCOMPARE(workspace()->moveResizeWindow(), m_window); + QCOMPARE(clientStartMoveResizedSpy.count(), 1); + QVERIFY(m_window->isInteractiveMove()); + QVERIFY(!m_window->isInteractiveResize()); + + const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); + m_window->keyPressEvent(Qt::Key_Right); + m_window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + QCOMPARE(m_window->pos(), QPoint(50, 42)); + + m_window->keyPressEvent(Qt::Key_Enter); + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(!m_window->isInteractiveMove()); + QVERIFY(!m_window->isInteractiveResize()); + QCOMPARE(m_window->pos(), QPoint(50, 42)); + + // The window should be placed at the last know position if we reopen it. + destroyTestWindow(); + createTestWindow(); + + QVERIFY(m_window->isMovable()); + QVERIFY(m_window->isMovableAcrossScreens()); + QCOMPARE(m_window->pos(), QPoint(50, 42)); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testPositionForce() +{ + setWindowRule("position", QPoint(42, 42), int(Rules::Force)); + + createTestWindow(); + + // The window should be moved to the position specified by the rule. + QVERIFY(!m_window->isMovable()); + QVERIFY(!m_window->isMovableAcrossScreens()); + QCOMPARE(m_window->pos(), QPoint(42, 42)); + + // User should not be able to move the window. + QSignalSpy clientStartMoveResizedSpy(m_window, &Window::clientStartUserMovedResized); + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(!m_window->isInteractiveMove()); + QVERIFY(!m_window->isInteractiveResize()); + workspace()->slotWindowMove(); + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QCOMPARE(clientStartMoveResizedSpy.count(), 0); + QVERIFY(!m_window->isInteractiveMove()); + QVERIFY(!m_window->isInteractiveResize()); + + // The position should still be forced if we reopen the window. + destroyTestWindow(); + createTestWindow(); + + QVERIFY(!m_window->isMovable()); + QVERIFY(!m_window->isMovableAcrossScreens()); + QCOMPARE(m_window->pos(), QPoint(42, 42)); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testPositionApplyNow() +{ + createTestWindow(); + + // The position of the window isn't set by any rule, thus the default placement + // policy will try to put the window in the top-left corner of the screen. + QVERIFY(m_window->isMovable()); + QVERIFY(m_window->isMovableAcrossScreens()); + QCOMPARE(m_window->pos(), QPoint(0, 0)); + + QSignalSpy frameGeometryChangedSpy(m_window, &Window::frameGeometryChanged); + + setWindowRule("position", QPoint(42, 42), int(Rules::ApplyNow)); + + // The window should be moved to the position specified by the rule. + QCOMPARE(frameGeometryChangedSpy.count(), 1); + QCOMPARE(m_window->pos(), QPoint(42, 42)); + + // We still have to be able to move the window around. + QVERIFY(m_window->isMovable()); + QVERIFY(m_window->isMovableAcrossScreens()); + QSignalSpy clientStartMoveResizedSpy(m_window, &Window::clientStartUserMovedResized); + QSignalSpy clientStepUserMovedResizedSpy(m_window, &Window::clientStepUserMovedResized); + QSignalSpy clientFinishUserMovedResizedSpy(m_window, &Window::clientFinishUserMovedResized); + + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(!m_window->isInteractiveMove()); + QVERIFY(!m_window->isInteractiveResize()); + workspace()->slotWindowMove(); + QCOMPARE(workspace()->moveResizeWindow(), m_window); + QCOMPARE(clientStartMoveResizedSpy.count(), 1); + QVERIFY(m_window->isInteractiveMove()); + QVERIFY(!m_window->isInteractiveResize()); + + const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); + m_window->keyPressEvent(Qt::Key_Right); + m_window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + QCOMPARE(m_window->pos(), QPoint(50, 42)); + + m_window->keyPressEvent(Qt::Key_Enter); + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(!m_window->isInteractiveMove()); + QVERIFY(!m_window->isInteractiveResize()); + QCOMPARE(m_window->pos(), QPoint(50, 42)); + + // The rule should not be applied again. + m_window->evaluateWindowRules(); + QCOMPARE(m_window->pos(), QPoint(50, 42)); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testPositionForceTemporarily() +{ + setWindowRule("position", QPoint(42, 42), int(Rules::ForceTemporarily)); + + createTestWindow(); + + // The window should be moved to the position specified by the rule. + QVERIFY(!m_window->isMovable()); + QVERIFY(!m_window->isMovableAcrossScreens()); + QCOMPARE(m_window->pos(), QPoint(42, 42)); + + // User should not be able to move the window. + QSignalSpy clientStartMoveResizedSpy(m_window, &Window::clientStartUserMovedResized); + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(!m_window->isInteractiveMove()); + QVERIFY(!m_window->isInteractiveResize()); + workspace()->slotWindowMove(); + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QCOMPARE(clientStartMoveResizedSpy.count(), 0); + QVERIFY(!m_window->isInteractiveMove()); + QVERIFY(!m_window->isInteractiveResize()); + + // The rule should be discarded if we close the window. + destroyTestWindow(); + createTestWindow(); + + QVERIFY(m_window->isMovable()); + QVERIFY(m_window->isMovableAcrossScreens()); + QCOMPARE(m_window->pos(), QPoint(0, 0)); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSizeDontAffect() +{ + setWindowRule("size", QSize(480, 640), int(Rules::DontAffect)); + + createTestWindow(ReturnAfterSurfaceConfiguration); + + // The window size shouldn't be enforced by the rule. + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().first().toSize(), QSize(0, 0)); + + // Map the window. + mapClientToSurface(QSize(100, 50)); + QVERIFY(m_window->isResizable()); + QCOMPARE(m_window->size(), QSize(100, 50)); + + // We should receive a configure event when the window becomes active. + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSizeApply() +{ + setWindowRule("size", QSize(480, 640), int(Rules::Apply)); + + createTestWindow(ReturnAfterSurfaceConfiguration); + + // The initial configure event should contain size hint set by the rule. + Test::XdgToplevel::States states; + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(480, 640)); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Resizing)); + + // Map the window. + mapClientToSurface(QSize(480, 640)); + QVERIFY(m_window->isResizable()); + QCOMPARE(m_window->size(), QSize(480, 640)); + + // We should receive a configure event when the window becomes active. + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Resizing)); + + // One still should be able to resize the window. + QSignalSpy frameGeometryChangedSpy(m_window, &Window::frameGeometryChanged); + QSignalSpy clientStartMoveResizedSpy(m_window, &Window::clientStartUserMovedResized); + QSignalSpy clientStepUserMovedResizedSpy(m_window, &Window::clientStepUserMovedResized); + QSignalSpy clientFinishUserMovedResizedSpy(m_window, &Window::clientFinishUserMovedResized); + + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(!m_window->isInteractiveMove()); + QVERIFY(!m_window->isInteractiveResize()); + workspace()->slotWindowResize(); + QCOMPARE(workspace()->moveResizeWindow(), m_window); + QCOMPARE(clientStartMoveResizedSpy.count(), 1); + QVERIFY(!m_window->isInteractiveMove()); + QVERIFY(m_window->isInteractiveResize()); + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 3); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 3); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing)); + m_shellSurface->xdgSurface()->ack_configure(m_surfaceConfigureRequestedSpy->last().at(0).value()); + + const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); + m_window->keyPressEvent(Qt::Key_Right); + m_window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 4); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 4); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing)); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(488, 640)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + m_shellSurface->xdgSurface()->ack_configure(m_surfaceConfigureRequestedSpy->last().at(0).value()); + Test::render(m_surface.get(), QSize(488, 640), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(m_window->size(), QSize(488, 640)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + + m_window->keyPressEvent(Qt::Key_Enter); + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(!m_window->isInteractiveMove()); + QVERIFY(!m_window->isInteractiveResize()); + + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 5); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 5); + + // The rule should be applied again if the window appears after it's been closed. + destroyTestWindow(); + createTestWindow(ReturnAfterSurfaceConfiguration); + + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().first().toSize(), QSize(480, 640)); + + mapClientToSurface(QSize(480, 640)); + QVERIFY(m_window->isResizable()); + QCOMPARE(m_window->size(), QSize(480, 640)); + + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSizeRemember() +{ + setWindowRule("size", QSize(480, 640), int(Rules::Remember)); + + createTestWindow(ReturnAfterSurfaceConfiguration); + + // The initial configure event should contain size hint set by the rule. + Test::XdgToplevel::States states; + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().first().toSize(), QSize(480, 640)); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Resizing)); + + // Map the window. + mapClientToSurface(QSize(480, 640)); + QVERIFY(m_window->isResizable()); + QCOMPARE(m_window->size(), QSize(480, 640)); + + // We should receive a configure event when the window becomes active. + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Resizing)); + + // One should still be able to resize the window. + QSignalSpy frameGeometryChangedSpy(m_window, &Window::frameGeometryChanged); + QSignalSpy clientStartMoveResizedSpy(m_window, &Window::clientStartUserMovedResized); + QSignalSpy clientStepUserMovedResizedSpy(m_window, &Window::clientStepUserMovedResized); + QSignalSpy clientFinishUserMovedResizedSpy(m_window, &Window::clientFinishUserMovedResized); + + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(!m_window->isInteractiveMove()); + QVERIFY(!m_window->isInteractiveResize()); + workspace()->slotWindowResize(); + QCOMPARE(workspace()->moveResizeWindow(), m_window); + QCOMPARE(clientStartMoveResizedSpy.count(), 1); + QVERIFY(!m_window->isInteractiveMove()); + QVERIFY(m_window->isInteractiveResize()); + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 3); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 3); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing)); + m_shellSurface->xdgSurface()->ack_configure(m_surfaceConfigureRequestedSpy->last().at(0).value()); + + const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); + m_window->keyPressEvent(Qt::Key_Right); + m_window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 4); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 4); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing)); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(488, 640)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + m_shellSurface->xdgSurface()->ack_configure(m_surfaceConfigureRequestedSpy->last().at(0).value()); + Test::render(m_surface.get(), QSize(488, 640), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(m_window->size(), QSize(488, 640)); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + + m_window->keyPressEvent(Qt::Key_Enter); + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(!m_window->isInteractiveMove()); + QVERIFY(!m_window->isInteractiveResize()); + + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 5); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 5); + + // If the window appears again, it should have the last known size. + destroyTestWindow(); + createTestWindow(ReturnAfterSurfaceConfiguration); + + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().first().toSize(), QSize(488, 640)); + + mapClientToSurface(QSize(488, 640)); + QVERIFY(m_window->isResizable()); + QCOMPARE(m_window->size(), QSize(488, 640)); + + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSizeForce() +{ + setWindowRule("size", QSize(480, 640), int(Rules::Force)); + + createTestWindow(ReturnAfterSurfaceConfiguration); + + // The initial configure event should contain size hint set by the rule. + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().first().toSize(), QSize(480, 640)); + + // Map the window. + mapClientToSurface(QSize(480, 640)); + QVERIFY(!m_window->isResizable()); + QCOMPARE(m_window->size(), QSize(480, 640)); + + // We should receive a configure event when the window becomes active. + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); + + // Any attempt to resize the window should not succeed. + QSignalSpy clientStartMoveResizedSpy(m_window, &Window::clientStartUserMovedResized); + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(!m_window->isInteractiveMove()); + QVERIFY(!m_window->isInteractiveResize()); + workspace()->slotWindowResize(); + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QCOMPARE(clientStartMoveResizedSpy.count(), 0); + QVERIFY(!m_window->isInteractiveMove()); + QVERIFY(!m_window->isInteractiveResize()); + QVERIFY(!m_surfaceConfigureRequestedSpy->wait(100)); + + // If the window appears again, the size should still be forced. + destroyTestWindow(); + createTestWindow(ReturnAfterSurfaceConfiguration); + + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().first().toSize(), QSize(480, 640)); + + mapClientToSurface(QSize(480, 640)); + QVERIFY(!m_window->isResizable()); + QCOMPARE(m_window->size(), QSize(480, 640)); + + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSizeApplyNow() +{ + createTestWindow(ReturnAfterSurfaceConfiguration); + + // The expected surface dimensions should be set by the rule. + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().first().toSize(), QSize(0, 0)); + + // Map the window. + mapClientToSurface(QSize(100, 50)); + QVERIFY(m_window->isResizable()); + QCOMPARE(m_window->size(), QSize(100, 50)); + + // We should receive a configure event when the window becomes active. + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); + + setWindowRule("size", QSize(480, 640), int(Rules::ApplyNow)); + + // The compositor should send a configure event with a new size. + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 3); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 3); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().first().toSize(), QSize(480, 640)); + + // Draw the surface with the new size. + QSignalSpy frameGeometryChangedSpy(m_window, &Window::frameGeometryChanged); + m_shellSurface->xdgSurface()->ack_configure(m_surfaceConfigureRequestedSpy->last().at(0).value()); + Test::render(m_surface.get(), QSize(480, 640), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(m_window->size(), QSize(480, 640)); + QVERIFY(!m_surfaceConfigureRequestedSpy->wait(100)); + + // The rule should not be applied again. + m_window->evaluateWindowRules(); + QVERIFY(!m_surfaceConfigureRequestedSpy->wait(100)); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSizeForceTemporarily() +{ + setWindowRule("size", QSize(480, 640), int(Rules::ForceTemporarily)); + + createTestWindow(ReturnAfterSurfaceConfiguration); + + // The initial configure event should contain size hint set by the rule. + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().first().toSize(), QSize(480, 640)); + + // Map the window. + mapClientToSurface(QSize(480, 640)); + QVERIFY(!m_window->isResizable()); + QCOMPARE(m_window->size(), QSize(480, 640)); + + // We should receive a configure event when the window becomes active. + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); + + // Any attempt to resize the window should not succeed. + QSignalSpy clientStartMoveResizedSpy(m_window, &Window::clientStartUserMovedResized); + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(!m_window->isInteractiveMove()); + QVERIFY(!m_window->isInteractiveResize()); + workspace()->slotWindowResize(); + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QCOMPARE(clientStartMoveResizedSpy.count(), 0); + QVERIFY(!m_window->isInteractiveMove()); + QVERIFY(!m_window->isInteractiveResize()); + QVERIFY(!m_surfaceConfigureRequestedSpy->wait(100)); + + // The rule should be discarded when the window is closed. + destroyTestWindow(); + createTestWindow(ReturnAfterSurfaceConfiguration); + + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().first().toSize(), QSize(0, 0)); + + mapClientToSurface(QSize(100, 50)); + QVERIFY(m_window->isResizable()); + QCOMPARE(m_window->size(), QSize(100, 50)); + + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testMaximizeDontAffect() +{ + setWindowRule("maximizehoriz", true, int(Rules::DontAffect)); + setWindowRule("maximizevert", true, int(Rules::DontAffect)); + + createTestWindow(ReturnAfterSurfaceConfiguration); + + // Wait for the initial configure event. + Test::XdgToplevel::States states; + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Map the window. + mapClientToSurface(QSize(100, 50)); + + QVERIFY(m_window->isMaximizable()); + QCOMPARE(m_window->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(m_window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(m_window->size(), QSize(100, 50)); + + // We should receive a configure event when the window becomes active. + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testMaximizeApply() +{ + setWindowRule("maximizehoriz", true, int(Rules::Apply)); + setWindowRule("maximizevert", true, int(Rules::Apply)); + + createTestWindow(ReturnAfterSurfaceConfiguration); + + // Wait for the initial configure event. + Test::XdgToplevel::States states; + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Map the window. + mapClientToSurface(QSize(1280, 1024)); + + QVERIFY(m_window->isMaximizable()); + QCOMPARE(m_window->maximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(m_window->requestedMaximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(m_window->size(), QSize(1280, 1024)); + + // We should receive a configure event when the window becomes active. + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); + + // One should still be able to change the maximized state of the window. + workspace()->slotWindowMaximize(); + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 3); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 3); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + QSignalSpy frameGeometryChangedSpy(m_window, &Window::frameGeometryChanged); + m_shellSurface->xdgSurface()->ack_configure(m_surfaceConfigureRequestedSpy->last().at(0).value()); + Test::render(m_surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(m_window->size(), QSize(100, 50)); + QCOMPARE(m_window->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(m_window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + + // If we create the window again, it should be initially maximized. + destroyTestWindow(); + createTestWindow(ReturnAfterSurfaceConfiguration); + + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); + + mapClientToSurface(QSize(1280, 1024)); + QVERIFY(m_window->isMaximizable()); + QCOMPARE(m_window->maximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(m_window->requestedMaximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(m_window->size(), QSize(1280, 1024)); + + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testMaximizeRemember() +{ + setWindowRule("maximizehoriz", true, int(Rules::Remember)); + setWindowRule("maximizevert", true, int(Rules::Remember)); + + createTestWindow(ReturnAfterSurfaceConfiguration); + + // Wait for the initial configure event. + Test::XdgToplevel::States states; + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Map the window. + mapClientToSurface(QSize(1280, 1024)); + + QVERIFY(m_window->isMaximizable()); + QCOMPARE(m_window->maximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(m_window->requestedMaximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(m_window->size(), QSize(1280, 1024)); + + // We should receive a configure event when the window becomes active. + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); + + // One should still be able to change the maximized state of the window. + workspace()->slotWindowMaximize(); + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 3); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 3); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + QSignalSpy frameGeometryChangedSpy(m_window, &Window::frameGeometryChanged); + m_shellSurface->xdgSurface()->ack_configure(m_surfaceConfigureRequestedSpy->last().at(0).value()); + Test::render(m_surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(m_window->size(), QSize(100, 50)); + QCOMPARE(m_window->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(m_window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + + // If we create the window again, it should not be maximized (because last time it wasn't). + destroyTestWindow(); + createTestWindow(ReturnAfterSurfaceConfiguration); + + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + mapClientToSurface(QSize(100, 50)); + + QVERIFY(m_window->isMaximizable()); + QCOMPARE(m_window->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(m_window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(m_window->size(), QSize(100, 50)); + + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testMaximizeForce() +{ + setWindowRule("maximizehoriz", true, int(Rules::Force)); + setWindowRule("maximizevert", true, int(Rules::Force)); + + createTestWindow(ReturnAfterSurfaceConfiguration); + + // Wait for the initial configure event. + Test::XdgToplevel::States states; + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Map the window. + mapClientToSurface(QSize(1280, 1024)); + + QVERIFY(!m_window->isMaximizable()); + QCOMPARE(m_window->maximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(m_window->requestedMaximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(m_window->size(), QSize(1280, 1024)); + + // We should receive a configure event when the window becomes active. + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Any attempt to change the maximized state should not succeed. + const QRectF oldGeometry = m_window->frameGeometry(); + workspace()->slotWindowMaximize(); + QVERIFY(!m_surfaceConfigureRequestedSpy->wait(100)); + QCOMPARE(m_window->maximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(m_window->requestedMaximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(m_window->frameGeometry(), oldGeometry); + + // If we create the window again, the maximized state should still be forced. + destroyTestWindow(); + createTestWindow(ReturnAfterSurfaceConfiguration); + + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); + + mapClientToSurface(QSize(1280, 1024)); + + QVERIFY(!m_window->isMaximizable()); + QCOMPARE(m_window->maximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(m_window->requestedMaximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(m_window->size(), QSize(1280, 1024)); + + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testMaximizeApplyNow() +{ + createTestWindow(ReturnAfterSurfaceConfiguration); + + // Wait for the initial configure event. + Test::XdgToplevel::States states; + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Map the window. + mapClientToSurface(QSize(100, 50)); + + QVERIFY(m_window->isMaximizable()); + QCOMPARE(m_window->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(m_window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(m_window->size(), QSize(100, 50)); + + // We should receive a configure event when the window becomes active. + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + setWindowRule("maximizehoriz", true, int(Rules::ApplyNow)); + setWindowRule("maximizevert", true, int(Rules::ApplyNow)); + + // We should receive a configure event with a new surface size. + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 3); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 3); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Draw contents of the maximized client. + QSignalSpy frameGeometryChangedSpy(m_window, &Window::frameGeometryChanged); + m_shellSurface->xdgSurface()->ack_configure(m_surfaceConfigureRequestedSpy->last().at(0).value()); + Test::render(m_surface.get(), QSize(1280, 1024), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(m_window->size(), QSize(1280, 1024)); + QCOMPARE(m_window->maximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(m_window->requestedMaximizeMode(), MaximizeMode::MaximizeFull); + + // The window still has to be maximizeable. + QVERIFY(m_window->isMaximizable()); + + // Restore the window. + workspace()->slotWindowMaximize(); + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 4); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 4); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(100, 50)); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + m_shellSurface->xdgSurface()->ack_configure(m_surfaceConfigureRequestedSpy->last().at(0).value()); + Test::render(m_surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(m_window->size(), QSize(100, 50)); + QCOMPARE(m_window->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(m_window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + + // The rule should be discarded after it's been applied. + const QRectF oldGeometry = m_window->frameGeometry(); + m_window->evaluateWindowRules(); + QVERIFY(!m_surfaceConfigureRequestedSpy->wait(100)); + QCOMPARE(m_window->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(m_window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(m_window->frameGeometry(), oldGeometry); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testMaximizeForceTemporarily() +{ + setWindowRule("maximizehoriz", true, int(Rules::ForceTemporarily)); + setWindowRule("maximizevert", true, int(Rules::ForceTemporarily)); + + createTestWindow(ReturnAfterSurfaceConfiguration); + + // Wait for the initial configure event. + Test::XdgToplevel::States states; + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(1280, 1024)); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Map the window. + mapClientToSurface(QSize(1280, 1024)); + + QVERIFY(!m_window->isMaximizable()); + QCOMPARE(m_window->maximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(m_window->requestedMaximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(m_window->size(), QSize(1280, 1024)); + + // We should receive a configure event when the window becomes active. + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Any attempt to change the maximized state should not succeed. + const QRectF oldGeometry = m_window->frameGeometry(); + workspace()->slotWindowMaximize(); + QVERIFY(!m_surfaceConfigureRequestedSpy->wait(100)); + QCOMPARE(m_window->maximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(m_window->requestedMaximizeMode(), MaximizeMode::MaximizeFull); + QCOMPARE(m_window->frameGeometry(), oldGeometry); + + // The rule should be discarded if we close the window. + destroyTestWindow(); + createTestWindow(ReturnAfterSurfaceConfiguration); + + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 1); + QCOMPARE(m_toplevelConfigureRequestedSpy->last().at(0).toSize(), QSize(0, 0)); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + mapClientToSurface(QSize(100, 50)); + + QVERIFY(m_window->isMaximizable()); + QCOMPARE(m_window->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(m_window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(m_window->size(), QSize(100, 50)); + + QVERIFY(m_surfaceConfigureRequestedSpy->wait()); + QCOMPARE(m_surfaceConfigureRequestedSpy->count(), 2); + QCOMPARE(m_toplevelConfigureRequestedSpy->count(), 2); + states = m_toplevelConfigureRequestedSpy->last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testDesktopsDontAffect() +{ + // We need at least two virtual desktop for this test. + VirtualDesktopManager::self()->setCount(2); + QCOMPARE(VirtualDesktopManager::self()->count(), 2u); + VirtualDesktop *vd1 = VirtualDesktopManager::self()->desktops().at(0); + VirtualDesktop *vd2 = VirtualDesktopManager::self()->desktops().at(1); + + VirtualDesktopManager::self()->setCurrent(vd1); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); + + setWindowRule("desktops", QStringList{vd2->id()}, int(Rules::DontAffect)); + + createTestWindow(); + + // The window should appear on the current virtual desktop. + QCOMPARE(m_window->desktops(), {vd1}); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testDesktopsApply() +{ + // We need at least two virtual desktop for this test. + VirtualDesktopManager::self()->setCount(2); + QCOMPARE(VirtualDesktopManager::self()->count(), 2u); + VirtualDesktop *vd1 = VirtualDesktopManager::self()->desktops().at(0); + VirtualDesktop *vd2 = VirtualDesktopManager::self()->desktops().at(1); + + VirtualDesktopManager::self()->setCurrent(vd1); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); + + setWindowRule("desktops", QStringList{vd2->id()}, int(Rules::Apply)); + + createTestWindow(); + + // The window should appear on the second virtual desktop. + QCOMPARE(m_window->desktops(), {vd2}); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd2); + + // We still should be able to move the window between desktops. + m_window->setDesktops({vd1}); + QCOMPARE(m_window->desktops(), {vd1}); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd2); + + // If we re-open the window, it should appear on the second virtual desktop again. + destroyTestWindow(); + VirtualDesktopManager::self()->setCurrent(vd1); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); + createTestWindow(); + + QCOMPARE(m_window->desktops(), {vd2}); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd2); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testDesktopsRemember() +{ + // We need at least two virtual desktop for this test. + VirtualDesktopManager::self()->setCount(2); + QCOMPARE(VirtualDesktopManager::self()->count(), 2u); + VirtualDesktop *vd1 = VirtualDesktopManager::self()->desktops().at(0); + VirtualDesktop *vd2 = VirtualDesktopManager::self()->desktops().at(1); + + VirtualDesktopManager::self()->setCurrent(vd1); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); + + setWindowRule("desktops", QStringList{vd2->id()}, int(Rules::Remember)); + + createTestWindow(); + + QCOMPARE(m_window->desktops(), {vd2}); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd2); + + // Move the window to the first virtual desktop. + m_window->setDesktops({vd1}); + QCOMPARE(m_window->desktops(), {vd1}); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd2); + + // If we create the window again, it should appear on the first virtual desktop. + destroyTestWindow(); + createTestWindow(); + + QCOMPARE(m_window->desktops(), {vd1}); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testDesktopsForce() +{ + // We need at least two virtual desktop for this test. + VirtualDesktopManager::self()->setCount(2); + QCOMPARE(VirtualDesktopManager::self()->count(), 2u); + VirtualDesktop *vd1 = VirtualDesktopManager::self()->desktops().at(0); + VirtualDesktop *vd2 = VirtualDesktopManager::self()->desktops().at(1); + + VirtualDesktopManager::self()->setCurrent(vd1); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); + + setWindowRule("desktops", QStringList{vd2->id()}, int(Rules::Force)); + + createTestWindow(); + + // The window should appear on the second virtual desktop. + QCOMPARE(m_window->desktops(), {vd2}); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd2); + + // Any attempt to move the window to another virtual desktop should fail. + m_window->setDesktops({vd1}); + QCOMPARE(m_window->desktops(), {vd2}); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd2); + + // If we re-open the window, it should appear on the second virtual desktop again. + destroyTestWindow(); + VirtualDesktopManager::self()->setCurrent(vd1); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); + createTestWindow(); + + QCOMPARE(m_window->desktops(), {vd2}); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd2); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testDesktopsApplyNow() +{ + // We need at least two virtual desktop for this test. + VirtualDesktopManager::self()->setCount(2); + QCOMPARE(VirtualDesktopManager::self()->count(), 2u); + VirtualDesktop *vd1 = VirtualDesktopManager::self()->desktops().at(0); + VirtualDesktop *vd2 = VirtualDesktopManager::self()->desktops().at(1); + + VirtualDesktopManager::self()->setCurrent(vd1); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); + + createTestWindow(); + + QCOMPARE(m_window->desktops(), {vd1}); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); + + setWindowRule("desktops", QStringList{vd2->id()}, int(Rules::ApplyNow)); + + // The window should have been moved to the second virtual desktop. + QCOMPARE(m_window->desktops(), {vd2}); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); + + // One should still be able to move the window between desktops. + m_window->setDesktops({vd1}); + QCOMPARE(m_window->desktops(), {vd1}); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); + + // The rule should not be applied again. + m_window->evaluateWindowRules(); + QCOMPARE(m_window->desktops(), {vd1}); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testDesktopsForceTemporarily() +{ + // We need at least two virtual desktop for this test. + VirtualDesktopManager::self()->setCount(2); + QCOMPARE(VirtualDesktopManager::self()->count(), 2u); + VirtualDesktop *vd1 = VirtualDesktopManager::self()->desktops().at(0); + VirtualDesktop *vd2 = VirtualDesktopManager::self()->desktops().at(1); + + VirtualDesktopManager::self()->setCurrent(vd1); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); + + setWindowRule("desktops", QStringList{vd2->id()}, int(Rules::ForceTemporarily)); + + createTestWindow(); + + // The window should appear on the second virtual desktop. + QCOMPARE(m_window->desktops(), {vd2}); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd2); + + // Any attempt to move the window to another virtual desktop should fail. + m_window->setDesktops({vd1}); + QCOMPARE(m_window->desktops(), {vd2}); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd2); + + // The rule should be discarded when the window is withdrawn. + destroyTestWindow(); + VirtualDesktopManager::self()->setCurrent(vd1); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); + createTestWindow(); + + QCOMPARE(m_window->desktops(), {vd1}); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); + + // One should be able to move the window between desktops. + m_window->setDesktops({vd2}); + QCOMPARE(m_window->desktops(), {vd2}); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); + + m_window->setDesktops({vd1}); + QCOMPARE(m_window->desktops(), {vd1}); + QCOMPARE(VirtualDesktopManager::self()->currentDesktop(), vd1); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testMinimizeDontAffect() +{ + setWindowRule("minimize", true, int(Rules::DontAffect)); + + createTestWindow(); + QVERIFY(m_window->isMinimizable()); + + // The window should not be minimized. + QVERIFY(!m_window->isMinimized()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testMinimizeApply() +{ + setWindowRule("minimize", true, int(Rules::Apply)); + + createTestWindow(ClientShouldBeInactive); + QVERIFY(m_window->isMinimizable()); + + // The window should be minimized. + QVERIFY(m_window->isMinimized()); + + // We should still be able to unminimize the window. + m_window->unminimize(); + QVERIFY(!m_window->isMinimized()); + + // If we re-open the window, it should be minimized back again. + destroyTestWindow(); + createTestWindow(ClientShouldBeInactive); + QVERIFY(m_window->isMinimizable()); + QVERIFY(m_window->isMinimized()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testMinimizeRemember() +{ + setWindowRule("minimize", false, int(Rules::Remember)); + + createTestWindow(); + QVERIFY(m_window->isMinimizable()); + QVERIFY(!m_window->isMinimized()); + + // Minimize the window. + m_window->minimize(); + QVERIFY(m_window->isMinimized()); + + // If we open the window again, it should be minimized. + destroyTestWindow(); + createTestWindow(ClientShouldBeInactive); + QVERIFY(m_window->isMinimizable()); + QVERIFY(m_window->isMinimized()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testMinimizeForce() +{ + setWindowRule("minimize", false, int(Rules::Force)); + + createTestWindow(); + QVERIFY(!m_window->isMinimizable()); + QVERIFY(!m_window->isMinimized()); + + // Any attempt to minimize the window should fail. + m_window->minimize(); + QVERIFY(!m_window->isMinimized()); + + // If we re-open the window, the minimized state should still be forced. + destroyTestWindow(); + createTestWindow(); + QVERIFY(!m_window->isMinimizable()); + QVERIFY(!m_window->isMinimized()); + m_window->minimize(); + QVERIFY(!m_window->isMinimized()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testMinimizeApplyNow() +{ + createTestWindow(); + QVERIFY(m_window->isMinimizable()); + QVERIFY(!m_window->isMinimized()); + + setWindowRule("minimize", true, int(Rules::ApplyNow)); + + // The window should be minimized now. + QVERIFY(m_window->isMinimizable()); + QVERIFY(m_window->isMinimized()); + + // One is still able to unminimize the window. + m_window->unminimize(); + QVERIFY(!m_window->isMinimized()); + + // The rule should not be applied again. + m_window->evaluateWindowRules(); + QVERIFY(m_window->isMinimizable()); + QVERIFY(!m_window->isMinimized()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testMinimizeForceTemporarily() +{ + setWindowRule("minimize", false, int(Rules::ForceTemporarily)); + + createTestWindow(); + QVERIFY(!m_window->isMinimizable()); + QVERIFY(!m_window->isMinimized()); + + // Any attempt to minimize the window should fail until the window is closed. + m_window->minimize(); + QVERIFY(!m_window->isMinimized()); + + // The rule should be discarded when the window is closed. + destroyTestWindow(); + createTestWindow(); + QVERIFY(m_window->isMinimizable()); + QVERIFY(!m_window->isMinimized()); + m_window->minimize(); + QVERIFY(m_window->isMinimized()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSkipTaskbarDontAffect() +{ + setWindowRule("skiptaskbar", true, int(Rules::DontAffect)); + + createTestWindow(); + + // The window should not be affected by the rule. + QVERIFY(!m_window->skipTaskbar()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSkipTaskbarApply() +{ + setWindowRule("skiptaskbar", true, int(Rules::Apply)); + + createTestWindow(); + + // The window should not be included on a taskbar. + QVERIFY(m_window->skipTaskbar()); + + // Though one can change that. + m_window->setOriginalSkipTaskbar(false); + QVERIFY(!m_window->skipTaskbar()); + + // Reopen the window, the rule should be applied again. + destroyTestWindow(); + createTestWindow(); + QVERIFY(m_window->skipTaskbar()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSkipTaskbarRemember() +{ + setWindowRule("skiptaskbar", true, int(Rules::Remember)); + + createTestWindow(); + + // The window should not be included on a taskbar. + QVERIFY(m_window->skipTaskbar()); + + // Change the skip-taskbar state. + m_window->setOriginalSkipTaskbar(false); + QVERIFY(!m_window->skipTaskbar()); + + // Reopen the window. + destroyTestWindow(); + createTestWindow(); + + // The window should be included on a taskbar. + QVERIFY(!m_window->skipTaskbar()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSkipTaskbarForce() +{ + setWindowRule("skiptaskbar", true, int(Rules::Force)); + + createTestWindow(); + + // The window should not be included on a taskbar. + QVERIFY(m_window->skipTaskbar()); + + // Any attempt to change the skip-taskbar state should not succeed. + m_window->setOriginalSkipTaskbar(false); + QVERIFY(m_window->skipTaskbar()); + + // Reopen the window. + destroyTestWindow(); + createTestWindow(); + + // The skip-taskbar state should be still forced. + QVERIFY(m_window->skipTaskbar()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSkipTaskbarApplyNow() +{ + createTestWindow(); + QVERIFY(!m_window->skipTaskbar()); + + setWindowRule("skiptaskbar", true, int(Rules::ApplyNow)); + + // The window should not be on a taskbar now. + QVERIFY(m_window->skipTaskbar()); + + // Also, one change the skip-taskbar state. + m_window->setOriginalSkipTaskbar(false); + QVERIFY(!m_window->skipTaskbar()); + + // The rule should not be applied again. + m_window->evaluateWindowRules(); + QVERIFY(!m_window->skipTaskbar()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSkipTaskbarForceTemporarily() +{ + setWindowRule("skiptaskbar", true, int(Rules::ForceTemporarily)); + + createTestWindow(); + + // The window should not be included on a taskbar. + QVERIFY(m_window->skipTaskbar()); + + // Any attempt to change the skip-taskbar state should not succeed. + m_window->setOriginalSkipTaskbar(false); + QVERIFY(m_window->skipTaskbar()); + + // The rule should be discarded when the window is closed. + destroyTestWindow(); + createTestWindow(); + QVERIFY(!m_window->skipTaskbar()); + + // The skip-taskbar state is no longer forced. + m_window->setOriginalSkipTaskbar(true); + QVERIFY(m_window->skipTaskbar()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSkipPagerDontAffect() +{ + setWindowRule("skippager", true, int(Rules::DontAffect)); + + createTestWindow(); + + // The window should not be affected by the rule. + QVERIFY(!m_window->skipPager()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSkipPagerApply() +{ + setWindowRule("skippager", true, int(Rules::Apply)); + + createTestWindow(); + + // The window should not be included on a pager. + QVERIFY(m_window->skipPager()); + + // Though one can change that. + m_window->setSkipPager(false); + QVERIFY(!m_window->skipPager()); + + // Reopen the window, the rule should be applied again. + destroyTestWindow(); + createTestWindow(); + QVERIFY(m_window->skipPager()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSkipPagerRemember() +{ + setWindowRule("skippager", true, int(Rules::Remember)); + + createTestWindow(); + + // The window should not be included on a pager. + QVERIFY(m_window->skipPager()); + + // Change the skip-pager state. + m_window->setSkipPager(false); + QVERIFY(!m_window->skipPager()); + + // Reopen the window. + destroyTestWindow(); + createTestWindow(); + + // The window should be included on a pager. + QVERIFY(!m_window->skipPager()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSkipPagerForce() +{ + setWindowRule("skippager", true, int(Rules::Force)); + + createTestWindow(); + + // The window should not be included on a pager. + QVERIFY(m_window->skipPager()); + + // Any attempt to change the skip-pager state should not succeed. + m_window->setSkipPager(false); + QVERIFY(m_window->skipPager()); + + // Reopen the window. + destroyTestWindow(); + createTestWindow(); + + // The skip-pager state should be still forced. + QVERIFY(m_window->skipPager()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSkipPagerApplyNow() +{ + createTestWindow(); + QVERIFY(!m_window->skipPager()); + + setWindowRule("skippager", true, int(Rules::ApplyNow)); + + // The window should not be on a pager now. + QVERIFY(m_window->skipPager()); + + // Also, one change the skip-pager state. + m_window->setSkipPager(false); + QVERIFY(!m_window->skipPager()); + + // The rule should not be applied again. + m_window->evaluateWindowRules(); + QVERIFY(!m_window->skipPager()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSkipPagerForceTemporarily() +{ + setWindowRule("skippager", true, int(Rules::ForceTemporarily)); + + createTestWindow(); + + // The window should not be included on a pager. + QVERIFY(m_window->skipPager()); + + // Any attempt to change the skip-pager state should not succeed. + m_window->setSkipPager(false); + QVERIFY(m_window->skipPager()); + + // The rule should be discarded when the window is closed. + destroyTestWindow(); + createTestWindow(); + QVERIFY(!m_window->skipPager()); + + // The skip-pager state is no longer forced. + m_window->setSkipPager(true); + QVERIFY(m_window->skipPager()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSkipSwitcherDontAffect() +{ + setWindowRule("skipswitcher", true, int(Rules::DontAffect)); + + createTestWindow(); + + // The window should not be affected by the rule. + QVERIFY(!m_window->skipSwitcher()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSkipSwitcherApply() +{ + setWindowRule("skipswitcher", true, int(Rules::Apply)); + + createTestWindow(); + + // The window should be excluded from window switching effects. + QVERIFY(m_window->skipSwitcher()); + + // Though one can change that. + m_window->setSkipSwitcher(false); + QVERIFY(!m_window->skipSwitcher()); + + // Reopen the window, the rule should be applied again. + destroyTestWindow(); + createTestWindow(); + QVERIFY(m_window->skipSwitcher()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSkipSwitcherRemember() +{ + setWindowRule("skipswitcher", true, int(Rules::Remember)); + + createTestWindow(); + + // The window should be excluded from window switching effects. + QVERIFY(m_window->skipSwitcher()); + + // Change the skip-switcher state. + m_window->setSkipSwitcher(false); + QVERIFY(!m_window->skipSwitcher()); + + // Reopen the window. + destroyTestWindow(); + createTestWindow(); + + // The window should be included in window switching effects. + QVERIFY(!m_window->skipSwitcher()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSkipSwitcherForce() +{ + setWindowRule("skipswitcher", true, int(Rules::Force)); + + createTestWindow(); + + // The window should be excluded from window switching effects. + QVERIFY(m_window->skipSwitcher()); + + // Any attempt to change the skip-switcher state should not succeed. + m_window->setSkipSwitcher(false); + QVERIFY(m_window->skipSwitcher()); + + // Reopen the window. + destroyTestWindow(); + createTestWindow(); + + // The skip-switcher state should be still forced. + QVERIFY(m_window->skipSwitcher()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSkipSwitcherApplyNow() +{ + createTestWindow(); + QVERIFY(!m_window->skipSwitcher()); + + setWindowRule("skipswitcher", true, int(Rules::ApplyNow)); + + // The window should be excluded from window switching effects now. + QVERIFY(m_window->skipSwitcher()); + + // Also, one change the skip-switcher state. + m_window->setSkipSwitcher(false); + QVERIFY(!m_window->skipSwitcher()); + + // The rule should not be applied again. + m_window->evaluateWindowRules(); + QVERIFY(!m_window->skipSwitcher()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testSkipSwitcherForceTemporarily() +{ + setWindowRule("skipswitcher", true, int(Rules::ForceTemporarily)); + + createTestWindow(); + + // The window should be excluded from window switching effects. + QVERIFY(m_window->skipSwitcher()); + + // Any attempt to change the skip-switcher state should not succeed. + m_window->setSkipSwitcher(false); + QVERIFY(m_window->skipSwitcher()); + + // The rule should be discarded when the window is closed. + destroyTestWindow(); + createTestWindow(); + QVERIFY(!m_window->skipSwitcher()); + + // The skip-switcher state is no longer forced. + m_window->setSkipSwitcher(true); + QVERIFY(m_window->skipSwitcher()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testKeepAboveDontAffect() +{ + setWindowRule("above", true, int(Rules::DontAffect)); + + createTestWindow(); + + // The keep-above state of the window should not be affected by the rule. + QVERIFY(!m_window->keepAbove()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testKeepAboveApply() +{ + setWindowRule("above", true, int(Rules::Apply)); + + createTestWindow(); + + // Initially, the window should be kept above. + QVERIFY(m_window->keepAbove()); + + // One should also be able to alter the keep-above state. + m_window->setKeepAbove(false); + QVERIFY(!m_window->keepAbove()); + + // If one re-opens the window, it should be kept above back again. + destroyTestWindow(); + createTestWindow(); + QVERIFY(m_window->keepAbove()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testKeepAboveRemember() +{ + setWindowRule("above", true, int(Rules::Remember)); + + createTestWindow(); + + // Initially, the window should be kept above. + QVERIFY(m_window->keepAbove()); + + // Unset the keep-above state. + m_window->setKeepAbove(false); + QVERIFY(!m_window->keepAbove()); + destroyTestWindow(); + + // Re-open the window, it should not be kept above. + createTestWindow(); + QVERIFY(!m_window->keepAbove()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testKeepAboveForce() +{ + setWindowRule("above", true, int(Rules::Force)); + + createTestWindow(); + + // Initially, the window should be kept above. + QVERIFY(m_window->keepAbove()); + + // Any attemt to unset the keep-above should not succeed. + m_window->setKeepAbove(false); + QVERIFY(m_window->keepAbove()); + + // If we re-open the window, it should still be kept above. + destroyTestWindow(); + createTestWindow(); + QVERIFY(m_window->keepAbove()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testKeepAboveApplyNow() +{ + createTestWindow(); + QVERIFY(!m_window->keepAbove()); + + setWindowRule("above", true, int(Rules::ApplyNow)); + + // The window should now be kept above other windows. + QVERIFY(m_window->keepAbove()); + + // One is still able to change the keep-above state of the window. + m_window->setKeepAbove(false); + QVERIFY(!m_window->keepAbove()); + + // The rule should not be applied again. + m_window->evaluateWindowRules(); + QVERIFY(!m_window->keepAbove()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testKeepAboveForceTemporarily() +{ + setWindowRule("above", true, int(Rules::ForceTemporarily)); + + createTestWindow(); + + // Initially, the window should be kept above. + QVERIFY(m_window->keepAbove()); + + // Any attempt to alter the keep-above state should not succeed. + m_window->setKeepAbove(false); + QVERIFY(m_window->keepAbove()); + + // The rule should be discarded when the window is closed. + destroyTestWindow(); + createTestWindow(); + QVERIFY(!m_window->keepAbove()); + + // The keep-above state is no longer forced. + m_window->setKeepAbove(true); + QVERIFY(m_window->keepAbove()); + m_window->setKeepAbove(false); + QVERIFY(!m_window->keepAbove()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testKeepBelowDontAffect() +{ + setWindowRule("below", true, int(Rules::DontAffect)); + + createTestWindow(); + + // The keep-below state of the window should not be affected by the rule. + QVERIFY(!m_window->keepBelow()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testKeepBelowApply() +{ + setWindowRule("below", true, int(Rules::Apply)); + + createTestWindow(); + + // Initially, the window should be kept below. + QVERIFY(m_window->keepBelow()); + + // One should also be able to alter the keep-below state. + m_window->setKeepBelow(false); + QVERIFY(!m_window->keepBelow()); + + // If one re-opens the window, it should be kept above back again. + destroyTestWindow(); + createTestWindow(); + QVERIFY(m_window->keepBelow()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testKeepBelowRemember() +{ + setWindowRule("below", true, int(Rules::Remember)); + + createTestWindow(); + + // Initially, the window should be kept below. + QVERIFY(m_window->keepBelow()); + + // Unset the keep-below state. + m_window->setKeepBelow(false); + QVERIFY(!m_window->keepBelow()); + destroyTestWindow(); + + // Re-open the window, it should not be kept below. + createTestWindow(); + QVERIFY(!m_window->keepBelow()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testKeepBelowForce() +{ + setWindowRule("below", true, int(Rules::Force)); + + createTestWindow(); + + // Initially, the window should be kept below. + QVERIFY(m_window->keepBelow()); + + // Any attemt to unset the keep-below should not succeed. + m_window->setKeepBelow(false); + QVERIFY(m_window->keepBelow()); + + // If we re-open the window, it should still be kept below. + destroyTestWindow(); + createTestWindow(); + QVERIFY(m_window->keepBelow()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testKeepBelowApplyNow() +{ + createTestWindow(); + QVERIFY(!m_window->keepBelow()); + + setWindowRule("below", true, int(Rules::ApplyNow)); + + // The window should now be kept below other windows. + QVERIFY(m_window->keepBelow()); + + // One is still able to change the keep-below state of the window. + m_window->setKeepBelow(false); + QVERIFY(!m_window->keepBelow()); + + // The rule should not be applied again. + m_window->evaluateWindowRules(); + QVERIFY(!m_window->keepBelow()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testKeepBelowForceTemporarily() +{ + setWindowRule("below", true, int(Rules::ForceTemporarily)); + + createTestWindow(); + + // Initially, the window should be kept below. + QVERIFY(m_window->keepBelow()); + + // Any attempt to alter the keep-below state should not succeed. + m_window->setKeepBelow(false); + QVERIFY(m_window->keepBelow()); + + // The rule should be discarded when the window is closed. + destroyTestWindow(); + createTestWindow(); + QVERIFY(!m_window->keepBelow()); + + // The keep-below state is no longer forced. + m_window->setKeepBelow(true); + QVERIFY(m_window->keepBelow()); + m_window->setKeepBelow(false); + QVERIFY(!m_window->keepBelow()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testShortcutDontAffect() +{ + setWindowRule("shortcut", "Ctrl+Alt+1", int(Rules::DontAffect)); + + createTestWindow(); + QCOMPARE(m_window->shortcut(), QKeySequence()); + m_window->minimize(); + QVERIFY(m_window->isMinimized()); + + // If we press the window shortcut, nothing should happen. + QSignalSpy clientUnminimizedSpy(m_window, &Window::clientUnminimized); + quint32 timestamp = 1; + Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); + Test::keyboardKeyPressed(KEY_1, timestamp++); + Test::keyboardKeyReleased(KEY_1, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(!clientUnminimizedSpy.wait(100)); + QVERIFY(m_window->isMinimized()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testShortcutApply() +{ + setWindowRule("shortcut", "Ctrl+Alt+1", int(Rules::Apply)); + + createTestWindow(); + + // If we press the window shortcut, the window should be brought back to user. + QSignalSpy clientUnminimizedSpy(m_window, &Window::clientUnminimized); + quint32 timestamp = 1; + QCOMPARE(m_window->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); + m_window->minimize(); + QVERIFY(m_window->isMinimized()); + Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); + Test::keyboardKeyPressed(KEY_1, timestamp++); + Test::keyboardKeyReleased(KEY_1, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(clientUnminimizedSpy.wait()); + QVERIFY(!m_window->isMinimized()); + + // One can also change the shortcut. + m_window->setShortcut(QStringLiteral("Ctrl+Alt+2")); + QCOMPARE(m_window->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_2})); + m_window->minimize(); + QVERIFY(m_window->isMinimized()); + Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); + Test::keyboardKeyPressed(KEY_2, timestamp++); + Test::keyboardKeyReleased(KEY_2, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(clientUnminimizedSpy.wait()); + QVERIFY(!m_window->isMinimized()); + + // The old shortcut should do nothing. + m_window->minimize(); + QVERIFY(m_window->isMinimized()); + Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); + Test::keyboardKeyPressed(KEY_1, timestamp++); + Test::keyboardKeyReleased(KEY_1, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(!clientUnminimizedSpy.wait(100)); + QVERIFY(m_window->isMinimized()); + + // Reopen the window. + destroyTestWindow(); + createTestWindow(); + + // The window shortcut should be set back to Ctrl+Alt+1. + QCOMPARE(m_window->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testShortcutRemember() +{ + QSKIP("KWin core doesn't try to save the last used window shortcut"); + + setWindowRule("shortcut", "Ctrl+Alt+1", int(Rules::Remember)); + + createTestWindow(); + + // If we press the window shortcut, the window should be brought back to user. + QSignalSpy clientUnminimizedSpy(m_window, &Window::clientUnminimized); + quint32 timestamp = 1; + QCOMPARE(m_window->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); + m_window->minimize(); + QVERIFY(m_window->isMinimized()); + Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); + Test::keyboardKeyPressed(KEY_1, timestamp++); + Test::keyboardKeyReleased(KEY_1, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(clientUnminimizedSpy.wait()); + QVERIFY(!m_window->isMinimized()); + + // Change the window shortcut to Ctrl+Alt+2. + m_window->setShortcut(QStringLiteral("Ctrl+Alt+2")); + QCOMPARE(m_window->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_2})); + m_window->minimize(); + QVERIFY(m_window->isMinimized()); + Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); + Test::keyboardKeyPressed(KEY_2, timestamp++); + Test::keyboardKeyReleased(KEY_2, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(clientUnminimizedSpy.wait()); + QVERIFY(!m_window->isMinimized()); + + // Reopen the window. + destroyTestWindow(); + createTestWindow(); + + // The window shortcut should be set to the last known value. + QCOMPARE(m_window->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_2})); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testShortcutForce() +{ + QSKIP("KWin core can't release forced window shortcuts"); + + setWindowRule("shortcut", "Ctrl+Alt+1", int(Rules::Force)); + + createTestWindow(); + + // If we press the window shortcut, the window should be brought back to user. + QSignalSpy clientUnminimizedSpy(m_window, &Window::clientUnminimized); + quint32 timestamp = 1; + QCOMPARE(m_window->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); + m_window->minimize(); + QVERIFY(m_window->isMinimized()); + Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); + Test::keyboardKeyPressed(KEY_1, timestamp++); + Test::keyboardKeyReleased(KEY_1, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(clientUnminimizedSpy.wait()); + QVERIFY(!m_window->isMinimized()); + + // Any attempt to change the window shortcut should not succeed. + m_window->setShortcut(QStringLiteral("Ctrl+Alt+2")); + QCOMPARE(m_window->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); + m_window->minimize(); + QVERIFY(m_window->isMinimized()); + Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); + Test::keyboardKeyPressed(KEY_2, timestamp++); + Test::keyboardKeyReleased(KEY_2, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(!clientUnminimizedSpy.wait(100)); + QVERIFY(m_window->isMinimized()); + + // Reopen the window. + destroyTestWindow(); + createTestWindow(); + + // The window shortcut should still be forced. + QCOMPARE(m_window->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testShortcutApplyNow() +{ + createTestWindow(); + QVERIFY(m_window->shortcut().isEmpty()); + + setWindowRule("shortcut", "Ctrl+Alt+1", int(Rules::ApplyNow)); + + // The window should now have a window shortcut assigned. + QCOMPARE(m_window->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); + QSignalSpy clientUnminimizedSpy(m_window, &Window::clientUnminimized); + quint32 timestamp = 1; + m_window->minimize(); + QVERIFY(m_window->isMinimized()); + Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); + Test::keyboardKeyPressed(KEY_1, timestamp++); + Test::keyboardKeyReleased(KEY_1, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(clientUnminimizedSpy.wait()); + QVERIFY(!m_window->isMinimized()); + + // Assign a different shortcut. + m_window->setShortcut(QStringLiteral("Ctrl+Alt+2")); + QCOMPARE(m_window->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_2})); + m_window->minimize(); + QVERIFY(m_window->isMinimized()); + Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); + Test::keyboardKeyPressed(KEY_2, timestamp++); + Test::keyboardKeyReleased(KEY_2, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(clientUnminimizedSpy.wait()); + QVERIFY(!m_window->isMinimized()); + + // The rule should not be applied again. + m_window->evaluateWindowRules(); + QCOMPARE(m_window->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_2})); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testShortcutForceTemporarily() +{ + QSKIP("KWin core can't release forced window shortcuts"); + + setWindowRule("shortcut", "Ctrl+Alt+1", int(Rules::ForceTemporarily)); + + createTestWindow(); + + // If we press the window shortcut, the window should be brought back to user. + QSignalSpy clientUnminimizedSpy(m_window, &Window::clientUnminimized); + quint32 timestamp = 1; + QCOMPARE(m_window->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); + m_window->minimize(); + QVERIFY(m_window->isMinimized()); + Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); + Test::keyboardKeyPressed(KEY_1, timestamp++); + Test::keyboardKeyReleased(KEY_1, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(clientUnminimizedSpy.wait()); + QVERIFY(!m_window->isMinimized()); + + // Any attempt to change the window shortcut should not succeed. + m_window->setShortcut(QStringLiteral("Ctrl+Alt+2")); + QCOMPARE(m_window->shortcut(), (QKeySequence{Qt::CTRL | Qt::ALT | Qt::Key_1})); + m_window->minimize(); + QVERIFY(m_window->isMinimized()); + Test::keyboardKeyPressed(KEY_LEFTCTRL, timestamp++); + Test::keyboardKeyPressed(KEY_LEFTALT, timestamp++); + Test::keyboardKeyPressed(KEY_2, timestamp++); + Test::keyboardKeyReleased(KEY_2, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTALT, timestamp++); + Test::keyboardKeyReleased(KEY_LEFTCTRL, timestamp++); + QVERIFY(!clientUnminimizedSpy.wait(100)); + QVERIFY(m_window->isMinimized()); + + // The rule should be discarded when the window is closed. + destroyTestWindow(); + createTestWindow(); + QVERIFY(m_window->shortcut().isEmpty()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testDesktopFileDontAffect() +{ + // Currently, the desktop file name is derived from the app id. If the app id is + // changed, then the old rules will be lost. Either setDesktopFileName should + // be exposed or the desktop file name rule should be removed for wayland windows. + QSKIP("Needs changes in KWin core to pass"); +} + +void TestXdgShellWindowRules::testDesktopFileApply() +{ + // Currently, the desktop file name is derived from the app id. If the app id is + // changed, then the old rules will be lost. Either setDesktopFileName should + // be exposed or the desktop file name rule should be removed for wayland windows. + QSKIP("Needs changes in KWin core to pass"); +} + +void TestXdgShellWindowRules::testDesktopFileRemember() +{ + // Currently, the desktop file name is derived from the app id. If the app id is + // changed, then the old rules will be lost. Either setDesktopFileName should + // be exposed or the desktop file name rule should be removed for wayland windows. + QSKIP("Needs changes in KWin core to pass"); +} + +void TestXdgShellWindowRules::testDesktopFileForce() +{ + // Currently, the desktop file name is derived from the app id. If the app id is + // changed, then the old rules will be lost. Either setDesktopFileName should + // be exposed or the desktop file name rule should be removed for wayland windows. + QSKIP("Needs changes in KWin core to pass"); +} + +void TestXdgShellWindowRules::testDesktopFileApplyNow() +{ + // Currently, the desktop file name is derived from the app id. If the app id is + // changed, then the old rules will be lost. Either setDesktopFileName should + // be exposed or the desktop file name rule should be removed for wayland windows. + QSKIP("Needs changes in KWin core to pass"); +} + +void TestXdgShellWindowRules::testDesktopFileForceTemporarily() +{ + // Currently, the desktop file name is derived from the app id. If the app id is + // changed, then the old rules will be lost. Either setDesktopFileName should + // be exposed or the desktop file name rule should be removed for wayland windows. + QSKIP("Needs changes in KWin core to pass"); +} + +void TestXdgShellWindowRules::testActiveOpacityDontAffect() +{ + setWindowRule("opacityactive", 90, int(Rules::DontAffect)); + + createTestWindow(); + QVERIFY(m_window->isActive()); + + // The opacity should not be affected by the rule. + QCOMPARE(m_window->opacity(), 1.0); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testActiveOpacityForce() +{ + setWindowRule("opacityactive", 90, int(Rules::Force)); + + createTestWindow(); + QVERIFY(m_window->isActive()); + QCOMPARE(m_window->opacity(), 0.9); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testActiveOpacityForceTemporarily() +{ + setWindowRule("opacityactive", 90, int(Rules::ForceTemporarily)); + + createTestWindow(); + QVERIFY(m_window->isActive()); + QCOMPARE(m_window->opacity(), 0.9); + + // The rule should be discarded when the window is closed. + destroyTestWindow(); + createTestWindow(); + QVERIFY(m_window->isActive()); + QCOMPARE(m_window->opacity(), 1.0); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testInactiveOpacityDontAffect() +{ + setWindowRule("opacityinactive", 80, int(Rules::DontAffect)); + + createTestWindow(); + QVERIFY(m_window->isActive()); + + // Make the window inactive. + workspace()->setActiveWindow(nullptr); + QVERIFY(!m_window->isActive()); + + // The opacity of the window should not be affected by the rule. + QCOMPARE(m_window->opacity(), 1.0); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testInactiveOpacityForce() +{ + setWindowRule("opacityinactive", 80, int(Rules::Force)); + + createTestWindow(); + QVERIFY(m_window->isActive()); + QCOMPARE(m_window->opacity(), 1.0); + + // Make the window inactive. + workspace()->setActiveWindow(nullptr); + QVERIFY(!m_window->isActive()); + + // The opacity should be forced by the rule. + QCOMPARE(m_window->opacity(), 0.8); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testInactiveOpacityForceTemporarily() +{ + setWindowRule("opacityinactive", 80, int(Rules::ForceTemporarily)); + + createTestWindow(); + QVERIFY(m_window->isActive()); + QCOMPARE(m_window->opacity(), 1.0); + + // Make the window inactive. + workspace()->setActiveWindow(nullptr); + QVERIFY(!m_window->isActive()); + + // The opacity should be forced by the rule. + QCOMPARE(m_window->opacity(), 0.8); + + // The rule should be discarded when the window is closed. + destroyTestWindow(); + createTestWindow(); + + QVERIFY(m_window->isActive()); + QCOMPARE(m_window->opacity(), 1.0); + workspace()->setActiveWindow(nullptr); + QVERIFY(!m_window->isActive()); + QCOMPARE(m_window->opacity(), 1.0); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testNoBorderDontAffect() +{ + setWindowRule("noborder", true, int(Rules::DontAffect)); + createTestWindow(ServerSideDecoration); + + // The window should not be affected by the rule. + QVERIFY(!m_window->noBorder()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testNoBorderApply() +{ + setWindowRule("noborder", true, int(Rules::Apply)); + createTestWindow(ServerSideDecoration); + + // Initially, the window should not be decorated. + QVERIFY(m_window->noBorder()); + QVERIFY(!m_window->isDecorated()); + + // But you should be able to change "no border" property afterwards. + QVERIFY(m_window->userCanSetNoBorder()); + m_window->setNoBorder(false); + QVERIFY(!m_window->noBorder()); + + // If one re-opens the window, it should have no border again. + destroyTestWindow(); + createTestWindow(ServerSideDecoration); + QVERIFY(m_window->noBorder()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testNoBorderRemember() +{ + setWindowRule("noborder", true, int(Rules::Remember)); + createTestWindow(ServerSideDecoration); + + // Initially, the window should not be decorated. + QVERIFY(m_window->noBorder()); + QVERIFY(!m_window->isDecorated()); + + // Unset the "no border" property. + QVERIFY(m_window->userCanSetNoBorder()); + m_window->setNoBorder(false); + QVERIFY(!m_window->noBorder()); + + // Re-open the window, it should be decorated. + destroyTestWindow(); + createTestWindow(ServerSideDecoration); + QVERIFY(m_window->isDecorated()); + QVERIFY(!m_window->noBorder()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testNoBorderForce() +{ + setWindowRule("noborder", true, int(Rules::Force)); + createTestWindow(ServerSideDecoration); + + // The window should not be decorated. + QVERIFY(m_window->noBorder()); + QVERIFY(!m_window->isDecorated()); + + // And the user should not be able to change the "no border" property. + m_window->setNoBorder(false); + QVERIFY(m_window->noBorder()); + + // Reopen the window. + destroyTestWindow(); + createTestWindow(ServerSideDecoration); + + // The "no border" property should be still forced. + QVERIFY(m_window->noBorder()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testNoBorderApplyNow() +{ + createTestWindow(ServerSideDecoration); + QVERIFY(!m_window->noBorder()); + + // Initialize RuleBook with the test rule. + setWindowRule("noborder", true, int(Rules::ApplyNow)); + + // The "no border" property should be set now. + QVERIFY(m_window->noBorder()); + + // One should be still able to change the "no border" property. + m_window->setNoBorder(false); + QVERIFY(!m_window->noBorder()); + + // The rule should not be applied again. + m_window->evaluateWindowRules(); + QVERIFY(!m_window->noBorder()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testNoBorderForceTemporarily() +{ + setWindowRule("noborder", true, int(Rules::ForceTemporarily)); + createTestWindow(ServerSideDecoration); + + // The "no border" property should be set. + QVERIFY(m_window->noBorder()); + + // And you should not be able to change it. + m_window->setNoBorder(false); + QVERIFY(m_window->noBorder()); + + // The rule should be discarded when the window is closed. + destroyTestWindow(); + createTestWindow(ServerSideDecoration); + QVERIFY(!m_window->noBorder()); + + // The "no border" property is no longer forced. + m_window->setNoBorder(true); + QVERIFY(m_window->noBorder()); + m_window->setNoBorder(false); + QVERIFY(!m_window->noBorder()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testScreenDontAffect() +{ + const QList outputs = workspace()->outputs(); + + setWindowRule("screen", int(1), int(Rules::DontAffect)); + + createTestWindow(); + + // The window should not be affected by the rule. + QCOMPARE(m_window->output()->name(), outputs.at(0)->name()); + + // The user can still move the window to another screen. + workspace()->sendWindowToOutput(m_window, outputs.at(1)); + QCOMPARE(m_window->output()->name(), outputs.at(1)->name()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testScreenApply() +{ + const QList outputs = workspace()->outputs(); + + setWindowRule("screen", int(1), int(Rules::Apply)); + + createTestWindow(); + + // The window should be in the screen specified by the rule. + QEXPECT_FAIL("", "Applying a screen rule on a new client fails on Wayland", Continue); + QCOMPARE(m_window->output()->name(), outputs.at(1)->name()); + + // The user can move the window to another screen. + workspace()->sendWindowToOutput(m_window, outputs.at(0)); + QCOMPARE(m_window->output()->name(), outputs.at(0)->name()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testScreenRemember() +{ + const QList outputs = workspace()->outputs(); + + setWindowRule("screen", int(1), int(Rules::Remember)); + + createTestWindow(); + + // Initially, the window should be in the first screen + QCOMPARE(m_window->output()->name(), outputs.at(0)->name()); + + // Move the window to the second screen. + workspace()->sendWindowToOutput(m_window, outputs.at(1)); + QCOMPARE(m_window->output()->name(), outputs.at(1)->name()); + + // Close and reopen the window. + destroyTestWindow(); + createTestWindow(); + + QEXPECT_FAIL("", "Applying a screen rule on a new client fails on Wayland", Continue); + QCOMPARE(m_window->output()->name(), outputs.at(1)->name()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testScreenForce() +{ + const QList outputs = workspace()->outputs(); + + createTestWindow(); + QVERIFY(m_window->isActive()); + + setWindowRule("screen", int(1), int(Rules::Force)); + + // The window should be forced to the screen specified by the rule. + QCOMPARE(m_window->output()->name(), outputs.at(1)->name()); + + // User should not be able to move the window to another screen. + workspace()->sendWindowToOutput(m_window, outputs.at(0)); + QCOMPARE(m_window->output()->name(), outputs.at(1)->name()); + + // Disable the output where the window is on, so the window is moved the other screen + OutputConfiguration config; + auto changeSet = config.changeSet(outputs.at(1)); + changeSet->enabled = false; + workspace()->applyOutputConfiguration(config); + + QVERIFY(!outputs.at(1)->isEnabled()); + QCOMPARE(m_window->output()->name(), outputs.at(0)->name()); + + // Enable the output and check that the window is moved there again + changeSet->enabled = true; + workspace()->applyOutputConfiguration(config); + + QVERIFY(outputs.at(1)->isEnabled()); + QCOMPARE(m_window->output()->name(), outputs.at(1)->name()); + + // Close and reopen the window. + destroyTestWindow(); + createTestWindow(); + + QEXPECT_FAIL("", "Applying a screen rule on a new client fails on Wayland", Continue); + QCOMPARE(m_window->output()->name(), outputs.at(1)->name()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testScreenApplyNow() +{ + const QList outputs = workspace()->outputs(); + + createTestWindow(); + + QCOMPARE(m_window->output()->name(), outputs.at(0)->name()); + + // Set the rule so the window should move to the screen specified by the rule. + setWindowRule("screen", int(1), int(Rules::ApplyNow)); + QCOMPARE(m_window->output()->name(), outputs.at(1)->name()); + + // The user can move the window to another screen. + workspace()->sendWindowToOutput(m_window, outputs.at(0)); + QCOMPARE(m_window->output()->name(), outputs.at(0)->name()); + + // The rule should not be applied again. + m_window->evaluateWindowRules(); + QCOMPARE(m_window->output()->name(), outputs.at(0)->name()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testScreenForceTemporarily() +{ + const QList outputs = workspace()->outputs(); + + createTestWindow(); + + setWindowRule("screen", int(1), int(Rules::ForceTemporarily)); + + // The window should be forced the second screen + QCOMPARE(m_window->output()->name(), outputs.at(1)->name()); + + // User is not allowed to move it + workspace()->sendWindowToOutput(m_window, outputs.at(0)); + QCOMPARE(m_window->output()->name(), outputs.at(1)->name()); + + // Close and reopen the window. + destroyTestWindow(); + createTestWindow(); + + // The rule should be discarded now + QCOMPARE(m_window->output()->name(), outputs.at(0)->name()); + + destroyTestWindow(); +} + +void TestXdgShellWindowRules::testMatchAfterNameChange() +{ + setWindowRule("above", true, int(Rules::Force)); + + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(window->isActive()); + QCOMPARE(window->keepAbove(), false); + + QSignalSpy desktopFileNameSpy(window, &Window::desktopFileNameChanged); + + shellSurface->set_app_id(QStringLiteral("org.kde.foo")); + QVERIFY(desktopFileNameSpy.wait()); + QCOMPARE(window->keepAbove(), true); +} + +WAYLANDTEST_MAIN(TestXdgShellWindowRules) +#include "xdgshellwindow_rules_test.moc" diff --git a/autotests/integration/xdgshellwindow_test.cpp b/autotests/integration/xdgshellwindow_test.cpp new file mode 100644 index 0000000..ea5b543 --- /dev/null +++ b/autotests/integration/xdgshellwindow_test.cpp @@ -0,0 +1,1935 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + SPDX-FileCopyrightText: 2019 David Edmundson + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "decorations/decorationbridge.h" +#include "decorations/settings.h" +#include "deleted.h" +#include "effects.h" +#include "virtualdesktops.h" +#include "wayland/clientconnection.h" +#include "wayland/display.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// system +#include +#include +#include + +#include + +using namespace KWin; +using namespace KWayland::Client; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_xdgshellwindow-0"); + +class TestXdgShellWindow : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + + void testMapUnmap(); + void testDesktopPresenceChanged(); + void testWindowOutputs(); + void testMinimizeActiveWindow(); + void testFullscreen_data(); + void testFullscreen(); + void testUserCanSetFullscreen(); + void testSendFullScreenWindowToAnotherOutput(); + + void testMaximizeHorizontal(); + void testMaximizeVertical(); + void testMaximizeFull(); + void testMaximizedToFullscreen_data(); + void testMaximizedToFullscreen(); + void testSendMaximizedWindowToAnotherOutput(); + void testFullscreenMultipleOutputs(); + void testHidden(); + void testDesktopFileName(); + void testCaptionSimplified(); + void testCaptionMultipleWindows(); + void testUnresponsiveWindow_data(); + void testUnresponsiveWindow(); + void testAppMenu(); + void testSendClientWithTransientToDesktop(); + void testMinimizeWindowWithTransients(); + void testXdgDecoration_data(); + void testXdgDecoration(); + void testXdgNeverCommitted(); + void testXdgInitialState(); + void testXdgInitiallyMaximised(); + void testXdgInitiallyFullscreen(); + void testXdgInitiallyMinimized(); + void testXdgWindowGeometryIsntSet(); + void testXdgWindowGeometryAttachBuffer(); + void testXdgWindowGeometryAttachSubSurface(); + void testXdgWindowGeometryInteractiveResize(); + void testXdgWindowGeometryFullScreen(); + void testXdgWindowGeometryMaximize(); + void testXdgWindowReactive(); + void testXdgWindowRepositioning(); + void testPointerInputTransform(); + void testReentrantSetFrameGeometry(); + void testDoubleMaximize(); + void testDoubleFullscreenSeparatedByCommit(); + void testMaximizeAndChangeDecorationModeAfterInitialCommit(); + void testFullScreenAndChangeDecorationModeAfterInitialCommit(); + void testChangeDecorationModeAfterInitialCommit(); +}; + +void TestXdgShellWindow::testXdgWindowReactive() +{ + std::unique_ptr positioner(Test::createXdgPositioner()); + positioner->set_size(10, 10); + positioner->set_anchor_rect(10, 10, 10, 10); + positioner->set_reactive(); + + std::unique_ptr rootSurface(Test::createSurface()); + std::unique_ptr root(Test::createXdgToplevelSurface(rootSurface.get())); + auto rootWindow = Test::renderAndWaitForShown(rootSurface.get(), QSize(100, 100), Qt::cyan); + QVERIFY(rootWindow); + + std::unique_ptr childSurface(Test::createSurface()); + std::unique_ptr popup(Test::createXdgPopupSurface(childSurface.get(), root->xdgSurface(), positioner.get())); + auto childWindow = Test::renderAndWaitForShown(childSurface.get(), QSize(10, 10), Qt::cyan); + QVERIFY(childWindow); + + QSignalSpy popupConfigureRequested(popup.get(), &Test::XdgPopup::configureRequested); + rootWindow->move(rootWindow->pos() + QPoint(20, 20)); + + QVERIFY(popupConfigureRequested.wait()); + QCOMPARE(popupConfigureRequested.count(), 1); +} + +void TestXdgShellWindow::testXdgWindowRepositioning() +{ + std::unique_ptr positioner(Test::createXdgPositioner()); + positioner->set_size(10, 10); + positioner->set_anchor_rect(10, 10, 10, 10); + + std::unique_ptr otherPositioner(Test::createXdgPositioner()); + otherPositioner->set_size(50, 50); + otherPositioner->set_anchor_rect(10, 10, 10, 10); + + std::unique_ptr rootSurface(Test::createSurface()); + std::unique_ptr root(Test::createXdgToplevelSurface(rootSurface.get())); + auto rootWindow = Test::renderAndWaitForShown(rootSurface.get(), QSize(100, 100), Qt::cyan); + QVERIFY(rootWindow); + + std::unique_ptr childSurface(Test::createSurface()); + std::unique_ptr popup(Test::createXdgPopupSurface(childSurface.get(), root->xdgSurface(), positioner.get())); + auto childWindow = Test::renderAndWaitForShown(childSurface.get(), QSize(10, 10), Qt::cyan); + QVERIFY(childWindow); + + QSignalSpy reconfigureSpy(popup.get(), &Test::XdgPopup::configureRequested); + + popup->reposition(otherPositioner->object(), 500000); + + QVERIFY(reconfigureSpy.wait()); + QCOMPARE(reconfigureSpy.count(), 1); +} + +void TestXdgShellWindow::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); +} + +void TestXdgShellWindow::init() +{ + QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::XdgDecorationV1 | Test::AdditionalWaylandInterface::AppMenu)); + QVERIFY(Test::waitForWaylandPointer()); + + workspace()->setActiveOutput(QPoint(640, 512)); + // put mouse in the middle of screen one + KWin::Cursors::self()->mouse()->setPos(QPoint(640, 512)); +} + +void TestXdgShellWindow::cleanup() +{ + Test::destroyWaylandConnection(); +} + +void TestXdgShellWindow::testMapUnmap() +{ + // This test verifies that the compositor destroys XdgToplevelWindow when the + // associated xdg_toplevel surface is unmapped. + + // Create a wl_surface and an xdg_toplevel, but don't commit them yet! + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + + QSignalSpy configureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + + // Tell the compositor that we want to map the surface. + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // The compositor will respond with a configure event. + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 1); + + // Now we can attach a buffer with actual data to the surface. + Test::render(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(windowAddedSpy.wait()); + QCOMPARE(windowAddedSpy.count(), 1); + Window *window = windowAddedSpy.last().first().value(); + QVERIFY(window); + QCOMPARE(window->readyForPainting(), true); + + // When the window becomes active, the compositor will send another configure event. + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 2); + + // Unmap the xdg_toplevel surface by committing a null buffer. + surface->attachBuffer(Buffer::Ptr()); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(Test::waitForWindowDestroyed(window)); + + // Tell the compositor that we want to re-map the xdg_toplevel surface. + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // The compositor will respond with a configure event. + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 3); + + // Now we can attach a buffer with actual data to the surface. + Test::render(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(windowAddedSpy.wait()); + QCOMPARE(windowAddedSpy.count(), 2); + window = windowAddedSpy.last().first().value(); + QVERIFY(window); + QCOMPARE(window->readyForPainting(), true); + + // The compositor will respond with a configure event. + QVERIFY(configureRequestedSpy.wait()); + QCOMPARE(configureRequestedSpy.count(), 4); + + // Destroy the test window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void TestXdgShellWindow::testDesktopPresenceChanged() +{ + // this test verifies that the desktop presence changed signals are properly emitted + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QCOMPARE(window->desktop(), 1); + effects->setNumberOfDesktops(4); + QSignalSpy desktopPresenceChangedClientSpy(window, &Window::desktopPresenceChanged); + QSignalSpy desktopPresenceChangedWorkspaceSpy(workspace(), &Workspace::desktopPresenceChanged); + QSignalSpy desktopPresenceChangedEffectsSpy(effects, &EffectsHandler::desktopPresenceChanged); + + // let's change the desktop + workspace()->sendWindowToDesktop(window, 2, false); + QCOMPARE(window->desktop(), 2); + QCOMPARE(desktopPresenceChangedClientSpy.count(), 1); + QCOMPARE(desktopPresenceChangedWorkspaceSpy.count(), 1); + QCOMPARE(desktopPresenceChangedEffectsSpy.count(), 1); + + // verify the arguments + QCOMPARE(desktopPresenceChangedClientSpy.first().at(0).value(), window); + QCOMPARE(desktopPresenceChangedClientSpy.first().at(1).toInt(), 1); + QCOMPARE(desktopPresenceChangedWorkspaceSpy.first().at(0).value(), window); + QCOMPARE(desktopPresenceChangedWorkspaceSpy.first().at(1).toInt(), 1); + QCOMPARE(desktopPresenceChangedEffectsSpy.first().at(0).value(), window->effectWindow()); + QCOMPARE(desktopPresenceChangedEffectsSpy.first().at(1).toInt(), 1); + QCOMPARE(desktopPresenceChangedEffectsSpy.first().at(2).toInt(), 2); +} + +void TestXdgShellWindow::testWindowOutputs() +{ + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto size = QSize(200, 200); + + QSignalSpy outputEnteredSpy(surface.get(), &KWayland::Client::Surface::outputEntered); + QSignalSpy outputLeftSpy(surface.get(), &KWayland::Client::Surface::outputLeft); + + auto window = Test::renderAndWaitForShown(surface.get(), size, Qt::blue); + // assumption: window is initially placed on first screen + QVERIFY(outputEnteredSpy.wait()); + QCOMPARE(outputEnteredSpy.count(), 1); + QCOMPARE(surface->outputs().count(), 1); + QCOMPARE(surface->outputs().first()->globalPosition(), QPoint(0, 0)); + + // move to overlapping both first and second screen + window->moveResize(QRect(QPoint(1250, 100), size)); + QVERIFY(outputEnteredSpy.wait()); + QCOMPARE(outputEnteredSpy.count(), 2); + QCOMPARE(outputLeftSpy.count(), 0); + QCOMPARE(surface->outputs().count(), 2); + QVERIFY(surface->outputs()[0] != surface->outputs()[1]); + + // move entirely into second screen + window->moveResize(QRect(QPoint(1400, 100), size)); + QVERIFY(outputLeftSpy.wait()); + QCOMPARE(outputEnteredSpy.count(), 2); + QCOMPARE(outputLeftSpy.count(), 1); + QCOMPARE(surface->outputs().count(), 1); + QCOMPARE(surface->outputs().first()->globalPosition(), QPoint(1280, 0)); +} + +void TestXdgShellWindow::testMinimizeActiveWindow() +{ + // this test verifies that when minimizing the active window it gets deactivated + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(window->isActive()); + QCOMPARE(workspace()->activeWindow(), window); + QVERIFY(window->wantsInput()); + QVERIFY(window->wantsTabFocus()); + QVERIFY(window->isShown()); + + workspace()->slotWindowMinimize(); + QVERIFY(!window->isShown()); + QVERIFY(window->wantsInput()); + QVERIFY(window->wantsTabFocus()); + QVERIFY(!window->isActive()); + QVERIFY(!workspace()->activeWindow()); + QVERIFY(window->isMinimized()); + + // unminimize again + window->unminimize(); + QVERIFY(!window->isMinimized()); + QVERIFY(!window->isActive()); + QVERIFY(window->wantsInput()); + QVERIFY(window->wantsTabFocus()); + QVERIFY(window->isShown()); + QCOMPARE(workspace()->activeWindow(), nullptr); +} + +void TestXdgShellWindow::testFullscreen_data() +{ + QTest::addColumn("decoMode"); + + QTest::newRow("client-side deco") << Test::XdgToplevelDecorationV1::mode_client_side; + QTest::newRow("server-side deco") << Test::XdgToplevelDecorationV1::mode_server_side; +} + +void TestXdgShellWindow::testFullscreen() +{ + // this test verifies that a window can be properly fullscreened + + Test::XdgToplevel::States states; + + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + std::unique_ptr decoration(Test::createXdgToplevelDecorationV1(shellSurface.get())); + QSignalSpy decorationConfigureRequestedSpy(decoration.get(), &Test::XdgToplevelDecorationV1::configureRequested); + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + + // Initialize the xdg-toplevel surface. + QFETCH(Test::XdgToplevelDecorationV1::mode, decoMode); + decoration->set_mode(decoMode); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(window->isActive()); + QCOMPARE(window->layer(), NormalLayer); + QVERIFY(!window->isFullScreen()); + QCOMPARE(window->clientSize(), QSize(100, 50)); + QCOMPARE(window->isDecorated(), decoMode == Test::XdgToplevelDecorationV1::mode_server_side); + QCOMPARE(window->clientSizeToFrameSize(window->clientSize()), window->size()); + + QSignalSpy fullScreenChangedSpy(window, &Window::fullScreenChanged); + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + + // Wait for the compositor to send a configure event with the Activated state. + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states & Test::XdgToplevel::State::Activated); + + // Ask the compositor to show the window in full screen mode. + shellSurface->set_fullscreen(nullptr); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states & Test::XdgToplevel::State::Fullscreen); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value(), window->output()->geometry().size()); + + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value(), Qt::red); + + QVERIFY(fullScreenChangedSpy.wait()); + QCOMPARE(fullScreenChangedSpy.count(), 1); + QVERIFY(window->isFullScreen()); + QVERIFY(!window->isDecorated()); + QCOMPARE(window->layer(), ActiveLayer); + QCOMPARE(window->frameGeometry(), QRect(QPoint(0, 0), window->output()->geometry().size())); + + // Ask the compositor to show the window in normal mode. + shellSurface->unset_fullscreen(); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 4); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(!(states & Test::XdgToplevel::State::Fullscreen)); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value(), QSize(100, 50)); + + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value(), Qt::blue); + + QVERIFY(fullScreenChangedSpy.wait()); + QCOMPARE(fullScreenChangedSpy.count(), 2); + QCOMPARE(window->clientSize(), QSize(100, 50)); + QVERIFY(!window->isFullScreen()); + QCOMPARE(window->isDecorated(), decoMode == Test::XdgToplevelDecorationV1::mode_server_side); + QCOMPARE(window->layer(), NormalLayer); + + // Destroy the window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void TestXdgShellWindow::testUserCanSetFullscreen() +{ + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(window->isActive()); + QVERIFY(!window->isFullScreen()); + QVERIFY(window->userCanSetFullScreen()); +} + +void TestXdgShellWindow::testSendFullScreenWindowToAnotherOutput() +{ + // This test verifies that the fullscreen window will have correct geometry restore + // after it's sent to another output. + + const auto outputs = workspace()->outputs(); + + // Create the window. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // Wait for the compositor to send a configure event with the activated state. + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + + // Move the window to the left monitor. + window->move(QPointF(10, 20)); + QCOMPARE(window->frameGeometry(), QRectF(10, 20, 100, 50)); + QCOMPARE(window->output(), outputs[0]); + + // Make the window fullscreen. + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + shellSurface->set_fullscreen(nullptr); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value(), Qt::red); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->isFullScreen(), true); + QCOMPARE(window->frameGeometry(), QRectF(0, 0, 1280, 1024)); + QCOMPARE(window->fullscreenGeometryRestore(), QRectF(10, 20, 100, 50)); + QCOMPARE(window->output(), outputs[0]); + + // Send the window to another output. + workspace()->sendWindowToOutput(window, outputs[1]); + QCOMPARE(window->isFullScreen(), true); + QCOMPARE(window->frameGeometry(), QRectF(1280, 0, 1280, 1024)); + QCOMPARE(window->fullscreenGeometryRestore(), QRectF(1280 + 10, 20, 100, 50)); + QCOMPARE(window->output(), outputs[1]); +} + +void TestXdgShellWindow::testMaximizedToFullscreen_data() +{ + QTest::addColumn("decoMode"); + + QTest::newRow("client-side deco") << Test::XdgToplevelDecorationV1::mode_client_side; + QTest::newRow("server-side deco") << Test::XdgToplevelDecorationV1::mode_server_side; +} + +void TestXdgShellWindow::testMaximizedToFullscreen() +{ + // this test verifies that a window can be properly fullscreened after maximizing + + Test::XdgToplevel::States states; + + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + std::unique_ptr decoration(Test::createXdgToplevelDecorationV1(shellSurface.get())); + QSignalSpy decorationConfigureRequestedSpy(decoration.get(), &Test::XdgToplevelDecorationV1::configureRequested); + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + + // Initialize the xdg-toplevel surface. + QFETCH(Test::XdgToplevelDecorationV1::mode, decoMode); + decoration->set_mode(decoMode); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(window->isActive()); + QVERIFY(!window->isFullScreen()); + QCOMPARE(window->clientSize(), QSize(100, 50)); + QCOMPARE(window->isDecorated(), decoMode == Test::XdgToplevelDecorationV1::mode_server_side); + + QSignalSpy fullscreenChangedSpy(window, &Window::fullScreenChanged); + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + + // Wait for the compositor to send a configure event with the Activated state. + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states & Test::XdgToplevel::State::Activated); + + // Ask the compositor to maximize the window. + shellSurface->set_maximized(); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states & Test::XdgToplevel::State::Maximized); + + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value(), Qt::red); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->maximizeMode(), MaximizeFull); + + // Ask the compositor to show the window in full screen mode. + shellSurface->set_fullscreen(nullptr); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 4); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value(), window->output()->geometry().size()); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states & Test::XdgToplevel::State::Maximized); + QVERIFY(states & Test::XdgToplevel::State::Fullscreen); + + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value(), Qt::red); + + QVERIFY(fullscreenChangedSpy.wait()); + QCOMPARE(fullscreenChangedSpy.count(), 1); + QCOMPARE(window->maximizeMode(), MaximizeFull); + QVERIFY(window->isFullScreen()); + QVERIFY(!window->isDecorated()); + + // Switch back to normal mode. + shellSurface->unset_fullscreen(); + shellSurface->unset_maximized(); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 5); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value(), QSize(100, 50)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(!(states & Test::XdgToplevel::State::Maximized)); + QVERIFY(!(states & Test::XdgToplevel::State::Fullscreen)); + + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value(), Qt::red); + + QVERIFY(frameGeometryChangedSpy.wait()); + QVERIFY(!window->isFullScreen()); + QCOMPARE(window->isDecorated(), decoMode == Test::XdgToplevelDecorationV1::mode_server_side); + QCOMPARE(window->maximizeMode(), MaximizeRestore); + + // Destroy the window. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void TestXdgShellWindow::testFullscreenMultipleOutputs() +{ + // this test verifies that kwin will place fullscreen windows in the outputs its instructed to + + const auto outputs = workspace()->outputs(); + for (KWin::Output *output : outputs) { + Test::XdgToplevel::States states; + + std::unique_ptr surface = Test::createSurface(); + QVERIFY(surface); + QSharedPointer shellSurface(Test::createXdgToplevelSurface(surface.get())); + QVERIFY(shellSurface); + + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(window->isActive()); + QVERIFY(!window->isFullScreen()); + QCOMPARE(window->clientSize(), QSize(100, 50)); + QVERIFY(!window->isDecorated()); + + QSignalSpy fullscreenChangedSpy(window, &Window::fullScreenChanged); + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + + // Wait for the compositor to send a configure event with the Activated state. + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + + // Ask the compositor to show the window in full screen mode. + shellSurface->set_fullscreen(*Test::waylandOutput(output->name())); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value(), output->geometry().size()); + + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value(), Qt::red); + + QVERIFY(!fullscreenChangedSpy.isEmpty() || fullscreenChangedSpy.wait()); + QCOMPARE(fullscreenChangedSpy.count(), 1); + + QVERIFY(!frameGeometryChangedSpy.isEmpty() || frameGeometryChangedSpy.wait()); + + QVERIFY(window->isFullScreen()); + + QCOMPARE(window->frameGeometry(), output->geometry()); + } +} + +void TestXdgShellWindow::testHidden() +{ + // this test verifies that when hiding window it doesn't get shown + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(window->isActive()); + QCOMPARE(workspace()->activeWindow(), window); + QVERIFY(window->wantsInput()); + QVERIFY(window->wantsTabFocus()); + QVERIFY(window->isShown()); + + window->hideClient(); + QVERIFY(!window->isShown()); + QVERIFY(!window->isActive()); + QVERIFY(window->wantsInput()); + QVERIFY(window->wantsTabFocus()); + + // unhide again + window->showClient(); + QVERIFY(window->isShown()); + QVERIFY(window->wantsInput()); + QVERIFY(window->wantsTabFocus()); + + // QCOMPARE(workspace()->activeClient(), c); +} + +void TestXdgShellWindow::testDesktopFileName() +{ + QIcon::setThemeName(QStringLiteral("breeze")); + // this test verifies that desktop file name is passed correctly to the window + std::unique_ptr surface(Test::createSurface()); + // only xdg-shell as ShellSurface misses the setter + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + shellSurface->set_app_id(QStringLiteral("org.kde.foo")); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QCOMPARE(window->desktopFileName(), QByteArrayLiteral("org.kde.foo")); + QCOMPARE(window->resourceClass(), QByteArrayLiteral("org.kde.foo")); + QVERIFY(window->resourceName().startsWith("testXdgShellWindow")); + // the desktop file does not exist, so icon should be generic Wayland + QCOMPARE(window->icon().name(), QStringLiteral("wayland")); + + QSignalSpy desktopFileNameChangedSpy(window, &Window::desktopFileNameChanged); + QSignalSpy iconChangedSpy(window, &Window::iconChanged); + shellSurface->set_app_id(QStringLiteral("org.kde.bar")); + QVERIFY(desktopFileNameChangedSpy.wait()); + QCOMPARE(window->desktopFileName(), QByteArrayLiteral("org.kde.bar")); + QCOMPARE(window->resourceClass(), QByteArrayLiteral("org.kde.bar")); + QVERIFY(window->resourceName().startsWith("testXdgShellWindow")); + // icon should still be wayland + QCOMPARE(window->icon().name(), QStringLiteral("wayland")); + QVERIFY(iconChangedSpy.isEmpty()); + + const QString dfPath = QFINDTESTDATA("data/example.desktop"); + shellSurface->set_app_id(dfPath.toUtf8()); + QVERIFY(desktopFileNameChangedSpy.wait()); + QCOMPARE(iconChangedSpy.count(), 1); + QCOMPARE(QString::fromUtf8(window->desktopFileName()), dfPath); + QCOMPARE(window->icon().name(), QStringLiteral("kwin")); +} + +void TestXdgShellWindow::testCaptionSimplified() +{ + // this test verifies that caption is properly trimmed + // see BUG 323798 comment #12 + std::unique_ptr surface(Test::createSurface()); + // only done for xdg-shell as ShellSurface misses the setter + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + const QString origTitle = QString::fromUtf8(QByteArrayLiteral("Was tun, wenn Schüler Autismus haben?\342\200\250\342\200\250\342\200\250 – Marlies Hübner - Mozilla Firefox")); + shellSurface->set_title(origTitle); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(window->caption() != origTitle); + QCOMPARE(window->caption(), origTitle.simplified()); +} + +void TestXdgShellWindow::testCaptionMultipleWindows() +{ + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + shellSurface->set_title(QStringLiteral("foo")); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QCOMPARE(window->caption(), QStringLiteral("foo")); + QCOMPARE(window->captionNormal(), QStringLiteral("foo")); + QCOMPARE(window->captionSuffix(), QString()); + + std::unique_ptr surface2(Test::createSurface()); + std::unique_ptr shellSurface2(Test::createXdgToplevelSurface(surface2.get())); + shellSurface2->set_title(QStringLiteral("foo")); + auto c2 = Test::renderAndWaitForShown(surface2.get(), QSize(100, 50), Qt::blue); + QVERIFY(c2); + QCOMPARE(c2->caption(), QStringLiteral("foo <2>")); + QCOMPARE(c2->captionNormal(), QStringLiteral("foo")); + QCOMPARE(c2->captionSuffix(), QStringLiteral(" <2>")); + + std::unique_ptr surface3(Test::createSurface()); + std::unique_ptr shellSurface3(Test::createXdgToplevelSurface(surface3.get())); + shellSurface3->set_title(QStringLiteral("foo")); + auto c3 = Test::renderAndWaitForShown(surface3.get(), QSize(100, 50), Qt::blue); + QVERIFY(c3); + QCOMPARE(c3->caption(), QStringLiteral("foo <3>")); + QCOMPARE(c3->captionNormal(), QStringLiteral("foo")); + QCOMPARE(c3->captionSuffix(), QStringLiteral(" <3>")); + + std::unique_ptr surface4(Test::createSurface()); + std::unique_ptr shellSurface4(Test::createXdgToplevelSurface(surface4.get())); + shellSurface4->set_title(QStringLiteral("bar")); + auto c4 = Test::renderAndWaitForShown(surface4.get(), QSize(100, 50), Qt::blue); + QVERIFY(c4); + QCOMPARE(c4->caption(), QStringLiteral("bar")); + QCOMPARE(c4->captionNormal(), QStringLiteral("bar")); + QCOMPARE(c4->captionSuffix(), QString()); + QSignalSpy captionChangedSpy(c4, &Window::captionChanged); + shellSurface4->set_title(QStringLiteral("foo")); + QVERIFY(captionChangedSpy.wait()); + QCOMPARE(captionChangedSpy.count(), 1); + QCOMPARE(c4->caption(), QStringLiteral("foo <4>")); + QCOMPARE(c4->captionNormal(), QStringLiteral("foo")); + QCOMPARE(c4->captionSuffix(), QStringLiteral(" <4>")); +} + +void TestXdgShellWindow::testUnresponsiveWindow_data() +{ + QTest::addColumn("shellInterface"); // see env selection in qwaylandintegration.cpp + QTest::addColumn("socketMode"); + + QTest::newRow("xdg display") << "xdg-shell" << false; + QTest::newRow("xdg socket") << "xdg-shell" << true; +} + +void TestXdgShellWindow::testUnresponsiveWindow() +{ + // this test verifies that killWindow properly terminates a process + // for this an external binary is launched + const QString kill = QFINDTESTDATA(QStringLiteral("kill")); + QVERIFY(!kill.isEmpty()); + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + + std::unique_ptr process(new QProcess); + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + + QFETCH(QString, shellInterface); + QFETCH(bool, socketMode); + env.insert("QT_WAYLAND_SHELL_INTEGRATION", shellInterface); + if (socketMode) { + int sx[2]; + QVERIFY(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) >= 0); + waylandServer()->display()->createClient(sx[0]); + int socket = dup(sx[1]); + QVERIFY(socket != -1); + env.insert(QStringLiteral("WAYLAND_SOCKET"), QByteArray::number(socket)); + env.remove("WAYLAND_DISPLAY"); + } else { + env.insert("WAYLAND_DISPLAY", s_socketName); + } + process->setProcessEnvironment(env); + process->setProcessChannelMode(QProcess::ForwardedChannels); + process->setProgram(kill); + QSignalSpy processStartedSpy{process.get(), &QProcess::started}; + process->start(); + QVERIFY(processStartedSpy.wait()); + + Window *killWindow = nullptr; + if (windowAddedSpy.isEmpty()) { + QVERIFY(windowAddedSpy.wait()); + } + ::kill(process->processId(), SIGUSR1); // send a signal to freeze the process + + killWindow = windowAddedSpy.first().first().value(); + QVERIFY(killWindow); + QSignalSpy unresponsiveSpy(killWindow, &Window::unresponsiveChanged); + QSignalSpy killedSpy(process.get(), static_cast(&QProcess::finished)); + QSignalSpy deletedSpy(killWindow, &QObject::destroyed); + + qint64 startTime = QDateTime::currentMSecsSinceEpoch(); + + // wait for the process to be frozen + QTest::qWait(10); + + // pretend the user clicked the close button + killWindow->closeWindow(); + + // window should not yet be marked unresponsive nor killed + QVERIFY(!killWindow->unresponsive()); + QVERIFY(killedSpy.isEmpty()); + + QVERIFY(unresponsiveSpy.wait()); + // window should be marked unresponsive but not killed + auto elapsed1 = QDateTime::currentMSecsSinceEpoch() - startTime; + QVERIFY(elapsed1 > 900 && elapsed1 < 1200); // ping timer is 1s, but coarse timers on a test across two processes means we need a fuzzy compare + QVERIFY(killWindow->unresponsive()); + QVERIFY(killedSpy.isEmpty()); + + QVERIFY(deletedSpy.wait()); + if (!socketMode) { + // process was killed - because we're across process this could happen in either order + QVERIFY(killedSpy.count() || killedSpy.wait()); + } + + auto elapsed2 = QDateTime::currentMSecsSinceEpoch() - startTime; + QVERIFY(elapsed2 > 1800); // second ping comes in a second later +} + +void TestXdgShellWindow::testAppMenu() +{ + // register a faux appmenu client + QVERIFY(QDBusConnection::sessionBus().registerService("org.kde.kappmenu")); + + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + std::unique_ptr menu(Test::waylandAppMenuManager()->create(surface.get())); + QSignalSpy spy(window, &Window::hasApplicationMenuChanged); + menu->setAddress("service.name", "object/path"); + spy.wait(); + QCOMPARE(window->hasApplicationMenu(), true); + QCOMPARE(window->applicationMenuServiceName(), QString("service.name")); + QCOMPARE(window->applicationMenuObjectPath(), QString("object/path")); + + QVERIFY(QDBusConnection::sessionBus().unregisterService("org.kde.kappmenu")); +} + +void TestXdgShellWindow::testSendClientWithTransientToDesktop() +{ + // this test verifies that when sending a window to a desktop all transients are also send to that desktop + + VirtualDesktopManager *vds = VirtualDesktopManager::self(); + vds->setCount(2); + const QVector desktops = vds->desktops(); + + std::unique_ptr surface{Test::createSurface()}; + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // let's create a transient window + std::unique_ptr transientSurface{Test::createSurface()}; + std::unique_ptr transientShellSurface(Test::createXdgToplevelSurface(transientSurface.get())); + transientShellSurface->set_parent(shellSurface->object()); + + auto transient = Test::renderAndWaitForShown(transientSurface.get(), QSize(100, 50), Qt::blue); + QVERIFY(transient); + QCOMPARE(workspace()->activeWindow(), transient); + QCOMPARE(transient->transientFor(), window); + QVERIFY(window->transients().contains(transient)); + + // initially, the parent and the transient are on the first virtual desktop + QCOMPARE(window->desktops(), QVector{desktops[0]}); + QVERIFY(!window->isOnAllDesktops()); + QCOMPARE(transient->desktops(), QVector{desktops[0]}); + QVERIFY(!transient->isOnAllDesktops()); + + // send the transient to the second virtual desktop + workspace()->slotWindowToDesktop(desktops[1]); + QCOMPARE(window->desktops(), QVector{desktops[0]}); + QCOMPARE(transient->desktops(), QVector{desktops[1]}); + + // activate c + workspace()->activateWindow(window); + QCOMPARE(workspace()->activeWindow(), window); + QVERIFY(window->isActive()); + + // and send it to the desktop it's already on + QCOMPARE(window->desktops(), QVector{desktops[0]}); + QCOMPARE(transient->desktops(), QVector{desktops[1]}); + workspace()->slotWindowToDesktop(desktops[0]); + + // which should move the transient back to the desktop + QCOMPARE(window->desktops(), QVector{desktops[0]}); + QCOMPARE(transient->desktops(), QVector{desktops[0]}); +} + +void TestXdgShellWindow::testMinimizeWindowWithTransients() +{ + // this test verifies that when minimizing/unminimizing a window all its + // transients will be minimized/unminimized as well + + // create the main window + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + QVERIFY(!window->isMinimized()); + + // create a transient window + std::unique_ptr transientSurface(Test::createSurface()); + std::unique_ptr transientShellSurface(Test::createXdgToplevelSurface(transientSurface.get())); + transientShellSurface->set_parent(shellSurface->object()); + auto transient = Test::renderAndWaitForShown(transientSurface.get(), QSize(100, 50), Qt::red); + QVERIFY(transient); + QVERIFY(!transient->isMinimized()); + QCOMPARE(transient->transientFor(), window); + QVERIFY(window->hasTransient(transient, false)); + + // minimize the main window, the transient should be minimized as well + window->minimize(); + QVERIFY(window->isMinimized()); + QVERIFY(transient->isMinimized()); + + // unminimize the main window, the transient should be unminimized as well + window->unminimize(); + QVERIFY(!window->isMinimized()); + QVERIFY(!transient->isMinimized()); +} + +void TestXdgShellWindow::testXdgDecoration_data() +{ + QTest::addColumn("requestedMode"); + QTest::addColumn("expectedMode"); + + QTest::newRow("client side requested") << Test::XdgToplevelDecorationV1::mode_client_side << Test::XdgToplevelDecorationV1::mode_client_side; + QTest::newRow("server side requested") << Test::XdgToplevelDecorationV1::mode_server_side << Test::XdgToplevelDecorationV1::mode_server_side; +} + +void TestXdgShellWindow::testXdgDecoration() +{ + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + std::unique_ptr deco(Test::createXdgToplevelDecorationV1(shellSurface.get())); + + QSignalSpy decorationConfigureRequestedSpy(deco.get(), &Test::XdgToplevelDecorationV1::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + + QFETCH(Test::XdgToplevelDecorationV1::mode, requestedMode); + QFETCH(Test::XdgToplevelDecorationV1::mode, expectedMode); + + // request a mode + deco->set_mode(requestedMode); + + // kwin will send a configure + QVERIFY(surfaceConfigureRequestedSpy.wait()); + + QCOMPARE(decorationConfigureRequestedSpy.count(), 1); + QCOMPARE(decorationConfigureRequestedSpy.last()[0].value(), expectedMode); + QVERIFY(decorationConfigureRequestedSpy.count() > 0); + + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last()[0].toInt()); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QCOMPARE(window->isDecorated(), expectedMode == Test::XdgToplevelDecorationV1::mode_server_side); +} + +void TestXdgShellWindow::testXdgNeverCommitted() +{ + // check we don't crash if we create a shell object but delete the XdgShellClient before committing it + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); +} + +void TestXdgShellWindow::testXdgInitialState() +{ + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + surfaceConfigureRequestedSpy.wait(); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + + const auto size = toplevelConfigureRequestedSpy.first()[0].value(); + + QCOMPARE(size, QSize(0, 0)); // window should chose it's preferred size + + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.first()[0].toUInt()); + + auto window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::blue); + QCOMPARE(window->size(), QSize(200, 100)); +} + +void TestXdgShellWindow::testXdgInitiallyMaximised() +{ + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + + shellSurface->set_maximized(); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + surfaceConfigureRequestedSpy.wait(); + + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + + const auto size = toplevelConfigureRequestedSpy.first()[0].value(); + const auto state = toplevelConfigureRequestedSpy.first()[1].value(); + + QCOMPARE(size, QSize(1280, 1024)); + QVERIFY(state & Test::XdgToplevel::State::Maximized); + + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.first()[0].toUInt()); + + auto window = Test::renderAndWaitForShown(surface.get(), size, Qt::blue); + QCOMPARE(window->maximizeMode(), MaximizeFull); + QCOMPARE(window->size(), QSize(1280, 1024)); +} + +void TestXdgShellWindow::testXdgInitiallyFullscreen() +{ + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + + shellSurface->set_fullscreen(nullptr); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + surfaceConfigureRequestedSpy.wait(); + + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + + const auto size = toplevelConfigureRequestedSpy.first()[0].value(); + const auto state = toplevelConfigureRequestedSpy.first()[1].value(); + + QCOMPARE(size, QSize(1280, 1024)); + QVERIFY(state & Test::XdgToplevel::State::Fullscreen); + + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.first()[0].toUInt()); + + auto window = Test::renderAndWaitForShown(surface.get(), size, Qt::blue); + QCOMPARE(window->isFullScreen(), true); + QCOMPARE(window->size(), QSize(1280, 1024)); +} + +void TestXdgShellWindow::testXdgInitiallyMinimized() +{ + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + shellSurface->set_minimized(); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + surfaceConfigureRequestedSpy.wait(); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + + const auto size = toplevelConfigureRequestedSpy.first()[0].value(); + const auto state = toplevelConfigureRequestedSpy.first()[1].value(); + + QCOMPARE(size, QSize(0, 0)); + QCOMPARE(state, 0); + + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.first()[0].toUInt()); + + QEXPECT_FAIL("", "Client created in a minimised state is not exposed to kwin bug 404838", Abort); + auto window = Test::renderAndWaitForShown(surface.get(), size, Qt::blue, QImage::Format_ARGB32, 10); + QVERIFY(window); + QVERIFY(window->isMinimized()); +} + +void TestXdgShellWindow::testXdgWindowGeometryIsntSet() +{ + // This test verifies that the effective window geometry corresponds to the + // bounding rectangle of the main surface and its sub-surfaces if no window + // geometry is set by the window. + + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red); + QVERIFY(window); + QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(window->frameGeometry().size(), QSize(200, 100)); + + const QPointF oldPosition = window->pos(); + + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + Test::render(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry().topLeft(), oldPosition); + QCOMPARE(window->frameGeometry().size(), QSize(100, 50)); + QCOMPARE(window->bufferGeometry().topLeft(), oldPosition); + QCOMPARE(window->bufferGeometry().size(), QSize(100, 50)); + + std::unique_ptr childSurface(Test::createSurface()); + std::unique_ptr subSurface(Test::createSubSurface(childSurface.get(), surface.get())); + QVERIFY(subSurface); + subSurface->setPosition(QPoint(-20, -10)); + Test::render(childSurface.get(), QSize(100, 50), Qt::blue); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry().topLeft(), oldPosition); + QCOMPARE(window->frameGeometry().size(), QSize(120, 60)); + QCOMPARE(window->bufferGeometry().topLeft(), oldPosition + QPoint(20, 10)); + QCOMPARE(window->bufferGeometry().size(), QSize(100, 50)); +} + +void TestXdgShellWindow::testXdgWindowGeometryAttachBuffer() +{ + // This test verifies that the effective window geometry remains the same when + // a new buffer is attached and xdg_surface.set_window_geometry is not called + // again. Notice that the window geometry must remain the same even if the new + // buffer is smaller. + + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red); + QVERIFY(window); + QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(window->frameGeometry().size(), QSize(200, 100)); + + const QPointF oldPosition = window->pos(); + + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + shellSurface->xdgSurface()->set_window_geometry(10, 10, 180, 80); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(frameGeometryChangedSpy.count(), 1); + QCOMPARE(window->frameGeometry().topLeft(), oldPosition); + QCOMPARE(window->frameGeometry().size(), QSize(180, 80)); + QCOMPARE(window->bufferGeometry().topLeft(), oldPosition - QPoint(10, 10)); + QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); + + Test::render(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(frameGeometryChangedSpy.count(), 2); + QCOMPARE(window->frameGeometry().topLeft(), oldPosition); + QCOMPARE(window->frameGeometry().size(), QSize(90, 40)); + QCOMPARE(window->bufferGeometry().topLeft(), oldPosition - QPoint(10, 10)); + QCOMPARE(window->bufferGeometry().size(), QSize(100, 50)); + + shellSurface->xdgSurface()->set_window_geometry(0, 0, 100, 50); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(frameGeometryChangedSpy.count(), 3); + QCOMPARE(window->frameGeometry().topLeft(), oldPosition); + QCOMPARE(window->frameGeometry().size(), QSize(100, 50)); + QCOMPARE(window->bufferGeometry().topLeft(), oldPosition); + QCOMPARE(window->bufferGeometry().size(), QSize(100, 50)); + + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void TestXdgShellWindow::testXdgWindowGeometryAttachSubSurface() +{ + // This test verifies that the effective window geometry remains the same + // when a new sub-surface is added and xdg_surface.set_window_geometry is + // not called again. + + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red); + QVERIFY(window); + QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(window->frameGeometry().size(), QSize(200, 100)); + + const QPointF oldPosition = window->pos(); + + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + shellSurface->xdgSurface()->set_window_geometry(10, 10, 180, 80); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry().topLeft(), oldPosition); + QCOMPARE(window->frameGeometry().size(), QSize(180, 80)); + QCOMPARE(window->bufferGeometry().topLeft(), oldPosition - QPoint(10, 10)); + QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); + + std::unique_ptr childSurface(Test::createSurface()); + std::unique_ptr subSurface(Test::createSubSurface(childSurface.get(), surface.get())); + QVERIFY(subSurface); + subSurface->setPosition(QPoint(-20, -20)); + Test::render(childSurface.get(), QSize(100, 50), Qt::blue); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QCOMPARE(window->frameGeometry().topLeft(), oldPosition); + QCOMPARE(window->frameGeometry().size(), QSize(180, 80)); + QCOMPARE(window->bufferGeometry().topLeft(), oldPosition - QPoint(10, 10)); + QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); + + shellSurface->xdgSurface()->set_window_geometry(-15, -15, 50, 40); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->frameGeometry().topLeft(), oldPosition); + QCOMPARE(window->frameGeometry().size(), QSize(50, 40)); + QCOMPARE(window->bufferGeometry().topLeft(), oldPosition - QPoint(-15, -15)); + QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); +} + +void TestXdgShellWindow::testXdgWindowGeometryInteractiveResize() +{ + // This test verifies that correct window geometry is provided along each + // configure event when an xdg-shell is being interactively resized. + + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red); + QVERIFY(window); + QVERIFY(window->isActive()); + QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(window->frameGeometry().size(), QSize(200, 100)); + + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + shellSurface->xdgSurface()->set_window_geometry(10, 10, 180, 80); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(window->frameGeometry().size(), QSize(180, 80)); + + QSignalSpy clientStartMoveResizedSpy(window, &Window::clientStartUserMovedResized); + QSignalSpy clientStepUserMovedResizedSpy(window, &Window::clientStepUserMovedResized); + QSignalSpy clientFinishUserMovedResizedSpy(window, &Window::clientFinishUserMovedResized); + + // Start interactively resizing the window. + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + workspace()->slotWindowResize(); + QCOMPARE(workspace()->moveResizeWindow(), window); + QCOMPARE(clientStartMoveResizedSpy.count(), 1); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); + Test::XdgToplevel::States states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing)); + + // Go right. + QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); + window->keyPressEvent(Qt::Key_Right); + window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing)); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(188, 80)); + shellSurface->xdgSurface()->set_window_geometry(10, 10, 188, 80); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(208, 100), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); + QCOMPARE(window->bufferGeometry().size(), QSize(208, 100)); + QCOMPARE(window->frameGeometry().size(), QSize(188, 80)); + + // Go down. + cursorPos = KWin::Cursors::self()->mouse()->pos(); + window->keyPressEvent(Qt::Key_Down); + window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); + QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(0, 8)); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 4); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Resizing)); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(188, 88)); + shellSurface->xdgSurface()->set_window_geometry(10, 10, 188, 88); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(208, 108), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(clientStepUserMovedResizedSpy.count(), 2); + QCOMPARE(window->bufferGeometry().size(), QSize(208, 108)); + QCOMPARE(window->frameGeometry().size(), QSize(188, 88)); + + // Finish resizing the window. + window->keyPressEvent(Qt::Key_Enter); + QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); + QCOMPARE(workspace()->moveResizeWindow(), nullptr); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 5); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Resizing)); + + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void TestXdgShellWindow::testXdgWindowGeometryFullScreen() +{ + // This test verifies that an xdg-shell receives correct window geometry when + // its fullscreen state gets changed. + + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red); + QVERIFY(window); + QVERIFY(window->isActive()); + QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(window->frameGeometry().size(), QSize(200, 100)); + + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + shellSurface->xdgSurface()->set_window_geometry(10, 10, 180, 80); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(window->frameGeometry().size(), QSize(180, 80)); + + workspace()->slotWindowFullScreen(); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024)); + Test::XdgToplevel::States states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Fullscreen)); + shellSurface->xdgSurface()->set_window_geometry(0, 0, 1280, 1024); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(1280, 1024), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->bufferGeometry().size(), QSize(1280, 1024)); + QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024)); + + workspace()->slotWindowFullScreen(); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(180, 80)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Fullscreen)); + shellSurface->xdgSurface()->set_window_geometry(10, 10, 180, 80); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(200, 100), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(window->frameGeometry().size(), QSize(180, 80)); + + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void TestXdgShellWindow::testXdgWindowGeometryMaximize() +{ + // This test verifies that an xdg-shell receives correct window geometry when + // its maximized state gets changed. + + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red); + QVERIFY(window); + QVERIFY(window->isActive()); + QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(window->frameGeometry().size(), QSize(200, 100)); + + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + shellSurface->xdgSurface()->set_window_geometry(10, 10, 180, 80); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(window->frameGeometry().size(), QSize(180, 80)); + + workspace()->slotWindowMaximize(); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024)); + Test::XdgToplevel::States states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); + shellSurface->xdgSurface()->set_window_geometry(0, 0, 1280, 1024); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(1280, 1024), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->bufferGeometry().size(), QSize(1280, 1024)); + QCOMPARE(window->frameGeometry().size(), QSize(1280, 1024)); + + workspace()->slotWindowMaximize(); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(180, 80)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + shellSurface->xdgSurface()->set_window_geometry(10, 10, 180, 80); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(200, 100), Qt::blue); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(window->frameGeometry().size(), QSize(180, 80)); + + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void TestXdgShellWindow::testPointerInputTransform() +{ + // This test verifies that XdgToplevelWindow provides correct input transform matrix. + // The input transform matrix is used by seat to map pointer events from the global + // screen coordinates to the surface-local coordinates. + + // Get a wl_pointer object on the client side. + std::unique_ptr pointer(Test::waylandSeat()->createPointer()); + QVERIFY(pointer); + QVERIFY(pointer->isValid()); + QSignalSpy pointerEnteredSpy(pointer.get(), &KWayland::Client::Pointer::entered); + QSignalSpy pointerMotionSpy(pointer.get(), &KWayland::Client::Pointer::motion); + + // Create an xdg_toplevel surface and wait for the compositor to catch up. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red); + QVERIFY(window); + QVERIFY(window->isActive()); + QCOMPARE(window->bufferGeometry().size(), QSize(200, 100)); + QCOMPARE(window->frameGeometry().size(), QSize(200, 100)); + + // Enter the surface. + quint32 timestamp = 0; + Test::pointerMotion(window->pos(), timestamp++); + QVERIFY(pointerEnteredSpy.wait()); + + // Move the pointer to (10, 5) relative to the upper left frame corner, which is located + // at (0, 0) in the surface-local coordinates. + Test::pointerMotion(window->pos() + QPoint(10, 5), timestamp++); + QVERIFY(pointerMotionSpy.wait()); + QCOMPARE(pointerMotionSpy.last().first(), QPoint(10, 5)); + + // Let's pretend that the window has changed the extents of the client-side drop-shadow + // but the frame geometry didn't change. + QSignalSpy bufferGeometryChangedSpy(window, &Window::bufferGeometryChanged); + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + shellSurface->xdgSurface()->set_window_geometry(10, 20, 200, 100); + Test::render(surface.get(), QSize(220, 140), Qt::blue); + QVERIFY(bufferGeometryChangedSpy.wait()); + QCOMPARE(frameGeometryChangedSpy.count(), 0); + QCOMPARE(window->frameGeometry().size(), QSize(200, 100)); + QCOMPARE(window->bufferGeometry().size(), QSize(220, 140)); + + // Move the pointer to (20, 50) relative to the upper left frame corner, which is located + // at (10, 20) in the surface-local coordinates. + Test::pointerMotion(window->pos() + QPoint(20, 50), timestamp++); + QVERIFY(pointerMotionSpy.wait()); + QCOMPARE(pointerMotionSpy.last().first(), QPoint(10, 20) + QPoint(20, 50)); + + // Destroy the xdg-toplevel surface. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void TestXdgShellWindow::testReentrantSetFrameGeometry() +{ + // This test verifies that calling moveResize() from a slot connected directly + // to the frameGeometryChanged() signal won't cause an infinite recursion. + + // Create an xdg-toplevel surface and wait for the compositor to catch up. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(200, 100), Qt::red); + QVERIFY(window); + QCOMPARE(window->pos(), QPoint(0, 0)); + + // Let's pretend that there is a script that really wants the window to be at (100, 100). + connect(window, &Window::frameGeometryChanged, this, [window]() { + window->moveResize(QRectF(QPointF(100, 100), window->size())); + }); + + // Trigger the lambda above. + window->move(QPoint(40, 50)); + + // Eventually, the window will end up at (100, 100). + QCOMPARE(window->pos(), QPoint(100, 100)); + + // Destroy the xdg-toplevel surface. + shellSurface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void TestXdgShellWindow::testDoubleMaximize() +{ + // This test verifies that the case where a window issues two set_maximized() requests + // separated by the initial commit is handled properly. + + // Create the test surface. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + shellSurface->set_maximized(); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // Wait for the compositor to respond with a configure event. + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + + QSize size = toplevelConfigureRequestedSpy.last().at(0).toSize(); + QCOMPARE(size, QSize(1280, 1024)); + Test::XdgToplevel::States states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Send another set_maximized() request, but do not attach any buffer yet. + shellSurface->set_maximized(); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // The compositor must respond with another configure event even if the state hasn't changed. + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); + size = toplevelConfigureRequestedSpy.last().at(0).toSize(); + QCOMPARE(size, QSize(1280, 1024)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); +} + +void TestXdgShellWindow::testDoubleFullscreenSeparatedByCommit() +{ + // Some applications do weird things at startup and this is one of them. This test verifies + // that the window will have good frame geometry if the window has issued several + // xdg_toplevel.set_fullscreen requests and they are separated by a surface commit with + // no attached buffer. + + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + + // Tell the compositor that we want the window to be shown in fullscreen mode. + shellSurface->set_fullscreen(nullptr); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value(), QSize(1280, 1024)); + QVERIFY(toplevelConfigureRequestedSpy.last().at(1).value() & Test::XdgToplevel::State::Fullscreen); + + // Ask again. + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + shellSurface->set_fullscreen(nullptr); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value(), QSize(1280, 1024)); + QVERIFY(toplevelConfigureRequestedSpy.last().at(1).value() & Test::XdgToplevel::State::Fullscreen); + + // Map the window. + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(1280, 1024), Qt::blue); + QVERIFY(window->isFullScreen()); + QCOMPARE(window->frameGeometry(), QRect(0, 0, 1280, 1024)); +} + +void TestXdgShellWindow::testMaximizeHorizontal() +{ + // Create the test window. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // Wait for the initial configure event. + Test::XdgToplevel::States states; + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(0, 0)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Map the window. + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(800, 600), Qt::blue); + QVERIFY(window); + QVERIFY(window->isActive()); + QVERIFY(window->isMaximizable()); + QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(window->size(), QSize(800, 600)); + + // We should receive a configure event when the window becomes active. + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Maximize the test window in horizontal direction. + workspace()->slotWindowMaximizeHorizontal(); + QCOMPARE(window->requestedMaximizeMode(), MaximizeHorizontal); + QCOMPARE(window->maximizeMode(), MaximizeRestore); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 600)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Draw contents of the maximized window. + QSignalSpy geometryChangedSpy(window, &Window::geometryChanged); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(1280, 600), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(window->size(), QSize(1280, 600)); + QCOMPARE(window->requestedMaximizeMode(), MaximizeHorizontal); + QCOMPARE(window->maximizeMode(), MaximizeHorizontal); + + // Restore the window. + workspace()->slotWindowMaximizeHorizontal(); + QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore); + QCOMPARE(window->maximizeMode(), MaximizeHorizontal); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 4); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(800, 600)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Draw contents of the restored window. + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(800, 600), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(window->size(), QSize(800, 600)); + QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore); + QCOMPARE(window->maximizeMode(), MaximizeRestore); + + // Destroy the window. + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void TestXdgShellWindow::testMaximizeVertical() +{ + // Create the test window. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // Wait for the initial configure event. + Test::XdgToplevel::States states; + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(0, 0)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Map the window. + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(800, 600), Qt::blue); + QVERIFY(window); + QVERIFY(window->isActive()); + QVERIFY(window->isMaximizable()); + QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(window->size(), QSize(800, 600)); + + // We should receive a configure event when the window becomes active. + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Maximize the test window in vertical direction. + workspace()->slotWindowMaximizeVertical(); + QCOMPARE(window->requestedMaximizeMode(), MaximizeVertical); + QCOMPARE(window->maximizeMode(), MaximizeRestore); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(800, 1024)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Draw contents of the maximized window. + QSignalSpy geometryChangedSpy(window, &Window::geometryChanged); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(800, 1024), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(window->size(), QSize(800, 1024)); + QCOMPARE(window->requestedMaximizeMode(), MaximizeVertical); + QCOMPARE(window->maximizeMode(), MaximizeVertical); + + // Restore the window. + workspace()->slotWindowMaximizeVertical(); + QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore); + QCOMPARE(window->maximizeMode(), MaximizeVertical); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 4); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(800, 600)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Draw contents of the restored window. + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(800, 600), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(window->size(), QSize(800, 600)); + QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore); + QCOMPARE(window->maximizeMode(), MaximizeRestore); + + // Destroy the window. + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void TestXdgShellWindow::testMaximizeFull() +{ + // Create the test window. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + surface->commit(KWayland::Client::Surface::CommitFlag::None); + + // Wait for the initial configure event. + Test::XdgToplevel::States states; + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 1); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(0, 0)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Map the window. + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Window *window = Test::renderAndWaitForShown(surface.get(), QSize(800, 600), Qt::blue); + QVERIFY(window); + QVERIFY(window->isActive()); + QVERIFY(window->isMaximizable()); + QCOMPARE(window->maximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(window->requestedMaximizeMode(), MaximizeMode::MaximizeRestore); + QCOMPARE(window->size(), QSize(800, 600)); + + // We should receive a configure event when the window becomes active. + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 2); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Activated)); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Maximize the test window. + workspace()->slotWindowMaximize(); + QCOMPARE(window->requestedMaximizeMode(), MaximizeFull); + QCOMPARE(window->maximizeMode(), MaximizeRestore); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 3); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(1280, 1024)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Draw contents of the maximized window. + QSignalSpy geometryChangedSpy(window, &Window::geometryChanged); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(1280, 1024), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(window->size(), QSize(1280, 1024)); + QCOMPARE(window->requestedMaximizeMode(), MaximizeFull); + QCOMPARE(window->maximizeMode(), MaximizeFull); + + // Restore the window. + workspace()->slotWindowMaximize(); + QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore); + QCOMPARE(window->maximizeMode(), MaximizeFull); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(surfaceConfigureRequestedSpy.count(), 4); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).toSize(), QSize(800, 600)); + states = toplevelConfigureRequestedSpy.last().at(1).value(); + QVERIFY(!states.testFlag(Test::XdgToplevel::State::Maximized)); + + // Draw contents of the restored window. + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), QSize(800, 600), Qt::blue); + QVERIFY(geometryChangedSpy.wait()); + QCOMPARE(window->size(), QSize(800, 600)); + QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore); + QCOMPARE(window->maximizeMode(), MaximizeRestore); + + // Destroy the window. + shellSurface.reset(); + surface.reset(); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +void TestXdgShellWindow::testSendMaximizedWindowToAnotherOutput() +{ + // This test verifies that the maximized window will have correct geometry restore + // after it's sent to another output. + + const auto outputs = workspace()->outputs(); + + // Create the window. + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get())); + auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); + QVERIFY(window); + + // Wait for the compositor to send a configure event with the activated state. + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + + // Move the window to the left monitor. + window->move(QPointF(10, 20)); + QCOMPARE(window->frameGeometry(), QRectF(10, 20, 100, 50)); + QCOMPARE(window->output(), outputs[0]); + + // Make the window maximized. + QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); + shellSurface->set_maximized(); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + shellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value()); + Test::render(surface.get(), toplevelConfigureRequestedSpy.last().at(0).value(), Qt::red); + QVERIFY(frameGeometryChangedSpy.wait()); + QCOMPARE(window->maximizeMode(), MaximizeFull); + QCOMPARE(window->frameGeometry(), QRectF(0, 0, 1280, 1024)); + QCOMPARE(window->geometryRestore(), QRectF(10, 20, 100, 50)); + QCOMPARE(window->output(), outputs[0]); + + // Send the window to another output. + workspace()->sendWindowToOutput(window, outputs[1]); + QCOMPARE(window->maximizeMode(), MaximizeFull); + QCOMPARE(window->frameGeometry(), QRectF(1280, 0, 1280, 1024)); + QCOMPARE(window->geometryRestore(), QRectF(1280 + 10, 20, 100, 50)); + QCOMPARE(window->output(), outputs[1]); +} + +void TestXdgShellWindow::testMaximizeAndChangeDecorationModeAfterInitialCommit() +{ + // Ideally, the app would initialize the xdg-toplevel surface before the initial commit, but + // many don't do it. They initialize the surface after the first commit. + // This test verifies that the window will receive a configure event with correct size + // if an xdg-toplevel surface is set maximized and decoration mode changes after initial commit. + + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + std::unique_ptr decoration(Test::createXdgToplevelDecorationV1(shellSurface.get())); + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + + // Commit the initial state. + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value(), QSize(0, 0)); + + // Request maximized mode and set decoration mode, i.e. perform late initialization. + shellSurface->set_maximized(); + decoration->set_mode(Test::XdgToplevelDecorationV1::mode_client_side); + + // The compositor will respond with a new configure event, which should contain maximized state. + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value(), QSize(1280, 1024)); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(1).value(), Test::XdgToplevel::State::Maximized); +} + +void TestXdgShellWindow::testFullScreenAndChangeDecorationModeAfterInitialCommit() +{ + // Ideally, the app would initialize the xdg-toplevel surface before the initial commit, but + // many don't do it. They initialize the surface after the first commit. + // This test verifies that the window will receive a configure event with correct size + // if an xdg-toplevel surface is set fullscreen and decoration mode changes after initial commit. + + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + std::unique_ptr decoration(Test::createXdgToplevelDecorationV1(shellSurface.get())); + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + + // Commit the initial state. + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value(), QSize(0, 0)); + + // Request fullscreen mode and set decoration mode, i.e. perform late initialization. + shellSurface->set_fullscreen(nullptr); + decoration->set_mode(Test::XdgToplevelDecorationV1::mode_client_side); + + // The compositor will respond with a new configure event, which should contain fullscreen state. + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value(), QSize(1280, 1024)); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(1).value(), Test::XdgToplevel::State::Fullscreen); +} + +void TestXdgShellWindow::testChangeDecorationModeAfterInitialCommit() +{ + // This test verifies that the compositor will respond with a good configure event when + // the decoration mode changes after the first surface commit but before the surface is mapped. + + std::unique_ptr surface(Test::createSurface()); + std::unique_ptr shellSurface(Test::createXdgToplevelSurface(surface.get(), Test::CreationSetup::CreateOnly)); + std::unique_ptr decoration(Test::createXdgToplevelDecorationV1(shellSurface.get())); + QSignalSpy decorationConfigureRequestedSpy(decoration.get(), &Test::XdgToplevelDecorationV1::configureRequested); + QSignalSpy toplevelConfigureRequestedSpy(shellSurface.get(), &Test::XdgToplevel::configureRequested); + QSignalSpy surfaceConfigureRequestedSpy(shellSurface->xdgSurface(), &Test::XdgSurface::configureRequested); + + // Perform the initial commit. + surface->commit(KWayland::Client::Surface::CommitFlag::None); + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value(), QSize(0, 0)); + QCOMPARE(decorationConfigureRequestedSpy.last().at(0).value(), Test::XdgToplevelDecorationV1::mode_server_side); + + // Change decoration mode. + decoration->set_mode(Test::XdgToplevelDecorationV1::mode_client_side); + + // The configure event should still have 0x0 size. + QVERIFY(surfaceConfigureRequestedSpy.wait()); + QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value(), QSize(0, 0)); + QCOMPARE(decorationConfigureRequestedSpy.last().at(0).value(), Test::XdgToplevelDecorationV1::mode_client_side); +} + +WAYLANDTEST_MAIN(TestXdgShellWindow) +#include "xdgshellwindow_test.moc" diff --git a/autotests/integration/xwayland_input_test.cpp b/autotests/integration/xwayland_input_test.cpp new file mode 100644 index 0000000..90291e4 --- /dev/null +++ b/autotests/integration/xwayland_input_test.cpp @@ -0,0 +1,281 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "cursor.h" +#include "deleted.h" +#include "wayland/seat_interface.h" +#include "wayland_server.h" +#include "workspace.h" +#include "x11window.h" + +#include + +#include +#include + +namespace KWin +{ + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_xwayland_input-0"); + +class XWaylandInputTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void testPointerEnterLeaveSsd(); + void testPointerEventLeaveCsd(); +}; + +void XWaylandInputTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); + setenv("QT_QPA_PLATFORM", "wayland", true); +} + +void XWaylandInputTest::init() +{ + workspace()->setActiveOutput(QPoint(640, 512)); + Cursors::self()->mouse()->setPos(QPoint(640, 512)); + xcb_warp_pointer(connection(), XCB_WINDOW_NONE, kwinApp()->x11RootWindow(), 0, 0, 0, 0, 640, 512); + xcb_flush(connection()); + QVERIFY(waylandServer()->windows().isEmpty()); +} + +struct XcbConnectionDeleter +{ + void operator()(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } +}; + +class X11EventReaderHelper : public QObject +{ + Q_OBJECT +public: + X11EventReaderHelper(xcb_connection_t *c); + +Q_SIGNALS: + void entered(const QPoint &localPoint); + void left(const QPoint &localPoint); + +private: + void processXcbEvents(); + xcb_connection_t *m_connection; + QSocketNotifier *m_notifier; +}; + +X11EventReaderHelper::X11EventReaderHelper(xcb_connection_t *c) + : QObject() + , m_connection(c) + , m_notifier(new QSocketNotifier(xcb_get_file_descriptor(m_connection), QSocketNotifier::Read, this)) +{ + connect(m_notifier, &QSocketNotifier::activated, this, &X11EventReaderHelper::processXcbEvents); + connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, &X11EventReaderHelper::processXcbEvents); + connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::awake, this, &X11EventReaderHelper::processXcbEvents); +} + +void X11EventReaderHelper::processXcbEvents() +{ + while (auto event = xcb_poll_for_event(m_connection)) { + const uint8_t eventType = event->response_type & ~0x80; + switch (eventType) { + case XCB_ENTER_NOTIFY: { + auto enterEvent = reinterpret_cast(event); + Q_EMIT entered(QPoint(enterEvent->event_x, enterEvent->event_y)); + break; + } + case XCB_LEAVE_NOTIFY: { + auto leaveEvent = reinterpret_cast(event); + Q_EMIT left(QPoint(leaveEvent->event_x, leaveEvent->event_y)); + break; + } + } + free(event); + } + xcb_flush(m_connection); +} + +void XWaylandInputTest::testPointerEnterLeaveSsd() +{ + // this test simulates a pointer enter and pointer leave on a server-side decorated X11 window + + // create the test window + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + if (xcb_get_setup(c.get())->release_number < 11800000) { + QSKIP("XWayland 1.18 required"); + } + X11EventReaderHelper eventReader(c.get()); + QSignalSpy enteredSpy(&eventReader, &X11EventReaderHelper::entered); + QSignalSpy leftSpy(&eventReader, &X11EventReaderHelper::left); + // atom for the screenedge show hide functionality + Xcb::Atom atom(QByteArrayLiteral("_KDE_NET_WM_SCREEN_EDGE_SHOW"), false, c.get()); + + xcb_window_t windowId = xcb_generate_id(c.get()); + const QRect windowGeometry = QRect(0, 0, 100, 200); + const uint32_t values[] = { + XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW}; + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + NETWinInfo info(c.get(), windowId, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); + info.setWindowType(NET::Normal); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.last().first().value(); + QVERIFY(window); + QVERIFY(window->isDecorated()); + QVERIFY(!window->hasStrut()); + QVERIFY(!window->isHiddenInternal()); + QVERIFY(!window->readyForPainting()); + + QMetaObject::invokeMethod(window, "setReadyForPainting"); + QVERIFY(window->readyForPainting()); + QVERIFY(Test::waitForWaylandSurface(window)); + + // move pointer into the window, should trigger an enter + QVERIFY(!window->frameGeometry().contains(Cursors::self()->mouse()->pos())); + QVERIFY(enteredSpy.isEmpty()); + Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window->surface()); + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.last().first(), window->frameGeometry().center() - window->clientPos()); + + // move out of window + Cursors::self()->mouse()->setPos(window->frameGeometry().bottomRight() + QPoint(10, 10)); + QVERIFY(leftSpy.wait()); + QCOMPARE(leftSpy.last().first(), window->frameGeometry().center() - window->clientPos()); + + // destroy window again + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + xcb_unmap_window(c.get(), windowId); + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + QVERIFY(windowClosedSpy.wait()); +} + +void XWaylandInputTest::testPointerEventLeaveCsd() +{ + // this test simulates a pointer enter and pointer leave on a client-side decorated X11 window + + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + + if (xcb_get_setup(c.get())->release_number < 11800000) { + QSKIP("XWayland 1.18 required"); + } + if (!Xcb::Extensions::self()->isShapeAvailable()) { + QSKIP("SHAPE extension is required"); + } + + X11EventReaderHelper eventReader(c.get()); + QSignalSpy enteredSpy(&eventReader, &X11EventReaderHelper::entered); + QSignalSpy leftSpy(&eventReader, &X11EventReaderHelper::left); + + // Extents of the client-side drop-shadow. + NETStrut clientFrameExtent; + clientFrameExtent.left = 10; + clientFrameExtent.right = 10; + clientFrameExtent.top = 5; + clientFrameExtent.bottom = 20; + + // Need to set the bounding shape in order to create a window without decoration. + xcb_rectangle_t boundingRect; + boundingRect.x = 0; + boundingRect.y = 0; + boundingRect.width = 100 + clientFrameExtent.left + clientFrameExtent.right; + boundingRect.height = 200 + clientFrameExtent.top + clientFrameExtent.bottom; + + xcb_window_t windowId = xcb_generate_id(c.get()); + const uint32_t values[] = { + XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW}; + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + boundingRect.x, boundingRect.y, boundingRect.width, boundingRect.height, + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, boundingRect.x, boundingRect.y); + xcb_icccm_size_hints_set_size(&hints, 1, boundingRect.width, boundingRect.height); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + xcb_shape_rectangles(c.get(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_BOUNDING, + XCB_CLIP_ORDERING_UNSORTED, windowId, 0, 0, 1, &boundingRect); + NETWinInfo info(c.get(), windowId, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); + info.setWindowType(NET::Normal); + info.setGtkFrameExtents(clientFrameExtent); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.last().first().value(); + QVERIFY(window); + QVERIFY(!window->isDecorated()); + QVERIFY(window->isClientSideDecorated()); + QCOMPARE(window->bufferGeometry(), QRect(0, 0, 120, 225)); + QCOMPARE(window->frameGeometry(), QRect(10, 5, 100, 200)); + + QMetaObject::invokeMethod(window, "setReadyForPainting"); + QVERIFY(window->readyForPainting()); + QVERIFY(Test::waitForWaylandSurface(window)); + + // Move pointer into the window, should trigger an enter. + QVERIFY(!window->frameGeometry().contains(Cursors::self()->mouse()->pos())); + QVERIFY(enteredSpy.isEmpty()); + Cursors::self()->mouse()->setPos(window->frameGeometry().center()); + QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), window->surface()); + QVERIFY(enteredSpy.wait()); + QCOMPARE(enteredSpy.last().first(), QPointF(60, 105)); + + // Move out of the window, should trigger a leave. + QVERIFY(leftSpy.isEmpty()); + Cursors::self()->mouse()->setPos(window->frameGeometry().bottomRight() + QPoint(100, 100)); + QVERIFY(leftSpy.wait()); + QCOMPARE(leftSpy.last().first(), QPointF(60, 105)); + + // Destroy the window. + QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); + xcb_unmap_window(c.get(), windowId); + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + QVERIFY(windowClosedSpy.wait()); +} + +} + +WAYLANDTEST_MAIN(KWin::XWaylandInputTest) +#include "xwayland_input_test.moc" diff --git a/autotests/integration/xwayland_selections_test.cpp b/autotests/integration/xwayland_selections_test.cpp new file mode 100644 index 0000000..27dd7bc --- /dev/null +++ b/autotests/integration/xwayland_selections_test.cpp @@ -0,0 +1,143 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + SPDX-FileCopyrightText: 2019 Roman Gilg + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "kwin_wayland_test.h" + +#include "core/output.h" +#include "core/platform.h" +#include "wayland/seat_interface.h" +#include "wayland_server.h" +#include "window.h" +#include "workspace.h" +#include "xwayland/databridge.h" + +#include +#include + +using namespace KWin; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_xwayland_selections-0"); + +struct ProcessKillBeforeDeleter +{ + void operator()(QProcess *pointer) + { + if (pointer) { + pointer->kill(); + } + delete pointer; + } +}; + +class XwaylandSelectionsTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void testSync_data(); + void testSync(); +}; + +void XwaylandSelectionsTest::initTestCase() +{ + // QSKIP("Skipped as it fails for unknown reasons on build.kde.org"); + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + // QSignalSpy clipboardSyncDevicedCreated{waylandServer(), &WaylandServer::xclipboardSyncDataDeviceCreated}; + // QVERIFY(clipboardSyncDevicedCreated.isValid()); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); + // // wait till the xclipboard sync data device is created + // if (clipboardSyncDevicedCreated.empty()) { + // QVERIFY(clipboardSyncDevicedCreated.wait()); + // } +} + +void XwaylandSelectionsTest::testSync_data() +{ + QTest::addColumn("copyPlatform"); + QTest::addColumn("pastePlatform"); + + QTest::newRow("x11->wayland") << QStringLiteral("xcb") << QStringLiteral("wayland"); + QTest::newRow("wayland->x11") << QStringLiteral("wayland") << QStringLiteral("xcb"); +} + +void XwaylandSelectionsTest::testSync() +{ + // this test verifies the syncing of X11 to Wayland clipboard + const QString copy = QFINDTESTDATA(QStringLiteral("copy")); + QVERIFY(!copy.isEmpty()); + const QString paste = QFINDTESTDATA(QStringLiteral("paste")); + QVERIFY(!paste.isEmpty()); + + QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); + QSignalSpy clipboardChangedSpy(waylandServer()->seat(), &KWaylandServer::SeatInterface::selectionChanged); + + QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); + + // start the copy process + QFETCH(QString, copyPlatform); + environment.insert(QStringLiteral("QT_QPA_PLATFORM"), copyPlatform); + environment.insert(QStringLiteral("WAYLAND_DISPLAY"), s_socketName); + std::unique_ptr copyProcess(new QProcess()); + copyProcess->setProcessEnvironment(environment); + copyProcess->setProcessChannelMode(QProcess::ForwardedChannels); + copyProcess->setProgram(copy); + copyProcess->start(); + QVERIFY(copyProcess->waitForStarted()); + + Window *copyWindow = nullptr; + QVERIFY(windowAddedSpy.wait()); + copyWindow = windowAddedSpy.first().first().value(); + QVERIFY(copyWindow); + if (workspace()->activeWindow() != copyWindow) { + workspace()->activateWindow(copyWindow); + } + QCOMPARE(workspace()->activeWindow(), copyWindow); + clipboardChangedSpy.wait(); + + // start the paste process + std::unique_ptr pasteProcess(new QProcess()); + QSignalSpy finishedSpy(pasteProcess.get(), static_cast(&QProcess::finished)); + QFETCH(QString, pastePlatform); + environment.insert(QStringLiteral("QT_QPA_PLATFORM"), pastePlatform); + pasteProcess->setProcessEnvironment(environment); + pasteProcess->setProcessChannelMode(QProcess::ForwardedChannels); + pasteProcess->setProgram(paste); + pasteProcess->start(); + QVERIFY(pasteProcess->waitForStarted()); + + windowAddedSpy.clear(); + Window *pasteWindow = nullptr; + QVERIFY(windowAddedSpy.wait()); + pasteWindow = windowAddedSpy.last().first().value(); + QCOMPARE(windowAddedSpy.count(), 1); + QVERIFY(pasteWindow); + + if (workspace()->activeWindow() != pasteWindow) { + QSignalSpy windowActivatedSpy(workspace(), &Workspace::windowActivated); + workspace()->activateWindow(pasteWindow); + QVERIFY(windowActivatedSpy.wait()); + } + QTRY_COMPARE(workspace()->activeWindow(), pasteWindow); + QVERIFY(finishedSpy.wait()); + QCOMPARE(finishedSpy.first().first().toInt(), 0); +} + +WAYLANDTEST_MAIN(XwaylandSelectionsTest) +#include "xwayland_selections_test.moc" diff --git a/autotests/integration/xwaylandserver_crash_test.cpp b/autotests/integration/xwaylandserver_crash_test.cpp new file mode 100644 index 0000000..af011e1 --- /dev/null +++ b/autotests/integration/xwaylandserver_crash_test.cpp @@ -0,0 +1,136 @@ +/* + SPDX-FileCopyrightText: 2020 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "kwin_wayland_test.h" + +#include "composite.h" +#include "core/output.h" +#include "core/platform.h" +#include "main.h" +#include "scene.h" +#include "unmanaged.h" +#include "wayland_server.h" +#include "workspace.h" +#include "x11window.h" +#include "xwayland/xwayland.h" +#include "xwayland/xwaylandlauncher.h" + +#include + +namespace KWin +{ + +struct XcbConnectionDeleter +{ + void operator()(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } +}; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_xwayland_server_crash-0"); + +class XwaylandServerCrashTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void testCrash(); +}; + +void XwaylandServerCrashTest::initTestCase() +{ + qRegisterMetaType(); + qRegisterMetaType(); + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup xwaylandGroup = config->group("Xwayland"); + xwaylandGroup.writeEntry(QStringLiteral("XwaylandCrashPolicy"), QStringLiteral("Stop")); + xwaylandGroup.sync(); + kwinApp()->setConfig(config); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); + const auto outputs = workspace()->outputs(); + QCOMPARE(outputs.count(), 2); + QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); + QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); +} + +void XwaylandServerCrashTest::testCrash() +{ + // This test verifies that all connected X11 clients get destroyed when Xwayland crashes. + + // Create a normal window. + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect windowGeometry(0, 0, 100, 200); + xcb_window_t windowId1 = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId1, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_size_hints_set_min_size(&hints, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId1, &hints); + xcb_map_window(c.get(), windowId1); + xcb_flush(c.get()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + QPointer window = windowCreatedSpy.last().first().value(); + QVERIFY(window); + QVERIFY(window->isDecorated()); + + // Create an override-redirect window. + xcb_window_t windowId2 = xcb_generate_id(c.get()); + const uint32_t values[] = {true}; + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId2, rootWindow(), + windowGeometry.x(), windowGeometry.y(), + windowGeometry.width(), windowGeometry.height(), 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, + XCB_CW_OVERRIDE_REDIRECT, values); + xcb_map_window(c.get(), windowId2); + xcb_flush(c.get()); + + QSignalSpy unmanagedAddedSpy(workspace(), &Workspace::unmanagedAdded); + QVERIFY(unmanagedAddedSpy.wait()); + QPointer unmanaged = unmanagedAddedSpy.last().first().value(); + QVERIFY(unmanaged); + + // Let's pretend that the Xwayland process has crashed. + QSignalSpy x11ConnectionChangedSpy(kwinApp(), &Application::x11ConnectionChanged); + Xwl::Xwayland *xwayland = static_cast(kwinApp()->xwayland()); + xwayland->xwaylandLauncher()->process()->terminate(); + QVERIFY(x11ConnectionChangedSpy.wait()); + + // When Xwayland crashes, the compositor should tear down the XCB connection and destroy + // all connected X11 clients. + QTRY_VERIFY(!window); + QTRY_VERIFY(!unmanaged); + QCOMPARE(kwinApp()->x11Connection(), nullptr); + QCOMPARE(kwinApp()->x11RootWindow(), XCB_WINDOW_NONE); + + // Render a frame to ensure that the compositor doesn't crash. + Compositor::self()->scene()->addRepaintFull(); + QSignalSpy frameRenderedSpy(Compositor::self()->scene(), &Scene::frameRendered); + QVERIFY(frameRenderedSpy.wait()); +} + +} // namespace KWin + +WAYLANDTEST_MAIN(KWin::XwaylandServerCrashTest) +#include "xwaylandserver_crash_test.moc" diff --git a/autotests/integration/xwaylandserver_restart_test.cpp b/autotests/integration/xwaylandserver_restart_test.cpp new file mode 100644 index 0000000..df37a86 --- /dev/null +++ b/autotests/integration/xwaylandserver_restart_test.cpp @@ -0,0 +1,116 @@ +/* + SPDX-FileCopyrightText: 2020 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "kwin_wayland_test.h" + +#include "composite.h" +#include "core/platform.h" +#include "main.h" +#include "scene.h" +#include "wayland_server.h" +#include "workspace.h" +#include "x11window.h" +#include "xwayland/xwayland.h" +#include "xwayland/xwaylandlauncher.h" + +#include + +namespace KWin +{ + +struct XcbConnectionDeleter +{ + void operator()(xcb_connection_t *pointer) + { + xcb_disconnect(pointer); + } +}; + +static const QString s_socketName = QStringLiteral("wayland_test_kwin_xwayland_server_restart-0"); + +class XwaylandServerRestartTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void testRestart(); +}; + +void XwaylandServerRestartTest::initTestCase() +{ + QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); + kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024)); + QVERIFY(waylandServer()->init(s_socketName)); + QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2)); + + KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup xwaylandGroup = config->group("Xwayland"); + xwaylandGroup.writeEntry(QStringLiteral("XwaylandCrashPolicy"), QStringLiteral("Restart")); + xwaylandGroup.sync(); + kwinApp()->setConfig(config); + + kwinApp()->start(); + QVERIFY(applicationStartedSpy.wait()); +} + +static void kwin_safe_kill(QProcess *process) +{ + // The SIGKILL signal must be sent when the event loop is spinning. + QTimer::singleShot(1, process, &QProcess::kill); +} + +void XwaylandServerRestartTest::testRestart() +{ + // This test verifies that the Xwayland server will be restarted after a crash. + + Xwl::Xwayland *xwayland = static_cast(kwinApp()->xwayland()); + + // Pretend that the Xwayland process has crashed by sending a SIGKILL to it. + QSignalSpy startedSpy(xwayland, &Xwl::Xwayland::started); + kwin_safe_kill(xwayland->xwaylandLauncher()->process()); + QVERIFY(startedSpy.wait()); + QCOMPARE(startedSpy.count(), 1); + + // Check that the compositor still accepts new X11 clients. + std::unique_ptr c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.get())); + const QRect rect(0, 0, 100, 200); + xcb_window_t windowId = xcb_generate_id(c.get()); + xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), + rect.x(), rect.y(), rect.width(), rect.height(), 0, + XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, rect.x(), rect.y()); + xcb_icccm_size_hints_set_size(&hints, 1, rect.width(), rect.height()); + xcb_icccm_size_hints_set_min_size(&hints, rect.width(), rect.height()); + xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); + xcb_map_window(c.get(), windowId); + xcb_flush(c.get()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); + QVERIFY(windowCreatedSpy.wait()); + X11Window *window = windowCreatedSpy.last().first().value(); + QVERIFY(window); + QCOMPARE(window->window(), windowId); + QVERIFY(window->isDecorated()); + + // Render a frame to ensure that the compositor doesn't crash. + Compositor::self()->scene()->addRepaintFull(); + QSignalSpy frameRenderedSpy(Compositor::self()->scene(), &Scene::frameRendered); + QVERIFY(frameRenderedSpy.wait()); + + // Destroy the test window. + xcb_destroy_window(c.get(), windowId); + xcb_flush(c.get()); + QVERIFY(Test::waitForWindowDestroyed(window)); +} + +} // namespace KWin + +WAYLANDTEST_MAIN(KWin::XwaylandServerRestartTest) +#include "xwaylandserver_restart_test.moc" diff --git a/autotests/libinput/CMakeLists.txt b/autotests/libinput/CMakeLists.txt new file mode 100644 index 0000000..8fed98f --- /dev/null +++ b/autotests/libinput/CMakeLists.txt @@ -0,0 +1,62 @@ +include_directories(${Libinput_INCLUDE_DIRS}) + +add_definitions(-DKWIN_BUILD_TESTING) +add_library(LibInputTestObjects STATIC ../../src/backends/libinput/device.cpp ../../src/backends/libinput/events.cpp ../../src/core/inputdevice.cpp ../../src/mousebuttons.cpp mock_libinput.cpp) +target_link_libraries(LibInputTestObjects Qt::Test Qt::Widgets Qt::DBus Qt::Gui KF5::ConfigCore) +target_include_directories(LibInputTestObjects PUBLIC ${CMAKE_SOURCE_DIR}/src) + +######################################################## +# Test Devices +######################################################## +add_executable(testLibinputDevice device_test.cpp) +target_link_libraries(testLibinputDevice Qt::Test Qt::DBus Qt::Gui KF5::ConfigCore LibInputTestObjects) +add_test(NAME kwin-testLibinputDevice COMMAND testLibinputDevice) +ecm_mark_as_test(testLibinputDevice) + +######################################################## +# Test Key Event +######################################################## +add_executable(testLibinputKeyEvent key_event_test.cpp) +target_link_libraries(testLibinputKeyEvent Qt::Test Qt::DBus Qt::Widgets KF5::ConfigCore LibInputTestObjects) +add_test(NAME kwin-testLibinputKeyEvent COMMAND testLibinputKeyEvent) +ecm_mark_as_test(testLibinputKeyEvent) + +######################################################## +# Test Pointer Event +######################################################## +add_executable(testLibinputPointerEvent pointer_event_test.cpp) +target_link_libraries(testLibinputPointerEvent Qt::Test Qt::DBus Qt::Widgets KF5::ConfigCore LibInputTestObjects) +add_test(NAME kwin-testLibinputPointerEvent COMMAND testLibinputPointerEvent) +ecm_mark_as_test(testLibinputPointerEvent) + +######################################################## +# Test Touch Event +######################################################## +add_executable(testLibinputTouchEvent touch_event_test.cpp) +target_link_libraries(testLibinputTouchEvent Qt::Test Qt::DBus Qt::Widgets KF5::ConfigCore LibInputTestObjects) +add_test(NAME kwin-testLibinputTouchEvent COMMAND testLibinputTouchEvent) +ecm_mark_as_test(testLibinputTouchEvent) + +######################################################## +# Test Gesture Event +######################################################## +add_executable(testLibinputGestureEvent gesture_event_test.cpp) +target_link_libraries(testLibinputGestureEvent Qt::Test Qt::DBus Qt::Widgets KF5::ConfigCore LibInputTestObjects) +add_test(NAME kwin-testLibinputGestureEvent COMMAND testLibinputGestureEvent) +ecm_mark_as_test(testLibinputGestureEvent) + +######################################################## +# Test Switch Event +######################################################## +add_executable(testLibinputSwitchEvent switch_event_test.cpp) +target_link_libraries(testLibinputSwitchEvent Qt::Test Qt::DBus Qt::Widgets KF5::ConfigCore LibInputTestObjects) +add_test(NAME kwin-testLibinputSwitchEvent COMMAND testLibinputSwitchEvent) +ecm_mark_as_test(testLibinputSwitchEvent) + +######################################################## +# Test Input Events +######################################################## +add_executable(testInputEvents input_event_test.cpp ../../src/input_event.cpp) +target_link_libraries(testInputEvents Qt::Test Qt::DBus Qt::Gui Qt::Widgets KF5::ConfigCore LibInputTestObjects) +add_test(NAME kwin-testInputEvents COMMAND testInputEvents) +ecm_mark_as_test(testInputEvents) diff --git a/autotests/libinput/device_test.cpp b/autotests/libinput/device_test.cpp new file mode 100644 index 0000000..2000328 --- /dev/null +++ b/autotests/libinput/device_test.cpp @@ -0,0 +1,2355 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include + +#include "backends/libinput/device.h" +#include "mock_libinput.h" + +#include + +#include +#include +#include + +#include + +using namespace KWin::LibInput; + +class TestLibinputDevice : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void testDeviceType_data(); + void testDeviceType(); + void testGestureSupport_data(); + void testGestureSupport(); + void testNames_data(); + void testNames(); + void testProduct(); + void testVendor(); + void testTapFingerCount(); + void testSize_data(); + void testSize(); + void testDefaultPointerAcceleration_data(); + void testDefaultPointerAcceleration(); + void testDefaultPointerAccelerationProfileFlat_data(); + void testDefaultPointerAccelerationProfileFlat(); + void testDefaultPointerAccelerationProfileAdaptive_data(); + void testDefaultPointerAccelerationProfileAdaptive(); + void testDefaultClickMethodAreas_data(); + void testDefaultClickMethodAreas(); + void testDefaultClickMethodClickfinger_data(); + void testDefaultClickMethodClickfinger(); + void testLeftHandedEnabledByDefault_data(); + void testLeftHandedEnabledByDefault(); + void testTapEnabledByDefault_data(); + void testTapEnabledByDefault(); + void testMiddleEmulationEnabledByDefault_data(); + void testMiddleEmulationEnabledByDefault(); + void testNaturalScrollEnabledByDefault_data(); + void testNaturalScrollEnabledByDefault(); + void testScrollTwoFingerEnabledByDefault_data(); + void testScrollTwoFingerEnabledByDefault(); + void testScrollEdgeEnabledByDefault_data(); + void testScrollEdgeEnabledByDefault(); + void testScrollOnButtonDownEnabledByDefault_data(); + void testScrollOnButtonDownEnabledByDefault(); + void testDisableWhileTypingEnabledByDefault_data(); + void testDisableWhileTypingEnabledByDefault(); + void testLmrTapButtonMapEnabledByDefault_data(); + void testLmrTapButtonMapEnabledByDefault(); + void testSupportsDisableWhileTyping_data(); + void testSupportsDisableWhileTyping(); + void testSupportsPointerAcceleration_data(); + void testSupportsPointerAcceleration(); + void testSupportsLeftHanded_data(); + void testSupportsLeftHanded(); + void testSupportsCalibrationMatrix_data(); + void testSupportsCalibrationMatrix(); + void testSupportsDisableEvents_data(); + void testSupportsDisableEvents(); + void testSupportsDisableEventsOnExternalMouse_data(); + void testSupportsDisableEventsOnExternalMouse(); + void testSupportsMiddleEmulation_data(); + void testSupportsMiddleEmulation(); + void testSupportsNaturalScroll_data(); + void testSupportsNaturalScroll(); + void testSupportsScrollTwoFinger_data(); + void testSupportsScrollTwoFinger(); + void testSupportsScrollEdge_data(); + void testSupportsScrollEdge(); + void testSupportsScrollOnButtonDown_data(); + void testSupportsScrollOnButtonDown(); + void testDefaultScrollButton_data(); + void testDefaultScrollButton(); + void testPointerAcceleration_data(); + void testPointerAcceleration(); + void testLeftHanded_data(); + void testLeftHanded(); + void testSupportedButtons_data(); + void testSupportedButtons(); + void testAlphaNumericKeyboard_data(); + void testAlphaNumericKeyboard(); + void testEnabled_data(); + void testEnabled(); + void testTapToClick_data(); + void testTapToClick(); + void testTapAndDragEnabledByDefault_data(); + void testTapAndDragEnabledByDefault(); + void testTapAndDrag_data(); + void testTapAndDrag(); + void testTapDragLockEnabledByDefault_data(); + void testTapDragLockEnabledByDefault(); + void testTapDragLock_data(); + void testTapDragLock(); + void testMiddleEmulation_data(); + void testMiddleEmulation(); + void testNaturalScroll_data(); + void testNaturalScroll(); + void testScrollFactor(); + void testScrollTwoFinger_data(); + void testScrollTwoFinger(); + void testScrollEdge_data(); + void testScrollEdge(); + void testScrollButtonDown_data(); + void testScrollButtonDown(); + void testScrollButton_data(); + void testScrollButton(); + void testDisableWhileTyping_data(); + void testDisableWhileTyping(); + void testLmrTapButtonMap_data(); + void testLmrTapButtonMap(); + void testLoadEnabled_data(); + void testLoadEnabled(); + void testLoadPointerAcceleration_data(); + void testLoadPointerAcceleration(); + void testLoadPointerAccelerationProfile_data(); + void testLoadPointerAccelerationProfile(); + void testLoadClickMethod_data(); + void testLoadClickMethod(); + void testLoadTapToClick_data(); + void testLoadTapToClick(); + void testLoadTapAndDrag_data(); + void testLoadTapAndDrag(); + void testLoadTapDragLock_data(); + void testLoadTapDragLock(); + void testLoadMiddleButtonEmulation_data(); + void testLoadMiddleButtonEmulation(); + void testLoadNaturalScroll_data(); + void testLoadNaturalScroll(); + void testLoadScrollMethod_data(); + void testLoadScrollMethod(); + void testLoadScrollButton_data(); + void testLoadScrollButton(); + void testLoadDisableWhileTyping_data(); + void testLoadDisableWhileTyping(); + void testLoadLmrTapButtonMap_data(); + void testLoadLmrTapButtonMap(); + void testLoadLeftHanded_data(); + void testLoadLeftHanded(); + void testOrientation_data(); + void testOrientation(); + void testCalibrationWithDefault(); + void testSwitch_data(); + void testSwitch(); +}; + +namespace +{ +template +T dbusProperty(const QString &name, const char *property) +{ + QDBusInterface interface { + QStringLiteral("org.kde.kwin.tests.libinputdevice"), + QStringLiteral("/org/kde/KWin/InputDevice/") + name, + QStringLiteral("org.kde.KWin.InputDevice") + }; + return interface.property(property).value(); +} +} + +void TestLibinputDevice::initTestCase() +{ + QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.kwin.tests.libinputdevice")); +} + +void TestLibinputDevice::testDeviceType_data() +{ + QTest::addColumn("keyboard"); + QTest::addColumn("pointer"); + QTest::addColumn("touch"); + QTest::addColumn("tabletTool"); + QTest::addColumn("switchDevice"); + + QTest::newRow("keyboard") << true << false << false << false << false; + QTest::newRow("pointer") << false << true << false << false << false; + QTest::newRow("touch") << false << false << true << false << false; + QTest::newRow("keyboard/pointer") << true << true << false << false << false; + QTest::newRow("keyboard/touch") << true << false << true << false << false; + QTest::newRow("pointer/touch") << false << true << true << false << false; + QTest::newRow("keyboard/pointer/touch") << true << true << true << false << false; + QTest::newRow("tabletTool") << false << false << false << true << false; + QTest::newRow("switch") << false << false << false << false << true; +} + +void TestLibinputDevice::testDeviceType() +{ + // this test verifies that the device type is recognized correctly + QFETCH(bool, keyboard); + QFETCH(bool, pointer); + QFETCH(bool, touch); + QFETCH(bool, tabletTool); + QFETCH(bool, switchDevice); + + libinput_device device; + device.keyboard = keyboard; + device.pointer = pointer; + device.touch = touch; + device.tabletTool = tabletTool; + device.switchDevice = switchDevice; + + Device d(&device); + QCOMPARE(d.isKeyboard(), keyboard); + QCOMPARE(d.property("keyboard").toBool(), keyboard); + QCOMPARE(dbusProperty(d.sysName(), "keyboard"), keyboard); + QCOMPARE(d.isPointer(), pointer); + QCOMPARE(d.property("pointer").toBool(), pointer); + QCOMPARE(dbusProperty(d.sysName(), "pointer"), pointer); + QCOMPARE(d.isTouch(), touch); + QCOMPARE(d.property("touch").toBool(), touch); + QCOMPARE(dbusProperty(d.sysName(), "touch"), touch); + QCOMPARE(d.isTabletPad(), false); + QCOMPARE(d.property("tabletPad").toBool(), false); + QCOMPARE(dbusProperty(d.sysName(), "tabletPad"), false); + QCOMPARE(d.isTabletTool(), tabletTool); + QCOMPARE(d.property("tabletTool").toBool(), tabletTool); + QCOMPARE(dbusProperty(d.sysName(), "tabletTool"), tabletTool); + QCOMPARE(d.isSwitch(), switchDevice); + QCOMPARE(d.property("switchDevice").toBool(), switchDevice); + QCOMPARE(dbusProperty(d.sysName(), "switchDevice"), switchDevice); + + QCOMPARE(d.device(), &device); +} + +void TestLibinputDevice::testGestureSupport_data() +{ + QTest::addColumn("supported"); + + QTest::newRow("supported") << true; + QTest::newRow("not supported") << false; +} + +void TestLibinputDevice::testGestureSupport() +{ + // this test verifies whether the Device supports gestures + QFETCH(bool, supported); + libinput_device device; + device.gestureSupported = supported; + + Device d(&device); + QCOMPARE(d.supportsGesture(), supported); + QCOMPARE(d.property("gestureSupport").toBool(), supported); + QCOMPARE(dbusProperty(d.sysName(), "gestureSupport"), supported); +} + +void TestLibinputDevice::testNames_data() +{ + QTest::addColumn("name"); + QTest::addColumn("sysName"); + QTest::addColumn("outputName"); + + QTest::newRow("empty") << QByteArray() << QByteArrayLiteral("event1") << QByteArray(); + QTest::newRow("set") << QByteArrayLiteral("awesome test device") << QByteArrayLiteral("event0") << QByteArrayLiteral("hdmi0"); +} + +void TestLibinputDevice::testNames() +{ + // this test verifies the various name properties of the Device + QFETCH(QByteArray, name); + QFETCH(QByteArray, sysName); + QFETCH(QByteArray, outputName); + libinput_device device; + device.name = name; + device.sysName = sysName; + device.outputName = outputName; + + Device d(&device); + QCOMPARE(d.name().toUtf8(), name); + QCOMPARE(d.property("name").toString().toUtf8(), name); + QCOMPARE(dbusProperty(d.sysName(), "name"), name); + QCOMPARE(d.sysName().toUtf8(), sysName); + QCOMPARE(d.property("sysName").toString().toUtf8(), sysName); + QCOMPARE(dbusProperty(d.sysName(), "sysName"), sysName); + QCOMPARE(d.outputName().toUtf8(), outputName); + QCOMPARE(d.property("outputName").toString().toUtf8(), outputName); + QCOMPARE(dbusProperty(d.sysName(), "outputName"), outputName); +} + +void TestLibinputDevice::testProduct() +{ + // this test verifies the product property + libinput_device device; + device.product = 100u; + Device d(&device); + QCOMPARE(d.product(), 100u); + QCOMPARE(d.property("product").toUInt(), 100u); + QCOMPARE(dbusProperty(d.sysName(), "product"), 100u); +} + +void TestLibinputDevice::testVendor() +{ + // this test verifies the vendor property + libinput_device device; + device.vendor = 200u; + Device d(&device); + QCOMPARE(d.vendor(), 200u); + QCOMPARE(d.property("vendor").toUInt(), 200u); + QCOMPARE(dbusProperty(d.sysName(), "vendor"), 200u); +} + +void TestLibinputDevice::testTapFingerCount() +{ + // this test verifies the tap finger count property + libinput_device device; + device.tapFingerCount = 3; + Device d(&device); + QCOMPARE(d.tapFingerCount(), 3); + QCOMPARE(d.property("tapFingerCount").toInt(), 3); + QCOMPARE(dbusProperty(d.sysName(), "tapFingerCount"), 3); +} + +void TestLibinputDevice::testSize_data() +{ + QTest::addColumn("setSize"); + QTest::addColumn("returnValue"); + QTest::addColumn("expectedSize"); + + QTest::newRow("10/20") << QSizeF(10.5, 20.2) << 0 << QSizeF(10.5, 20.2); + QTest::newRow("failure") << QSizeF(10, 20) << 1 << QSizeF(); +} + +void TestLibinputDevice::testSize() +{ + // this test verifies that getting the size works correctly including failures + QFETCH(QSizeF, setSize); + QFETCH(int, returnValue); + libinput_device device; + device.deviceSize = setSize; + device.deviceSizeReturnValue = returnValue; + + Device d(&device); + QTEST(d.size(), "expectedSize"); + QTEST(d.property("size").toSizeF(), "expectedSize"); + QTEST(dbusProperty(d.sysName(), "size"), "expectedSize"); +} + +void TestLibinputDevice::testLeftHandedEnabledByDefault_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testLeftHandedEnabledByDefault() +{ + QFETCH(bool, enabled); + libinput_device device; + device.leftHandedEnabledByDefault = enabled; + + Device d(&device); + QCOMPARE(d.leftHandedEnabledByDefault(), enabled); + QCOMPARE(d.property("leftHandedEnabledByDefault").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "leftHandedEnabledByDefault"), enabled); +} + +void TestLibinputDevice::testTapEnabledByDefault_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testTapEnabledByDefault() +{ + QFETCH(bool, enabled); + libinput_device device; + device.tapEnabledByDefault = enabled; + + Device d(&device); + QCOMPARE(d.tapToClickEnabledByDefault(), enabled); + QCOMPARE(d.property("tapToClickEnabledByDefault").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "tapToClickEnabledByDefault"), enabled); +} + +void TestLibinputDevice::testMiddleEmulationEnabledByDefault_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testMiddleEmulationEnabledByDefault() +{ + QFETCH(bool, enabled); + libinput_device device; + device.middleEmulationEnabledByDefault = enabled; + + Device d(&device); + QCOMPARE(d.middleEmulationEnabledByDefault(), enabled); + QCOMPARE(d.property("middleEmulationEnabledByDefault").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "middleEmulationEnabledByDefault"), enabled); +} + +void TestLibinputDevice::testNaturalScrollEnabledByDefault_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testNaturalScrollEnabledByDefault() +{ + QFETCH(bool, enabled); + libinput_device device; + device.naturalScrollEnabledByDefault = enabled; + + Device d(&device); + QCOMPARE(d.naturalScrollEnabledByDefault(), enabled); + QCOMPARE(d.property("naturalScrollEnabledByDefault").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "naturalScrollEnabledByDefault"), enabled); +} + +void TestLibinputDevice::testScrollTwoFingerEnabledByDefault_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testScrollTwoFingerEnabledByDefault() +{ + QFETCH(bool, enabled); + libinput_device device; + device.defaultScrollMethod = enabled ? LIBINPUT_CONFIG_SCROLL_2FG : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; + + Device d(&device); + QCOMPARE(d.scrollTwoFingerEnabledByDefault(), enabled); + QCOMPARE(d.property("scrollTwoFingerEnabledByDefault").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "scrollTwoFingerEnabledByDefault"), enabled); +} + +void TestLibinputDevice::testScrollEdgeEnabledByDefault_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testScrollEdgeEnabledByDefault() +{ + QFETCH(bool, enabled); + libinput_device device; + device.defaultScrollMethod = enabled ? LIBINPUT_CONFIG_SCROLL_EDGE : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; + + Device d(&device); + QCOMPARE(d.scrollEdgeEnabledByDefault(), enabled); + QCOMPARE(d.property("scrollEdgeEnabledByDefault").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "scrollEdgeEnabledByDefault"), enabled); +} + +void TestLibinputDevice::testDefaultPointerAccelerationProfileFlat_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testDefaultPointerAccelerationProfileFlat() +{ + QFETCH(bool, enabled); + libinput_device device; + device.defaultPointerAccelerationProfile = enabled ? LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT : LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; + + Device d(&device); + QCOMPARE(d.defaultPointerAccelerationProfileFlat(), enabled); + QCOMPARE(d.property("defaultPointerAccelerationProfileFlat").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "defaultPointerAccelerationProfileFlat"), enabled); +} + +void TestLibinputDevice::testDefaultPointerAccelerationProfileAdaptive_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testDefaultPointerAccelerationProfileAdaptive() +{ + QFETCH(bool, enabled); + libinput_device device; + device.defaultPointerAccelerationProfile = enabled ? LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE : LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; + + Device d(&device); + QCOMPARE(d.defaultPointerAccelerationProfileAdaptive(), enabled); + QCOMPARE(d.property("defaultPointerAccelerationProfileAdaptive").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "defaultPointerAccelerationProfileAdaptive"), enabled); +} + +void TestLibinputDevice::testDefaultClickMethodAreas_data() +{ + QTest::addColumn("enabled"); + + QTest::addRow("enabled") << true; + QTest::addRow("disabled") << false; +} + +void TestLibinputDevice::testDefaultClickMethodAreas() +{ + QFETCH(bool, enabled); + libinput_device device; + device.defaultClickMethod = enabled ? LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS : LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER; + + Device d(&device); + QCOMPARE(d.defaultClickMethodAreas(), enabled); + QCOMPARE(d.property("defaultClickMethodAreas").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "defaultClickMethodAreas"), enabled); +} + +void TestLibinputDevice::testDefaultClickMethodClickfinger_data() +{ + QTest::addColumn("enabled"); + + QTest::addRow("enabled") << true; + QTest::addRow("disabled") << false; +} + +void TestLibinputDevice::testDefaultClickMethodClickfinger() +{ + QFETCH(bool, enabled); + libinput_device device; + device.defaultClickMethod = enabled ? LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER : LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS; + + Device d(&device); + QCOMPARE(d.defaultClickMethodClickfinger(), enabled); + QCOMPARE(d.property("defaultClickMethodClickfinger").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "defaultClickMethodClickfinger"), enabled); +} + +void TestLibinputDevice::testScrollOnButtonDownEnabledByDefault_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testScrollOnButtonDownEnabledByDefault() +{ + QFETCH(bool, enabled); + libinput_device device; + device.defaultScrollMethod = enabled ? LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; + + Device d(&device); + QCOMPARE(d.scrollOnButtonDownEnabledByDefault(), enabled); + QCOMPARE(d.property("scrollOnButtonDownEnabledByDefault").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "scrollOnButtonDownEnabledByDefault"), enabled); +} + +void TestLibinputDevice::testDefaultScrollButton_data() +{ + QTest::addColumn("button"); + + QTest::newRow("0") << 0u; + QTest::newRow("BTN_LEFT") << quint32(BTN_LEFT); + QTest::newRow("BTN_RIGHT") << quint32(BTN_RIGHT); + QTest::newRow("BTN_MIDDLE") << quint32(BTN_MIDDLE); + QTest::newRow("BTN_SIDE") << quint32(BTN_SIDE); + QTest::newRow("BTN_EXTRA") << quint32(BTN_EXTRA); + QTest::newRow("BTN_FORWARD") << quint32(BTN_FORWARD); + QTest::newRow("BTN_BACK") << quint32(BTN_BACK); + QTest::newRow("BTN_TASK") << quint32(BTN_TASK); +} + +void TestLibinputDevice::testDefaultScrollButton() +{ + libinput_device device; + QFETCH(quint32, button); + device.defaultScrollButton = button; + + Device d(&device); + QCOMPARE(d.defaultScrollButton(), button); + QCOMPARE(d.property("defaultScrollButton").value(), button); + QCOMPARE(dbusProperty(d.sysName(), "defaultScrollButton"), button); +} + +void TestLibinputDevice::testSupportsDisableWhileTyping_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testSupportsDisableWhileTyping() +{ + QFETCH(bool, enabled); + libinput_device device; + device.supportsDisableWhileTyping = enabled; + + Device d(&device); + QCOMPARE(d.supportsDisableWhileTyping(), enabled); + QCOMPARE(d.property("supportsDisableWhileTyping").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "supportsDisableWhileTyping"), enabled); +} + +void TestLibinputDevice::testSupportsPointerAcceleration_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testSupportsPointerAcceleration() +{ + QFETCH(bool, enabled); + libinput_device device; + device.supportsPointerAcceleration = enabled; + + Device d(&device); + QCOMPARE(d.supportsPointerAcceleration(), enabled); + QCOMPARE(d.property("supportsPointerAcceleration").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "supportsPointerAcceleration"), enabled); +} + +void TestLibinputDevice::testSupportsLeftHanded_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testSupportsLeftHanded() +{ + QFETCH(bool, enabled); + libinput_device device; + device.supportsLeftHanded = enabled; + + Device d(&device); + QCOMPARE(d.supportsLeftHanded(), enabled); + QCOMPARE(d.property("supportsLeftHanded").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "supportsLeftHanded"), enabled); +} + +void TestLibinputDevice::testSupportsCalibrationMatrix_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testSupportsCalibrationMatrix() +{ + QFETCH(bool, enabled); + libinput_device device; + device.supportsCalibrationMatrix = enabled; + + Device d(&device); + QCOMPARE(d.supportsCalibrationMatrix(), enabled); + QCOMPARE(d.property("supportsCalibrationMatrix").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "supportsCalibrationMatrix"), enabled); +} + +void TestLibinputDevice::testSupportsDisableEvents_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testSupportsDisableEvents() +{ + QFETCH(bool, enabled); + libinput_device device; + device.supportsDisableEvents = enabled; + + Device d(&device); + QCOMPARE(d.supportsDisableEvents(), enabled); + QCOMPARE(d.property("supportsDisableEvents").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "supportsDisableEvents"), enabled); +} + +void TestLibinputDevice::testSupportsDisableEventsOnExternalMouse_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testSupportsDisableEventsOnExternalMouse() +{ + QFETCH(bool, enabled); + libinput_device device; + device.supportsDisableEventsOnExternalMouse = enabled; + + Device d(&device); + QCOMPARE(d.supportsDisableEventsOnExternalMouse(), enabled); + QCOMPARE(d.property("supportsDisableEventsOnExternalMouse").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "supportsDisableEventsOnExternalMouse"), enabled); +} + +void TestLibinputDevice::testSupportsMiddleEmulation_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testSupportsMiddleEmulation() +{ + QFETCH(bool, enabled); + libinput_device device; + device.supportsMiddleEmulation = enabled; + + Device d(&device); + QCOMPARE(d.supportsMiddleEmulation(), enabled); + QCOMPARE(d.property("supportsMiddleEmulation").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "supportsMiddleEmulation"), enabled); +} + +void TestLibinputDevice::testSupportsNaturalScroll_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testSupportsNaturalScroll() +{ + QFETCH(bool, enabled); + libinput_device device; + device.supportsNaturalScroll = enabled; + + Device d(&device); + QCOMPARE(d.supportsNaturalScroll(), enabled); + QCOMPARE(d.property("supportsNaturalScroll").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "supportsNaturalScroll"), enabled); +} + +void TestLibinputDevice::testSupportsScrollTwoFinger_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testSupportsScrollTwoFinger() +{ + QFETCH(bool, enabled); + libinput_device device; + device.supportedScrollMethods = enabled ? LIBINPUT_CONFIG_SCROLL_2FG : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; + + Device d(&device); + QCOMPARE(d.supportsScrollTwoFinger(), enabled); + QCOMPARE(d.property("supportsScrollTwoFinger").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "supportsScrollTwoFinger"), enabled); +} + +void TestLibinputDevice::testSupportsScrollEdge_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testSupportsScrollEdge() +{ + QFETCH(bool, enabled); + libinput_device device; + device.supportedScrollMethods = enabled ? LIBINPUT_CONFIG_SCROLL_EDGE : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; + + Device d(&device); + QCOMPARE(d.supportsScrollEdge(), enabled); + QCOMPARE(d.property("supportsScrollEdge").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "supportsScrollEdge"), enabled); +} + +void TestLibinputDevice::testSupportsScrollOnButtonDown_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testSupportsScrollOnButtonDown() +{ + QFETCH(bool, enabled); + libinput_device device; + device.supportedScrollMethods = enabled ? LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; + + Device d(&device); + QCOMPARE(d.supportsScrollOnButtonDown(), enabled); + QCOMPARE(d.property("supportsScrollOnButtonDown").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "supportsScrollOnButtonDown"), enabled); +} + +void TestLibinputDevice::testDefaultPointerAcceleration_data() +{ + QTest::addColumn("accel"); + + QTest::newRow("-1.0") << -1.0; + QTest::newRow("-0.5") << -0.5; + QTest::newRow("0.0") << 0.0; + QTest::newRow("0.3") << 0.3; + QTest::newRow("1.0") << 1.0; +} + +void TestLibinputDevice::testDefaultPointerAcceleration() +{ + QFETCH(qreal, accel); + libinput_device device; + device.defaultPointerAcceleration = accel; + + Device d(&device); + QCOMPARE(d.defaultPointerAcceleration(), accel); + QCOMPARE(d.property("defaultPointerAcceleration").toReal(), accel); + QCOMPARE(dbusProperty(d.sysName(), "defaultPointerAcceleration"), accel); +} + +void TestLibinputDevice::testPointerAcceleration_data() +{ + QTest::addColumn("supported"); + QTest::addColumn("setShouldFail"); + QTest::addColumn("accel"); + QTest::addColumn("setAccel"); + QTest::addColumn("expectedAccel"); + QTest::addColumn("expectedChanged"); + + QTest::newRow("-1 -> 2.0") << true << false << -1.0 << 2.0 << 1.0 << true; + QTest::newRow("0 -> -1.0") << true << false << 0.0 << -1.0 << -1.0 << true; + QTest::newRow("1 -> 1") << true << false << 1.0 << 1.0 << 1.0 << false; + QTest::newRow("unsupported") << false << false << 0.0 << 1.0 << 0.0 << false; + QTest::newRow("set fails") << true << true << -1.0 << 1.0 << -1.0 << false; +} + +void TestLibinputDevice::testPointerAcceleration() +{ + QFETCH(bool, supported); + QFETCH(bool, setShouldFail); + QFETCH(qreal, accel); + libinput_device device; + device.supportsPointerAcceleration = supported; + device.pointerAcceleration = accel; + device.setPointerAccelerationReturnValue = setShouldFail; + + Device d(&device); + QCOMPARE(d.pointerAcceleration(), accel); + QCOMPARE(d.property("pointerAcceleration").toReal(), accel); + QCOMPARE(dbusProperty(d.sysName(), "pointerAcceleration"), accel); + + QSignalSpy pointerAccelChangedSpy(&d, &Device::pointerAccelerationChanged); + QFETCH(qreal, setAccel); + d.setPointerAcceleration(setAccel); + QTEST(d.pointerAcceleration(), "expectedAccel"); + QTEST(!pointerAccelChangedSpy.isEmpty(), "expectedChanged"); + QTEST(dbusProperty(d.sysName(), "pointerAcceleration"), "expectedAccel"); +} + +void TestLibinputDevice::testLeftHanded_data() +{ + QTest::addColumn("supported"); + QTest::addColumn("setShouldFail"); + QTest::addColumn("initValue"); + QTest::addColumn("setValue"); + QTest::addColumn("expectedValue"); + + QTest::newRow("unsupported/true") << false << false << true << false << false; + QTest::newRow("unsupported/false") << false << false << false << true << false; + QTest::newRow("true -> false") << true << false << true << false << false; + QTest::newRow("false -> true") << true << false << false << true << true; + QTest::newRow("set fails") << true << true << true << false << true; + QTest::newRow("true -> true") << true << false << true << true << true; + QTest::newRow("false -> false") << true << false << false << false << false; +} + +void TestLibinputDevice::testLeftHanded() +{ + QFETCH(bool, supported); + QFETCH(bool, setShouldFail); + QFETCH(bool, initValue); + libinput_device device; + device.supportsLeftHanded = supported; + device.leftHanded = initValue; + device.setLeftHandedReturnValue = setShouldFail; + + Device d(&device); + QCOMPARE(d.isLeftHanded(), supported && initValue); + QCOMPARE(d.property("leftHanded").toBool(), supported && initValue); + QCOMPARE(dbusProperty(d.sysName(), "leftHanded"), supported && initValue); + + QSignalSpy leftHandedChangedSpy(&d, &Device::leftHandedChanged); + QFETCH(bool, setValue); + d.setLeftHanded(setValue); + QFETCH(bool, expectedValue); + QCOMPARE(d.isLeftHanded(), expectedValue); + QCOMPARE(leftHandedChangedSpy.isEmpty(), (supported && initValue) == expectedValue); + QCOMPARE(dbusProperty(d.sysName(), "leftHanded"), expectedValue); +} + +void TestLibinputDevice::testSupportedButtons_data() +{ + QTest::addColumn("isPointer"); + QTest::addColumn("setButtons"); + QTest::addColumn("expectedButtons"); + + QTest::newRow("left") << true << Qt::MouseButtons(Qt::LeftButton) << Qt::MouseButtons(Qt::LeftButton); + QTest::newRow("right") << true << Qt::MouseButtons(Qt::RightButton) << Qt::MouseButtons(Qt::RightButton); + QTest::newRow("middle") << true << Qt::MouseButtons(Qt::MiddleButton) << Qt::MouseButtons(Qt::MiddleButton); + QTest::newRow("extra1") << true << Qt::MouseButtons(Qt::ExtraButton1) << Qt::MouseButtons(Qt::ExtraButton1); + QTest::newRow("extra2") << true << Qt::MouseButtons(Qt::ExtraButton2) << Qt::MouseButtons(Qt::ExtraButton2); + QTest::newRow("back") << true << Qt::MouseButtons(Qt::BackButton) << Qt::MouseButtons(Qt::BackButton); + QTest::newRow("forward") << true << Qt::MouseButtons(Qt::ForwardButton) << Qt::MouseButtons(Qt::ForwardButton); + QTest::newRow("task") << true << Qt::MouseButtons(Qt::TaskButton) << Qt::MouseButtons(Qt::TaskButton); + + QTest::newRow("no pointer/left") << false << Qt::MouseButtons(Qt::LeftButton) << Qt::MouseButtons(); + QTest::newRow("no pointer/right") << false << Qt::MouseButtons(Qt::RightButton) << Qt::MouseButtons(); + QTest::newRow("no pointer/middle") << false << Qt::MouseButtons(Qt::MiddleButton) << Qt::MouseButtons(); + QTest::newRow("no pointer/extra1") << false << Qt::MouseButtons(Qt::ExtraButton1) << Qt::MouseButtons(); + QTest::newRow("no pointer/extra2") << false << Qt::MouseButtons(Qt::ExtraButton2) << Qt::MouseButtons(); + QTest::newRow("no pointer/back") << false << Qt::MouseButtons(Qt::BackButton) << Qt::MouseButtons(); + QTest::newRow("no pointer/forward") << false << Qt::MouseButtons(Qt::ForwardButton) << Qt::MouseButtons(); + QTest::newRow("no pointer/task") << false << Qt::MouseButtons(Qt::TaskButton) << Qt::MouseButtons(); + + QTest::newRow("all") << true + << Qt::MouseButtons(Qt::LeftButton | Qt::RightButton | Qt::MiddleButton | Qt::ExtraButton1 | Qt::ExtraButton2 | Qt::BackButton | Qt::ForwardButton | Qt::TaskButton) + << Qt::MouseButtons(Qt::LeftButton | Qt::RightButton | Qt::MiddleButton | Qt::ExtraButton1 | Qt::ExtraButton2 | Qt::BackButton | Qt::ForwardButton | Qt::TaskButton); +} + +void TestLibinputDevice::testSupportedButtons() +{ + libinput_device device; + QFETCH(bool, isPointer); + device.pointer = isPointer; + QFETCH(Qt::MouseButtons, setButtons); + device.supportedButtons = setButtons; + + Device d(&device); + QCOMPARE(d.isPointer(), isPointer); + QTEST(d.supportedButtons(), "expectedButtons"); + QTEST(Qt::MouseButtons(dbusProperty(d.sysName(), "supportedButtons")), "expectedButtons"); +} + +void TestLibinputDevice::testAlphaNumericKeyboard_data() +{ + QTest::addColumn>("supportedKeys"); + QTest::addColumn("isAlpha"); + + QVector keys; + + for (int i = KEY_1; i <= KEY_0; i++) { + keys << i; + QByteArray row = QByteArrayLiteral("number"); + row.append(QByteArray::number(i)); + QTest::newRow(row.constData()) << keys << false; + } + for (int i = KEY_Q; i <= KEY_P; i++) { + keys << i; + QByteArray row = QByteArrayLiteral("alpha"); + row.append(QByteArray::number(i)); + QTest::newRow(row.constData()) << keys << false; + } + for (int i = KEY_A; i <= KEY_L; i++) { + keys << i; + QByteArray row = QByteArrayLiteral("alpha"); + row.append(QByteArray::number(i)); + QTest::newRow(row.constData()) << keys << false; + } + for (int i = KEY_Z; i < KEY_M; i++) { + keys << i; + QByteArray row = QByteArrayLiteral("alpha"); + row.append(QByteArray::number(i)); + QTest::newRow(row.constData()) << keys << false; + } + // adding a different key should not result in it becoming alphanumeric keyboard + keys << KEY_SEMICOLON; + QTest::newRow("semicolon") << keys << false; + + // last but not least the M which should turn everything on + keys << KEY_M; + QTest::newRow("alphanumeric") << keys << true; +} + +void TestLibinputDevice::testAlphaNumericKeyboard() +{ + QFETCH(QVector, supportedKeys); + libinput_device device; + device.keyboard = true; + device.keys = supportedKeys; + + Device d(&device); + QCOMPARE(d.isKeyboard(), true); + QTEST(d.isAlphaNumericKeyboard(), "isAlpha"); + QTEST(dbusProperty(d.sysName(), "alphaNumericKeyboard"), "isAlpha"); +} + +void TestLibinputDevice::testEnabled_data() +{ + QTest::addColumn("supported"); + QTest::addColumn("setShouldFail"); + QTest::addColumn("initValue"); + QTest::addColumn("setValue"); + QTest::addColumn("expectedValue"); + + QTest::newRow("unsupported/true") << false << false << true << false << true; + QTest::newRow("unsupported/false") << false << false << false << true << true; + QTest::newRow("true -> false") << true << false << true << false << false; + QTest::newRow("false -> true") << true << false << false << true << true; + QTest::newRow("set fails") << true << true << true << false << true; + QTest::newRow("true -> true") << true << false << true << true << true; + QTest::newRow("false -> false") << true << false << false << false << false; +} + +void TestLibinputDevice::testEnabled() +{ + libinput_device device; + QFETCH(bool, supported); + QFETCH(bool, setShouldFail); + QFETCH(bool, initValue); + device.supportsDisableEvents = supported; + device.enabled = initValue; + device.setEnableModeReturnValue = setShouldFail; + + Device d(&device); + QCOMPARE(d.isEnabled(), !supported || initValue); + QCOMPARE(d.property("enabled").toBool(), !supported || initValue); + QCOMPARE(dbusProperty(d.sysName(), "enabled"), !supported || initValue); + + QSignalSpy enabledChangedSpy(&d, &Device::enabledChanged); + QFETCH(bool, setValue); + d.setEnabled(setValue); + QFETCH(bool, expectedValue); + QCOMPARE(d.isEnabled(), expectedValue); + + QCOMPARE(dbusProperty(d.sysName(), "enabled"), expectedValue); +} + +void TestLibinputDevice::testTapToClick_data() +{ + QTest::addColumn("fingerCount"); + QTest::addColumn("initValue"); + QTest::addColumn("setValue"); + QTest::addColumn("setShouldFail"); + QTest::addColumn("expectedValue"); + + QTest::newRow("unsupported") << 0 << false << true << true << false; + QTest::newRow("true -> false") << 1 << true << false << false << false; + QTest::newRow("false -> true") << 2 << false << true << false << true; + QTest::newRow("set fails") << 3 << true << false << true << true; + QTest::newRow("true -> true") << 2 << true << true << false << true; + QTest::newRow("false -> false") << 1 << false << false << false << false; +} + +void TestLibinputDevice::testTapToClick() +{ + libinput_device device; + QFETCH(int, fingerCount); + QFETCH(bool, initValue); + QFETCH(bool, setShouldFail); + device.tapFingerCount = fingerCount; + device.tapToClick = initValue; + device.setTapToClickReturnValue = setShouldFail; + + Device d(&device); + QCOMPARE(d.tapFingerCount(), fingerCount); + QCOMPARE(d.isTapToClick(), initValue); + QCOMPARE(d.property("tapToClick").toBool(), initValue); + QCOMPARE(dbusProperty(d.sysName(), "tapToClick"), initValue); + + QSignalSpy tapToClickChangedSpy(&d, &Device::tapToClickChanged); + QFETCH(bool, setValue); + d.setTapToClick(setValue); + QFETCH(bool, expectedValue); + QCOMPARE(d.isTapToClick(), expectedValue); + QCOMPARE(tapToClickChangedSpy.isEmpty(), initValue == expectedValue); + QCOMPARE(dbusProperty(d.sysName(), "tapToClick"), expectedValue); +} + +void TestLibinputDevice::testTapAndDragEnabledByDefault_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testTapAndDragEnabledByDefault() +{ + QFETCH(bool, enabled); + libinput_device device; + device.tapAndDragEnabledByDefault = enabled; + + Device d(&device); + QCOMPARE(d.tapAndDragEnabledByDefault(), enabled); + QCOMPARE(d.property("tapAndDragEnabledByDefault").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "tapAndDragEnabledByDefault"), enabled); +} + +void TestLibinputDevice::testTapAndDrag_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("setValue"); + QTest::addColumn("setShouldFail"); + QTest::addColumn("expectedValue"); + + QTest::newRow("true -> false") << true << false << false << false; + QTest::newRow("false -> true") << false << true << false << true; + QTest::newRow("set fails") << true << false << true << true; + QTest::newRow("true -> true") << true << true << false << true; + QTest::newRow("false -> false") << false << false << false << false; +} + +void TestLibinputDevice::testTapAndDrag() +{ + libinput_device device; + QFETCH(bool, initValue); + QFETCH(bool, setShouldFail); + device.tapAndDrag = initValue; + device.setTapAndDragReturnValue = setShouldFail; + + Device d(&device); + QCOMPARE(d.isTapAndDrag(), initValue); + QCOMPARE(d.property("tapAndDrag").toBool(), initValue); + QCOMPARE(dbusProperty(d.sysName(), "tapAndDrag"), initValue); + + QSignalSpy tapAndDragChangedSpy(&d, &Device::tapAndDragChanged); + QFETCH(bool, setValue); + d.setTapAndDrag(setValue); + QFETCH(bool, expectedValue); + QCOMPARE(d.isTapAndDrag(), expectedValue); + QCOMPARE(tapAndDragChangedSpy.isEmpty(), initValue == expectedValue); + QCOMPARE(dbusProperty(d.sysName(), "tapAndDrag"), expectedValue); +} + +void TestLibinputDevice::testTapDragLockEnabledByDefault_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testTapDragLockEnabledByDefault() +{ + QFETCH(bool, enabled); + libinput_device device; + device.tapDragLockEnabledByDefault = enabled; + + Device d(&device); + QCOMPARE(d.tapDragLockEnabledByDefault(), enabled); + QCOMPARE(d.property("tapDragLockEnabledByDefault").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "tapDragLockEnabledByDefault"), enabled); +} + +void TestLibinputDevice::testTapDragLock_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("setValue"); + QTest::addColumn("setShouldFail"); + QTest::addColumn("expectedValue"); + + QTest::newRow("true -> false") << true << false << false << false; + QTest::newRow("false -> true") << false << true << false << true; + QTest::newRow("set fails") << true << false << true << true; + QTest::newRow("true -> true") << true << true << false << true; + QTest::newRow("false -> false") << false << false << false << false; +} + +void TestLibinputDevice::testTapDragLock() +{ + libinput_device device; + QFETCH(bool, initValue); + QFETCH(bool, setShouldFail); + device.tapDragLock = initValue; + device.setTapDragLockReturnValue = setShouldFail; + + Device d(&device); + QCOMPARE(d.isTapDragLock(), initValue); + QCOMPARE(d.property("tapDragLock").toBool(), initValue); + QCOMPARE(dbusProperty(d.sysName(), "tapDragLock"), initValue); + + QSignalSpy tapDragLockChangedSpy(&d, &Device::tapDragLockChanged); + QFETCH(bool, setValue); + d.setTapDragLock(setValue); + QFETCH(bool, expectedValue); + QCOMPARE(d.isTapDragLock(), expectedValue); + QCOMPARE(tapDragLockChangedSpy.isEmpty(), initValue == expectedValue); + QCOMPARE(dbusProperty(d.sysName(), "tapDragLock"), expectedValue); +} + +void TestLibinputDevice::testMiddleEmulation_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("setValue"); + QTest::addColumn("setShouldFail"); + QTest::addColumn("expectedValue"); + QTest::addColumn("supportsMiddleButton"); + + QTest::newRow("true -> false") << true << false << false << false << true; + QTest::newRow("false -> true") << false << true << false << true << true; + QTest::newRow("set fails") << true << false << true << true << true; + QTest::newRow("true -> true") << true << true << false << true << true; + QTest::newRow("false -> false") << false << false << false << false << true; + + QTest::newRow("false -> true, unsupported") << false << true << true << false << false; +} + +void TestLibinputDevice::testMiddleEmulation() +{ + libinput_device device; + QFETCH(bool, initValue); + QFETCH(bool, setShouldFail); + QFETCH(bool, supportsMiddleButton); + device.supportsMiddleEmulation = supportsMiddleButton; + device.middleEmulation = initValue; + device.setMiddleEmulationReturnValue = setShouldFail; + + Device d(&device); + QCOMPARE(d.isMiddleEmulation(), initValue); + QCOMPARE(d.property("middleEmulation").toBool(), initValue); + QCOMPARE(dbusProperty(d.sysName(), "middleEmulation"), initValue); + + QSignalSpy middleEmulationChangedSpy(&d, &Device::middleEmulationChanged); + QFETCH(bool, setValue); + d.setMiddleEmulation(setValue); + QFETCH(bool, expectedValue); + QCOMPARE(d.isMiddleEmulation(), expectedValue); + QCOMPARE(d.property("middleEmulation").toBool(), expectedValue); + QCOMPARE(middleEmulationChangedSpy.isEmpty(), initValue == expectedValue); + QCOMPARE(dbusProperty(d.sysName(), "middleEmulation"), expectedValue); +} + +void TestLibinputDevice::testNaturalScroll_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("setValue"); + QTest::addColumn("setShouldFail"); + QTest::addColumn("expectedValue"); + QTest::addColumn("supportsNaturalScroll"); + + QTest::newRow("true -> false") << true << false << false << false << true; + QTest::newRow("false -> true") << false << true << false << true << true; + QTest::newRow("set fails") << true << false << true << true << true; + QTest::newRow("true -> true") << true << true << false << true << true; + QTest::newRow("false -> false") << false << false << false << false << true; + + QTest::newRow("false -> true, unsupported") << false << true << true << false << false; +} + +void TestLibinputDevice::testNaturalScroll() +{ + libinput_device device; + QFETCH(bool, initValue); + QFETCH(bool, setShouldFail); + QFETCH(bool, supportsNaturalScroll); + device.supportsNaturalScroll = supportsNaturalScroll; + device.naturalScroll = initValue; + device.setNaturalScrollReturnValue = setShouldFail; + + Device d(&device); + QCOMPARE(d.isNaturalScroll(), initValue); + QCOMPARE(d.property("naturalScroll").toBool(), initValue); + QCOMPARE(dbusProperty(d.sysName(), "naturalScroll"), initValue); + + QSignalSpy naturalScrollChangedSpy(&d, &Device::naturalScrollChanged); + QFETCH(bool, setValue); + d.setNaturalScroll(setValue); + QFETCH(bool, expectedValue); + QCOMPARE(d.isNaturalScroll(), expectedValue); + QCOMPARE(d.property("naturalScroll").toBool(), expectedValue); + QCOMPARE(naturalScrollChangedSpy.isEmpty(), initValue == expectedValue); + QCOMPARE(dbusProperty(d.sysName(), "naturalScroll"), expectedValue); +} + +void TestLibinputDevice::testScrollFactor() +{ + libinput_device device; + + qreal initValue = 1.0; + + Device d(&device); + QCOMPARE(d.scrollFactor(), initValue); + QCOMPARE(d.property("scrollFactor").toReal(), initValue); + QCOMPARE(dbusProperty(d.sysName(), "scrollFactor"), initValue); + + QSignalSpy scrollFactorChangedSpy(&d, &Device::scrollFactorChanged); + + qreal expectedValue = 2.0; + + d.setScrollFactor(expectedValue); + QCOMPARE(d.scrollFactor(), expectedValue); + QCOMPARE(d.property("scrollFactor").toReal(), expectedValue); + QCOMPARE(scrollFactorChangedSpy.isEmpty(), false); + QCOMPARE(dbusProperty(d.sysName(), "scrollFactor"), expectedValue); +} + +void TestLibinputDevice::testScrollTwoFinger_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("otherValue"); + QTest::addColumn("setValue"); + QTest::addColumn("setShouldFail"); + QTest::addColumn("expectedValue"); + QTest::addColumn("supportsScrollTwoFinger"); + + QTest::newRow("true -> false") << true << false << false << false << false << true; + QTest::newRow("other -> false") << false << true << false << false << false << true; + QTest::newRow("false -> true") << false << false << true << false << true << true; + QTest::newRow("set fails") << true << false << false << true << true << true; + QTest::newRow("true -> true") << true << false << true << false << true << true; + QTest::newRow("false -> false") << false << false << false << false << false << true; + + QTest::newRow("false -> true, unsupported") << false << false << true << true << false << false; +} + +void TestLibinputDevice::testScrollTwoFinger() +{ + libinput_device device; + QFETCH(bool, initValue); + QFETCH(bool, otherValue); + QFETCH(bool, setShouldFail); + QFETCH(bool, supportsScrollTwoFinger); + device.supportedScrollMethods = (supportsScrollTwoFinger ? LIBINPUT_CONFIG_SCROLL_2FG : LIBINPUT_CONFIG_SCROLL_NO_SCROLL) | LIBINPUT_CONFIG_SCROLL_EDGE; + device.scrollMethod = initValue ? LIBINPUT_CONFIG_SCROLL_2FG : otherValue ? LIBINPUT_CONFIG_SCROLL_EDGE + : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; + device.setScrollMethodReturnValue = setShouldFail; + + Device d(&device); + QCOMPARE(d.isScrollTwoFinger(), initValue); + QCOMPARE(d.property("scrollTwoFinger").toBool(), initValue); + QCOMPARE(d.property("scrollEdge").toBool(), otherValue); + QCOMPARE(dbusProperty(d.sysName(), "scrollTwoFinger"), initValue); + QCOMPARE(dbusProperty(d.sysName(), "scrollEdge"), otherValue); + + QSignalSpy scrollMethodChangedSpy(&d, &Device::scrollMethodChanged); + QFETCH(bool, setValue); + d.setScrollTwoFinger(setValue); + QFETCH(bool, expectedValue); + QCOMPARE(d.isScrollTwoFinger(), expectedValue); + QCOMPARE(d.property("scrollTwoFinger").toBool(), expectedValue); + QCOMPARE(scrollMethodChangedSpy.isEmpty(), initValue == expectedValue); + QCOMPARE(dbusProperty(d.sysName(), "scrollTwoFinger"), expectedValue); +} + +void TestLibinputDevice::testScrollEdge_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("otherValue"); + QTest::addColumn("setValue"); + QTest::addColumn("setShouldFail"); + QTest::addColumn("expectedValue"); + QTest::addColumn("supportsScrollEdge"); + + QTest::newRow("true -> false") << true << false << false << false << false << true; + QTest::newRow("other -> false") << false << true << false << false << false << true; + QTest::newRow("false -> true") << false << false << true << false << true << true; + QTest::newRow("set fails") << true << false << false << true << true << true; + QTest::newRow("true -> true") << true << false << true << false << true << true; + QTest::newRow("false -> false") << false << false << false << false << false << true; + + QTest::newRow("false -> true, unsupported") << false << false << true << true << false << false; +} + +void TestLibinputDevice::testScrollEdge() +{ + libinput_device device; + QFETCH(bool, initValue); + QFETCH(bool, otherValue); + QFETCH(bool, setShouldFail); + QFETCH(bool, supportsScrollEdge); + device.supportedScrollMethods = (supportsScrollEdge ? LIBINPUT_CONFIG_SCROLL_EDGE : LIBINPUT_CONFIG_SCROLL_NO_SCROLL) | LIBINPUT_CONFIG_SCROLL_2FG; + device.scrollMethod = initValue ? LIBINPUT_CONFIG_SCROLL_EDGE : otherValue ? LIBINPUT_CONFIG_SCROLL_2FG + : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; + device.setScrollMethodReturnValue = setShouldFail; + + Device d(&device); + QCOMPARE(d.isScrollEdge(), initValue); + QCOMPARE(d.property("scrollEdge").toBool(), initValue); + QCOMPARE(d.property("scrollTwoFinger").toBool(), otherValue); + QCOMPARE(dbusProperty(d.sysName(), "scrollEdge"), initValue); + QCOMPARE(dbusProperty(d.sysName(), "scrollTwoFinger"), otherValue); + + QSignalSpy scrollMethodChangedSpy(&d, &Device::scrollMethodChanged); + QFETCH(bool, setValue); + d.setScrollEdge(setValue); + QFETCH(bool, expectedValue); + QCOMPARE(d.isScrollEdge(), expectedValue); + QCOMPARE(d.property("scrollEdge").toBool(), expectedValue); + QCOMPARE(scrollMethodChangedSpy.isEmpty(), initValue == expectedValue); + QCOMPARE(dbusProperty(d.sysName(), "scrollEdge"), expectedValue); +} + +void TestLibinputDevice::testScrollButtonDown_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("otherValue"); + QTest::addColumn("setValue"); + QTest::addColumn("setShouldFail"); + QTest::addColumn("expectedValue"); + QTest::addColumn("supportsScrollButtonDown"); + + QTest::newRow("true -> false") << true << false << false << false << false << true; + QTest::newRow("other -> false") << false << true << false << false << false << true; + QTest::newRow("false -> true") << false << false << true << false << true << true; + QTest::newRow("set fails") << true << false << false << true << true << true; + QTest::newRow("true -> true") << true << false << true << false << true << true; + QTest::newRow("false -> false") << false << false << false << false << false << true; + + QTest::newRow("false -> true, unsupported") << false << false << true << true << false << false; +} + +void TestLibinputDevice::testScrollButtonDown() +{ + libinput_device device; + QFETCH(bool, initValue); + QFETCH(bool, otherValue); + QFETCH(bool, setShouldFail); + QFETCH(bool, supportsScrollButtonDown); + device.supportedScrollMethods = (supportsScrollButtonDown ? LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN : LIBINPUT_CONFIG_SCROLL_NO_SCROLL) | LIBINPUT_CONFIG_SCROLL_2FG; + device.scrollMethod = initValue ? LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN : otherValue ? LIBINPUT_CONFIG_SCROLL_2FG + : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; + device.setScrollMethodReturnValue = setShouldFail; + + Device d(&device); + QCOMPARE(d.isScrollOnButtonDown(), initValue); + QCOMPARE(d.property("scrollOnButtonDown").toBool(), initValue); + QCOMPARE(d.property("scrollTwoFinger").toBool(), otherValue); + QCOMPARE(dbusProperty(d.sysName(), "scrollOnButtonDown"), initValue); + QCOMPARE(dbusProperty(d.sysName(), "scrollTwoFinger"), otherValue); + + QSignalSpy scrollMethodChangedSpy(&d, &Device::scrollMethodChanged); + QFETCH(bool, setValue); + d.setScrollOnButtonDown(setValue); + QFETCH(bool, expectedValue); + QCOMPARE(d.isScrollOnButtonDown(), expectedValue); + QCOMPARE(d.property("scrollOnButtonDown").toBool(), expectedValue); + QCOMPARE(scrollMethodChangedSpy.isEmpty(), initValue == expectedValue); + QCOMPARE(dbusProperty(d.sysName(), "scrollOnButtonDown"), expectedValue); +} + +void TestLibinputDevice::testScrollButton_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("setValue"); + QTest::addColumn("expectedValue"); + QTest::addColumn("setShouldFail"); + QTest::addColumn("scrollOnButton"); + + QTest::newRow("BTN_LEFT -> BTN_RIGHT") << quint32(BTN_LEFT) << quint32(BTN_RIGHT) << quint32(BTN_RIGHT) << false << true; + QTest::newRow("BTN_LEFT -> BTN_LEFT") << quint32(BTN_LEFT) << quint32(BTN_LEFT) << quint32(BTN_LEFT) << false << true; + QTest::newRow("set should fail") << quint32(BTN_LEFT) << quint32(BTN_RIGHT) << quint32(BTN_LEFT) << true << true; + QTest::newRow("not scroll on button") << quint32(BTN_LEFT) << quint32(BTN_RIGHT) << quint32(BTN_LEFT) << false << false; +} + +void TestLibinputDevice::testScrollButton() +{ + libinput_device device; + QFETCH(quint32, initValue); + QFETCH(bool, setShouldFail); + QFETCH(bool, scrollOnButton); + device.scrollButton = initValue; + device.supportedScrollMethods = scrollOnButton ? LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN : LIBINPUT_CONFIG_SCROLL_NO_SCROLL; + device.setScrollButtonReturnValue = setShouldFail; + + Device d(&device); + QCOMPARE(d.scrollButton(), initValue); + QCOMPARE(d.property("scrollButton").value(), initValue); + QCOMPARE(dbusProperty(d.sysName(), "scrollButton"), initValue); + + QSignalSpy scrollButtonChangedSpy(&d, &Device::scrollButtonChanged); + QFETCH(quint32, setValue); + d.setScrollButton(setValue); + QFETCH(quint32, expectedValue); + QCOMPARE(d.scrollButton(), expectedValue); + QCOMPARE(d.property("scrollButton").value(), expectedValue); + QCOMPARE(scrollButtonChangedSpy.isEmpty(), initValue == expectedValue); + QCOMPARE(dbusProperty(d.sysName(), "scrollButton"), expectedValue); +} + +void TestLibinputDevice::testDisableWhileTypingEnabledByDefault_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testDisableWhileTypingEnabledByDefault() +{ + QFETCH(bool, enabled); + libinput_device device; + device.disableWhileTypingEnabledByDefault = enabled ? LIBINPUT_CONFIG_DWT_ENABLED : LIBINPUT_CONFIG_DWT_DISABLED; + + Device d(&device); + QCOMPARE(d.disableWhileTypingEnabledByDefault(), enabled); + QCOMPARE(d.property("disableWhileTypingEnabledByDefault").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "disableWhileTypingEnabledByDefault"), enabled); +} + +void TestLibinputDevice::testLmrTapButtonMapEnabledByDefault_data() +{ + QTest::addColumn("enabled"); + + QTest::newRow("enabled") << true; + QTest::newRow("disabled") << false; +} + +void TestLibinputDevice::testLmrTapButtonMapEnabledByDefault() +{ + QFETCH(bool, enabled); + libinput_device device; + device.defaultTapButtonMap = enabled ? LIBINPUT_CONFIG_TAP_MAP_LMR : LIBINPUT_CONFIG_TAP_MAP_LRM; + + Device d(&device); + QCOMPARE(d.lmrTapButtonMapEnabledByDefault(), enabled); + QCOMPARE(d.property("lmrTapButtonMapEnabledByDefault").toBool(), enabled); + QCOMPARE(dbusProperty(d.sysName(), "lmrTapButtonMapEnabledByDefault"), enabled); +} + +void TestLibinputDevice::testLmrTapButtonMap_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("setValue"); + QTest::addColumn("setShouldFail"); + QTest::addColumn("expectedValue"); + QTest::addColumn("fingerCount"); + + QTest::newRow("true -> false") << true << false << false << false << 3; + QTest::newRow("false -> true") << false << true << false << true << 3; + QTest::newRow("true -> false") << true << false << false << false << 2; + QTest::newRow("false -> true") << false << true << false << true << 2; + + QTest::newRow("set fails") << true << false << true << true << 3; + + QTest::newRow("true -> true") << true << true << false << true << 3; + QTest::newRow("false -> false") << false << false << false << false << 3; + QTest::newRow("true -> true") << true << true << false << true << 2; + QTest::newRow("false -> false") << false << false << false << false << 2; + + QTest::newRow("false -> true, fingerCount 0") << false << true << true << false << 0; + + // TODO: is this a fail in libinput? + // QTest::newRow("false -> true, fingerCount 1") << false << true << true << false << 1; +} + +void TestLibinputDevice::testLmrTapButtonMap() +{ + libinput_device device; + QFETCH(bool, initValue); + QFETCH(bool, setShouldFail); + QFETCH(int, fingerCount); + device.tapFingerCount = fingerCount; + device.tapButtonMap = initValue ? LIBINPUT_CONFIG_TAP_MAP_LMR : LIBINPUT_CONFIG_TAP_MAP_LRM; + device.setTapButtonMapReturnValue = setShouldFail; + + Device d(&device); + QCOMPARE(d.lmrTapButtonMap(), initValue); + QCOMPARE(d.property("lmrTapButtonMap").toBool(), initValue); + + QSignalSpy tapButtonMapChangedSpy(&d, &Device::tapButtonMapChanged); + QFETCH(bool, setValue); + d.setLmrTapButtonMap(setValue); + QFETCH(bool, expectedValue); + QCOMPARE(d.lmrTapButtonMap(), expectedValue); + QCOMPARE(d.property("lmrTapButtonMap").toBool(), expectedValue); + QCOMPARE(tapButtonMapChangedSpy.isEmpty(), initValue == expectedValue); +} + +void TestLibinputDevice::testDisableWhileTyping_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("setValue"); + QTest::addColumn("setShouldFail"); + QTest::addColumn("expectedValue"); + QTest::addColumn("supportsDisableWhileTyping"); + + QTest::newRow("true -> false") << true << false << false << false << true; + QTest::newRow("false -> true") << false << true << false << true << true; + QTest::newRow("set fails") << true << false << true << true << true; + QTest::newRow("true -> true") << true << true << false << true << true; + QTest::newRow("false -> false") << false << false << false << false << true; + + QTest::newRow("false -> true, unsupported") << false << true << true << false << false; +} + +void TestLibinputDevice::testDisableWhileTyping() +{ + libinput_device device; + QFETCH(bool, initValue); + QFETCH(bool, setShouldFail); + QFETCH(bool, supportsDisableWhileTyping); + device.supportsDisableWhileTyping = supportsDisableWhileTyping; + device.disableWhileTyping = initValue ? LIBINPUT_CONFIG_DWT_ENABLED : LIBINPUT_CONFIG_DWT_DISABLED; + device.setDisableWhileTypingReturnValue = setShouldFail; + + Device d(&device); + QCOMPARE(d.isDisableWhileTyping(), initValue); + QCOMPARE(d.property("disableWhileTyping").toBool(), initValue); + QCOMPARE(dbusProperty(d.sysName(), "disableWhileTyping"), initValue); + + QSignalSpy disableWhileTypingChangedSpy(&d, &Device::disableWhileTypingChanged); + QFETCH(bool, setValue); + d.setDisableWhileTyping(setValue); + QFETCH(bool, expectedValue); + QCOMPARE(d.isDisableWhileTyping(), expectedValue); + QCOMPARE(d.property("disableWhileTyping").toBool(), expectedValue); + QCOMPARE(disableWhileTypingChangedSpy.isEmpty(), initValue == expectedValue); + QCOMPARE(dbusProperty(d.sysName(), "disableWhileTyping"), expectedValue); +} + +void TestLibinputDevice::testLoadEnabled_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("configValue"); + + QTest::newRow("false -> true") << false << true; + QTest::newRow("true -> false") << true << false; + QTest::newRow("true -> true") << true << true; + QTest::newRow("false -> false") << false << false; +} + +void TestLibinputDevice::testLoadEnabled() +{ + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup inputConfig(config, QStringLiteral("Test")); + QFETCH(bool, configValue); + QFETCH(bool, initValue); + inputConfig.writeEntry("Enabled", configValue); + + libinput_device device; + device.supportsDisableEvents = true; + device.enabled = initValue; + device.setEnableModeReturnValue = false; + + Device d(&device); + QCOMPARE(d.isEnabled(), initValue); + // no config group set, should not change + d.loadConfiguration(); + QCOMPARE(d.isEnabled(), initValue); + + // set the group + d.setConfig(inputConfig); + d.loadConfiguration(); + QCOMPARE(d.isEnabled(), configValue); + + // and try to store + if (configValue != initValue) { + d.setEnabled(initValue); + QCOMPARE(inputConfig.readEntry("Enabled", configValue), initValue); + } +} + +void TestLibinputDevice::testLoadPointerAcceleration_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("configValue"); + + QTest::newRow("-0.2 -> 0.9") << -0.2 << 0.9; + QTest::newRow("0.0 -> -1.0") << 0.0 << -1.0; + QTest::newRow("0.123 -> -0.456") << 0.123 << -0.456; + QTest::newRow("0.7 -> 0.7") << 0.7 << 0.7; +} + +void TestLibinputDevice::testLoadPointerAcceleration() +{ + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup inputConfig(config, QStringLiteral("Test")); + QFETCH(qreal, configValue); + QFETCH(qreal, initValue); + inputConfig.writeEntry("PointerAcceleration", configValue); + + libinput_device device; + device.supportsPointerAcceleration = true; + device.pointerAcceleration = initValue; + device.setPointerAccelerationReturnValue = false; + + Device d(&device); + QCOMPARE(d.pointerAcceleration(), initValue); + QCOMPARE(d.property("pointerAcceleration").toReal(), initValue); + // no config group set, should not change + d.loadConfiguration(); + QCOMPARE(d.pointerAcceleration(), initValue); + QCOMPARE(d.property("pointerAcceleration").toReal(), initValue); + + // set the group + d.setConfig(inputConfig); + d.loadConfiguration(); + QCOMPARE(d.pointerAcceleration(), configValue); + QCOMPARE(d.property("pointerAcceleration").toReal(), configValue); + + // and try to store + if (configValue != initValue) { + d.setPointerAcceleration(initValue); + QCOMPARE(inputConfig.readEntry("PointerAcceleration", configValue), initValue); + } +} + +void TestLibinputDevice::testLoadPointerAccelerationProfile_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("initValuePropNameString"); + QTest::addColumn("configValue"); + QTest::addColumn("configValuePropNameString"); + + QTest::newRow("pointerAccelerationProfileFlat -> pointerAccelerationProfileAdaptive") + << (quint32)LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT << "pointerAccelerationProfileFlat" + << (quint32)LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE << "pointerAccelerationProfileAdaptive"; + QTest::newRow("pointerAccelerationProfileAdaptive -> pointerAccelerationProfileFlat") + << (quint32)LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE << "pointerAccelerationProfileAdaptive" + << (quint32)LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT << "pointerAccelerationProfileFlat"; + QTest::newRow("pointerAccelerationProfileAdaptive -> pointerAccelerationProfileAdaptive") + << (quint32)LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE << "pointerAccelerationProfileAdaptive" << (quint32)LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE << "pointerAccelerationProfileAdaptive"; +} + +void TestLibinputDevice::testLoadPointerAccelerationProfile() +{ + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup inputConfig(config, QStringLiteral("Test")); + QFETCH(quint32, initValue); + QFETCH(quint32, configValue); + QFETCH(QString, initValuePropNameString); + QFETCH(QString, configValuePropNameString); + + QByteArray initValuePropName = initValuePropNameString.toLatin1(); + QByteArray configValuePropName = configValuePropNameString.toLatin1(); + + inputConfig.writeEntry("PointerAccelerationProfile", configValue); + + libinput_device device; + device.supportedPointerAccelerationProfiles = LIBINPUT_CONFIG_ACCEL_PROFILE_FLAT | LIBINPUT_CONFIG_ACCEL_PROFILE_ADAPTIVE; + device.pointerAccelerationProfile = (libinput_config_accel_profile)initValue; + device.setPointerAccelerationProfileReturnValue = false; + + Device d(&device); + QCOMPARE(d.property(initValuePropName).toBool(), true); + QCOMPARE(d.property(configValuePropName).toBool(), initValue == configValue); + // no config group set, should not change + d.loadConfiguration(); + QCOMPARE(d.property(initValuePropName).toBool(), true); + QCOMPARE(d.property(configValuePropName).toBool(), initValue == configValue); + + // set the group + d.setConfig(inputConfig); + d.loadConfiguration(); + QCOMPARE(d.property(initValuePropName).toBool(), initValue == configValue); + QCOMPARE(d.property(configValuePropName).toBool(), true); + QCOMPARE(dbusProperty(d.sysName(), initValuePropName), initValue == configValue); + QCOMPARE(dbusProperty(d.sysName(), configValuePropName), true); + + // and try to store + if (configValue != initValue) { + d.setProperty(initValuePropName, true); + QCOMPARE(inputConfig.readEntry("PointerAccelerationProfile", configValue), initValue); + } +} + +void TestLibinputDevice::testLoadClickMethod_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("initValuePropNameString"); + QTest::addColumn("configValue"); + QTest::addColumn("configValuePropNameString"); + + QTest::newRow("clickMethodAreas -> clickMethodClickfinger") + << static_cast(LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS) << "clickMethodAreas" + << static_cast(LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER) << "clickMethodClickfinger"; + QTest::newRow("clickMethodClickfinger -> clickMethodAreas") + << static_cast(LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER) << "clickMethodClickfinger" + << static_cast(LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS) << "clickMethodAreas"; + QTest::newRow("clickMethodAreas -> clickMethodAreas") + << static_cast(LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS) << "clickMethodAreas" + << static_cast(LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS) << "clickMethodAreas"; + QTest::newRow("clickMethodClickfinger -> clickMethodClickfinger") + << static_cast(LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER) << "clickMethodClickfinger" + << static_cast(LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER) << "clickMethodClickfinger"; +} + +void TestLibinputDevice::testLoadClickMethod() +{ + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup inputConfig(config, QStringLiteral("Test")); + QFETCH(quint32, initValue); + QFETCH(quint32, configValue); + QFETCH(QString, initValuePropNameString); + QFETCH(QString, configValuePropNameString); + + QByteArray initValuePropName = initValuePropNameString.toLatin1(); + QByteArray configValuePropName = configValuePropNameString.toLatin1(); + + inputConfig.writeEntry("ClickMethod", configValue); + + libinput_device device; + device.supportedClickMethods = LIBINPUT_CONFIG_CLICK_METHOD_BUTTON_AREAS | LIBINPUT_CONFIG_CLICK_METHOD_CLICKFINGER; + device.clickMethod = (libinput_config_click_method)initValue; + device.setClickMethodReturnValue = false; + + Device d(&device); + QCOMPARE(d.property(initValuePropName).toBool(), true); + QCOMPARE(d.property(configValuePropName).toBool(), initValue == configValue); + // no config group set, should not change + d.loadConfiguration(); + QCOMPARE(d.property(initValuePropName).toBool(), true); + QCOMPARE(d.property(configValuePropName).toBool(), initValue == configValue); + + // set the group + d.setConfig(inputConfig); + d.loadConfiguration(); + QCOMPARE(d.property(initValuePropName).toBool(), initValue == configValue); + QCOMPARE(d.property(configValuePropName).toBool(), true); + QCOMPARE(dbusProperty(d.sysName(), initValuePropName), initValue == configValue); + QCOMPARE(dbusProperty(d.sysName(), configValuePropName), true); + + // and try to store + if (configValue != initValue) { + d.setProperty(initValuePropName, true); + QCOMPARE(inputConfig.readEntry("ClickMethod", configValue), initValue); + } +} + +void TestLibinputDevice::testLoadTapToClick_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("configValue"); + + QTest::newRow("false -> true") << false << true; + QTest::newRow("true -> false") << true << false; + QTest::newRow("true -> true") << true << true; + QTest::newRow("false -> false") << false << false; +} + +void TestLibinputDevice::testLoadTapToClick() +{ + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup inputConfig(config, QStringLiteral("Test")); + QFETCH(bool, configValue); + QFETCH(bool, initValue); + inputConfig.writeEntry("TapToClick", configValue); + + libinput_device device; + device.tapFingerCount = 2; + device.tapToClick = initValue; + device.setTapToClickReturnValue = false; + + Device d(&device); + QCOMPARE(d.isTapToClick(), initValue); + // no config group set, should not change + d.loadConfiguration(); + QCOMPARE(d.isTapToClick(), initValue); + + // set the group + d.setConfig(inputConfig); + d.loadConfiguration(); + QCOMPARE(d.isTapToClick(), configValue); + + // and try to store + if (configValue != initValue) { + d.setTapToClick(initValue); + QCOMPARE(inputConfig.readEntry("TapToClick", configValue), initValue); + } +} + +void TestLibinputDevice::testLoadTapAndDrag_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("configValue"); + + QTest::newRow("false -> true") << false << true; + QTest::newRow("true -> false") << true << false; + QTest::newRow("true -> true") << true << true; + QTest::newRow("false -> false") << false << false; +} + +void TestLibinputDevice::testLoadTapAndDrag() +{ + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup inputConfig(config, QStringLiteral("Test")); + QFETCH(bool, configValue); + QFETCH(bool, initValue); + inputConfig.writeEntry("TapAndDrag", configValue); + + libinput_device device; + device.tapAndDrag = initValue; + device.setTapAndDragReturnValue = false; + + Device d(&device); + QCOMPARE(d.isTapAndDrag(), initValue); + // no config group set, should not change + d.loadConfiguration(); + QCOMPARE(d.isTapAndDrag(), initValue); + + // set the group + d.setConfig(inputConfig); + d.loadConfiguration(); + QCOMPARE(d.isTapAndDrag(), configValue); + + // and try to store + if (configValue != initValue) { + d.setTapAndDrag(initValue); + QCOMPARE(inputConfig.readEntry("TapAndDrag", configValue), initValue); + } +} + +void TestLibinputDevice::testLoadTapDragLock_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("configValue"); + + QTest::newRow("false -> true") << false << true; + QTest::newRow("true -> false") << true << false; + QTest::newRow("true -> true") << true << true; + QTest::newRow("false -> false") << false << false; +} + +void TestLibinputDevice::testLoadTapDragLock() +{ + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup inputConfig(config, QStringLiteral("Test")); + QFETCH(bool, configValue); + QFETCH(bool, initValue); + inputConfig.writeEntry("TapDragLock", configValue); + + libinput_device device; + device.tapDragLock = initValue; + device.setTapDragLockReturnValue = false; + + Device d(&device); + QCOMPARE(d.isTapDragLock(), initValue); + // no config group set, should not change + d.loadConfiguration(); + QCOMPARE(d.isTapDragLock(), initValue); + + // set the group + d.setConfig(inputConfig); + d.loadConfiguration(); + QCOMPARE(d.isTapDragLock(), configValue); + + // and try to store + if (configValue != initValue) { + d.setTapDragLock(initValue); + QCOMPARE(inputConfig.readEntry("TapDragLock", configValue), initValue); + } +} + +void TestLibinputDevice::testLoadMiddleButtonEmulation_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("configValue"); + + QTest::newRow("false -> true") << false << true; + QTest::newRow("true -> false") << true << false; + QTest::newRow("true -> true") << true << true; + QTest::newRow("false -> false") << false << false; +} + +void TestLibinputDevice::testLoadMiddleButtonEmulation() +{ + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup inputConfig(config, QStringLiteral("Test")); + QFETCH(bool, configValue); + QFETCH(bool, initValue); + inputConfig.writeEntry("MiddleButtonEmulation", configValue); + + libinput_device device; + device.supportsMiddleEmulation = true; + device.middleEmulation = initValue; + device.setMiddleEmulationReturnValue = false; + + Device d(&device); + QCOMPARE(d.isMiddleEmulation(), initValue); + // no config group set, should not change + d.loadConfiguration(); + QCOMPARE(d.isMiddleEmulation(), initValue); + + // set the group + d.setConfig(inputConfig); + d.loadConfiguration(); + QCOMPARE(d.isMiddleEmulation(), configValue); + + // and try to store + if (configValue != initValue) { + d.setMiddleEmulation(initValue); + QCOMPARE(inputConfig.readEntry("MiddleButtonEmulation", configValue), initValue); + } +} + +void TestLibinputDevice::testLoadNaturalScroll_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("configValue"); + + QTest::newRow("false -> true") << false << true; + QTest::newRow("true -> false") << true << false; + QTest::newRow("true -> true") << true << true; + QTest::newRow("false -> false") << false << false; +} + +void TestLibinputDevice::testLoadNaturalScroll() +{ + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup inputConfig(config, QStringLiteral("Test")); + QFETCH(bool, configValue); + QFETCH(bool, initValue); + inputConfig.writeEntry("NaturalScroll", configValue); + + libinput_device device; + device.supportsNaturalScroll = true; + device.naturalScroll = initValue; + device.setNaturalScrollReturnValue = false; + + Device d(&device); + QCOMPARE(d.isNaturalScroll(), initValue); + // no config group set, should not change + d.loadConfiguration(); + QCOMPARE(d.isNaturalScroll(), initValue); + + // set the group + d.setConfig(inputConfig); + d.loadConfiguration(); + QCOMPARE(d.isNaturalScroll(), configValue); + + // and try to store + if (configValue != initValue) { + d.setNaturalScroll(initValue); + QCOMPARE(inputConfig.readEntry("NaturalScroll", configValue), initValue); + } +} + +void TestLibinputDevice::testLoadScrollMethod_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("initValuePropNameString"); + QTest::addColumn("configValue"); + QTest::addColumn("configValuePropNameString"); + + QTest::newRow("scrollTwoFinger -> scrollEdge") << (quint32)LIBINPUT_CONFIG_SCROLL_2FG << "scrollTwoFinger" << (quint32)LIBINPUT_CONFIG_SCROLL_EDGE << "scrollEdge"; + QTest::newRow("scrollOnButtonDown -> scrollTwoFinger") << (quint32)LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN << "scrollOnButtonDown" << (quint32)LIBINPUT_CONFIG_SCROLL_2FG << "scrollTwoFinger"; + QTest::newRow("scrollEdge -> scrollEdge") << (quint32)LIBINPUT_CONFIG_SCROLL_EDGE << "scrollEdge" << (quint32)LIBINPUT_CONFIG_SCROLL_EDGE << "scrollEdge"; +} + +void TestLibinputDevice::testLoadScrollMethod() +{ + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup inputConfig(config, QStringLiteral("Test")); + QFETCH(quint32, initValue); + QFETCH(quint32, configValue); + QFETCH(QString, initValuePropNameString); + QFETCH(QString, configValuePropNameString); + + QByteArray initValuePropName = initValuePropNameString.toLatin1(); + QByteArray configValuePropName = configValuePropNameString.toLatin1(); + + inputConfig.writeEntry("ScrollMethod", configValue); + + libinput_device device; + device.supportedScrollMethods = LIBINPUT_CONFIG_SCROLL_2FG | LIBINPUT_CONFIG_SCROLL_EDGE | LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; + device.scrollMethod = (libinput_config_scroll_method)initValue; + device.setScrollMethodReturnValue = false; + + Device d(&device); + QCOMPARE(d.property(initValuePropName).toBool(), true); + QCOMPARE(d.property(configValuePropName).toBool(), initValue == configValue); + // no config group set, should not change + d.loadConfiguration(); + QCOMPARE(d.property(initValuePropName).toBool(), true); + QCOMPARE(d.property(configValuePropName).toBool(), initValue == configValue); + + // set the group + d.setConfig(inputConfig); + d.loadConfiguration(); + QCOMPARE(d.property(initValuePropName).toBool(), initValue == configValue); + QCOMPARE(d.property(configValuePropName).toBool(), true); + + // and try to store + if (configValue != initValue) { + d.setProperty(initValuePropName, true); + QCOMPARE(inputConfig.readEntry("ScrollMethod", configValue), initValue); + } +} + +void TestLibinputDevice::testLoadScrollButton_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("configValue"); + + QTest::newRow("BTN_LEFT -> BTN_RIGHT") << quint32(BTN_LEFT) << quint32(BTN_RIGHT); + QTest::newRow("BTN_LEFT -> BTN_LEFT") << quint32(BTN_LEFT) << quint32(BTN_LEFT); +} + +void TestLibinputDevice::testLoadScrollButton() +{ + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup inputConfig(config, QStringLiteral("Test")); + QFETCH(quint32, configValue); + QFETCH(quint32, initValue); + inputConfig.writeEntry("ScrollButton", configValue); + + libinput_device device; + device.supportedScrollMethods = LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; + device.scrollMethod = LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN; + device.scrollButton = initValue; + device.setScrollButtonReturnValue = false; + + Device d(&device); + QCOMPARE(d.isScrollOnButtonDown(), true); + QCOMPARE(d.scrollButton(), initValue); + // no config group set, should not change + d.loadConfiguration(); + QCOMPARE(d.isScrollOnButtonDown(), true); + QCOMPARE(d.scrollButton(), initValue); + + // set the group + d.setConfig(inputConfig); + d.loadConfiguration(); + QCOMPARE(d.isScrollOnButtonDown(), true); + QCOMPARE(d.scrollButton(), configValue); + + // and try to store + if (configValue != initValue) { + d.setScrollButton(initValue); + QCOMPARE(inputConfig.readEntry("ScrollButton", configValue), initValue); + } +} + +void TestLibinputDevice::testLoadLeftHanded_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("configValue"); + + QTest::newRow("false -> true") << false << true; + QTest::newRow("true -> false") << true << false; + QTest::newRow("true -> true") << true << true; + QTest::newRow("false -> false") << false << false; +} + +void TestLibinputDevice::testLoadLeftHanded() +{ + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup inputConfig(config, QStringLiteral("Test")); + QFETCH(bool, configValue); + QFETCH(bool, initValue); + inputConfig.writeEntry("LeftHanded", configValue); + + libinput_device device; + device.supportsLeftHanded = true; + device.leftHanded = initValue; + device.setLeftHandedReturnValue = false; + + Device d(&device); + QCOMPARE(d.isLeftHanded(), initValue); + // no config group set, should not change + d.loadConfiguration(); + QCOMPARE(d.isLeftHanded(), initValue); + + // set the group + d.setConfig(inputConfig); + d.loadConfiguration(); + QCOMPARE(d.isLeftHanded(), configValue); + + // and try to store + if (configValue != initValue) { + d.setLeftHanded(initValue); + QCOMPARE(inputConfig.readEntry("LeftHanded", configValue), initValue); + } +} + +void TestLibinputDevice::testLoadDisableWhileTyping_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("configValue"); + + QTest::newRow("false -> true") << false << true; + QTest::newRow("true -> false") << true << false; + QTest::newRow("true -> true") << true << true; + QTest::newRow("false -> false") << false << false; +} + +void TestLibinputDevice::testLoadDisableWhileTyping() +{ + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup inputConfig(config, QStringLiteral("Test")); + QFETCH(bool, configValue); + QFETCH(bool, initValue); + inputConfig.writeEntry("DisableWhileTyping", configValue); + + libinput_device device; + device.supportsDisableWhileTyping = true; + device.disableWhileTyping = initValue ? LIBINPUT_CONFIG_DWT_ENABLED : LIBINPUT_CONFIG_DWT_DISABLED; + device.setDisableWhileTypingReturnValue = false; + + Device d(&device); + QCOMPARE(d.isDisableWhileTyping(), initValue); + // no config group set, should not change + d.loadConfiguration(); + QCOMPARE(d.isDisableWhileTyping(), initValue); + + // set the group + d.setConfig(inputConfig); + d.loadConfiguration(); + QCOMPARE(d.isDisableWhileTyping(), configValue); + + // and try to store + if (configValue != initValue) { + d.setDisableWhileTyping(initValue); + QCOMPARE(inputConfig.readEntry("DisableWhileTyping", configValue), initValue); + } +} + +void TestLibinputDevice::testLoadLmrTapButtonMap_data() +{ + QTest::addColumn("initValue"); + QTest::addColumn("configValue"); + + QTest::newRow("false -> true") << false << true; + QTest::newRow("true -> false") << true << false; + QTest::newRow("true -> true") << true << true; + QTest::newRow("false -> false") << false << false; +} + +void TestLibinputDevice::testLoadLmrTapButtonMap() +{ + auto config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + KConfigGroup inputConfig(config, QStringLiteral("Test")); + QFETCH(bool, configValue); + QFETCH(bool, initValue); + inputConfig.writeEntry("LmrTapButtonMap", configValue); + + libinput_device device; + device.tapFingerCount = 3; + device.tapButtonMap = initValue ? LIBINPUT_CONFIG_TAP_MAP_LMR : LIBINPUT_CONFIG_TAP_MAP_LRM; + device.setTapButtonMapReturnValue = false; + + Device d(&device); + QCOMPARE(d.lmrTapButtonMap(), initValue); + // no config group set, should not change + d.loadConfiguration(); + QCOMPARE(d.lmrTapButtonMap(), initValue); + QCOMPARE(dbusProperty(d.sysName(), "lmrTapButtonMap"), initValue); + + // set the group + d.setConfig(inputConfig); + d.loadConfiguration(); + QCOMPARE(d.lmrTapButtonMap(), configValue); + QCOMPARE(dbusProperty(d.sysName(), "lmrTapButtonMap"), configValue); + + // and try to store + if (configValue != initValue) { + d.setLmrTapButtonMap(initValue); + QCOMPARE(inputConfig.readEntry("LmrTapButtonMap", configValue), initValue); + } +} + +void TestLibinputDevice::testOrientation_data() +{ + QTest::addColumn("orientation"); + QTest::addColumn("m11"); + QTest::addColumn("m12"); + QTest::addColumn("m13"); + QTest::addColumn("m21"); + QTest::addColumn("m22"); + QTest::addColumn("m23"); + QTest::addColumn("defaultIsIdentity"); + + QTest::newRow("Primary") << Qt::PrimaryOrientation << 1.0f << 2.0f << 3.0f << 4.0f << 5.0f << 6.0f << false; + QTest::newRow("Landscape") << Qt::LandscapeOrientation << 1.0f << 2.0f << 3.0f << 4.0f << 5.0f << 6.0f << false; + QTest::newRow("Portrait") << Qt::PortraitOrientation << 0.0f << -1.0f << 1.0f << 1.0f << 0.0f << 0.0f << true; + QTest::newRow("InvertedLandscape") << Qt::InvertedLandscapeOrientation << -1.0f << 0.0f << 1.0f << 0.0f << -1.0f << 1.0f << true; + QTest::newRow("InvertedPortrait") << Qt::InvertedPortraitOrientation << 0.0f << 1.0f << 0.0f << -1.0f << 0.0f << 1.0f << true; +} + +void TestLibinputDevice::testOrientation() +{ + libinput_device device; + device.supportsCalibrationMatrix = true; + device.defaultCalibrationMatrix = std::array{{1.0, 2.0, 3.0, 4.0, 5.0, 6.0}}; + QFETCH(bool, defaultIsIdentity); + device.defaultCalibrationMatrixIsIdentity = defaultIsIdentity; + Device d(&device); + QFETCH(Qt::ScreenOrientation, orientation); + d.setOrientation(orientation); + QTEST(device.calibrationMatrix[0], "m11"); + QTEST(device.calibrationMatrix[1], "m12"); + QTEST(device.calibrationMatrix[2], "m13"); + QTEST(device.calibrationMatrix[3], "m21"); + QTEST(device.calibrationMatrix[4], "m22"); + QTEST(device.calibrationMatrix[5], "m23"); +} + +void TestLibinputDevice::testCalibrationWithDefault() +{ + libinput_device device; + device.supportsCalibrationMatrix = true; + device.defaultCalibrationMatrix = std::array{{2.0, 3.0, 0.0, 4.0, 5.0, 0.0}}; + device.defaultCalibrationMatrixIsIdentity = false; + Device d(&device); + d.setOrientation(Qt::PortraitOrientation); + QCOMPARE(device.calibrationMatrix[0], 3.0f); + QCOMPARE(device.calibrationMatrix[1], -2.0f); + QCOMPARE(device.calibrationMatrix[2], 2.0f); + QCOMPARE(device.calibrationMatrix[3], 5.0f); + QCOMPARE(device.calibrationMatrix[4], -4.0f); + QCOMPARE(device.calibrationMatrix[5], 4.0f); +} + +void TestLibinputDevice::testSwitch_data() +{ + QTest::addColumn("lid"); + QTest::addColumn("tablet"); + + QTest::newRow("lid") << true << false; + QTest::newRow("tablet") << false << true; +} + +void TestLibinputDevice::testSwitch() +{ + libinput_device device; + device.switchDevice = true; + QFETCH(bool, lid); + QFETCH(bool, tablet); + device.lidSwitch = lid; + device.tabletModeSwitch = tablet; + + Device d(&device); + QCOMPARE(d.isSwitch(), true); + QCOMPARE(d.isLidSwitch(), lid); + QCOMPARE(d.property("lidSwitch").toBool(), lid); + QCOMPARE(dbusProperty(d.sysName(), "lidSwitch"), lid); + QCOMPARE(d.isTabletModeSwitch(), tablet); + QCOMPARE(d.property("tabletModeSwitch").toBool(), tablet); + QCOMPARE(dbusProperty(d.sysName(), "tabletModeSwitch"), tablet); +} + +QTEST_GUILESS_MAIN(TestLibinputDevice) +#include "device_test.moc" diff --git a/autotests/libinput/gesture_event_test.cpp b/autotests/libinput/gesture_event_test.cpp new file mode 100644 index 0000000..d07d838 --- /dev/null +++ b/autotests/libinput/gesture_event_test.cpp @@ -0,0 +1,204 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "mock_libinput.h" + +#include "backends/libinput/device.h" +#include "backends/libinput/events.h" + +#include + +#include + +Q_DECLARE_METATYPE(libinput_event_type) + +using namespace KWin::LibInput; + +class TestLibinputGestureEvent : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void init(); + void cleanup(); + + void testType_data(); + void testType(); + + void testStart_data(); + void testStart(); + + void testSwipeUpdate(); + void testPinchUpdate(); + + void testEnd_data(); + void testEnd(); + +private: + libinput_device *m_nativeDevice = nullptr; + Device *m_device = nullptr; +}; + +void TestLibinputGestureEvent::init() +{ + m_nativeDevice = new libinput_device; + m_nativeDevice->pointer = true; + m_nativeDevice->gestureSupported = true; + m_nativeDevice->deviceSize = QSizeF(12.5, 13.8); + m_device = new Device(m_nativeDevice); +} + +void TestLibinputGestureEvent::cleanup() +{ + delete m_device; + m_device = nullptr; + + delete m_nativeDevice; + m_nativeDevice = nullptr; +} + +void TestLibinputGestureEvent::testType_data() +{ + QTest::addColumn("type"); + + QTest::newRow("pinch-start") << LIBINPUT_EVENT_GESTURE_PINCH_BEGIN; + QTest::newRow("pinch-update") << LIBINPUT_EVENT_GESTURE_PINCH_UPDATE; + QTest::newRow("pinch-end") << LIBINPUT_EVENT_GESTURE_PINCH_END; + QTest::newRow("swipe-start") << LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN; + QTest::newRow("swipe-update") << LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE; + QTest::newRow("swipe-end") << LIBINPUT_EVENT_GESTURE_SWIPE_END; +} + +void TestLibinputGestureEvent::testType() +{ + // this test verifies the initialization of a PointerEvent and the parent Event class + libinput_event_gesture *gestureEvent = new libinput_event_gesture; + QFETCH(libinput_event_type, type); + gestureEvent->type = type; + gestureEvent->device = m_nativeDevice; + + std::unique_ptr event(Event::create(gestureEvent)); + // API of event + QCOMPARE(event->type(), type); + QCOMPARE(event->device(), m_device); + QCOMPARE(event->nativeDevice(), m_nativeDevice); + QCOMPARE((libinput_event *)(*event.get()), gestureEvent); + // verify it's a pointer event + QVERIFY(dynamic_cast(event.get())); + QCOMPARE((libinput_event_gesture *)(*dynamic_cast(event.get())), gestureEvent); +} + +void TestLibinputGestureEvent::testStart_data() +{ + QTest::addColumn("type"); + + QTest::newRow("pinch") << LIBINPUT_EVENT_GESTURE_PINCH_BEGIN; + QTest::newRow("swipe") << LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN; +} + +void TestLibinputGestureEvent::testStart() +{ + libinput_event_gesture *gestureEvent = new libinput_event_gesture; + gestureEvent->device = m_nativeDevice; + QFETCH(libinput_event_type, type); + gestureEvent->type = type; + gestureEvent->fingerCount = 3; + gestureEvent->time = 100u; + + std::unique_ptr event(Event::create(gestureEvent)); + auto ge = dynamic_cast(event.get()); + QVERIFY(ge); + QCOMPARE(ge->fingerCount(), gestureEvent->fingerCount); + QVERIFY(!ge->isCancelled()); + QCOMPARE(ge->time(), gestureEvent->time); + QCOMPARE(ge->delta(), QPointF(0, 0)); + if (ge->type() == LIBINPUT_EVENT_GESTURE_PINCH_BEGIN) { + auto pe = dynamic_cast(event.get()); + QCOMPARE(pe->scale(), 1.0); + QCOMPARE(pe->angleDelta(), 0.0); + } +} + +void TestLibinputGestureEvent::testSwipeUpdate() +{ + libinput_event_gesture *gestureEvent = new libinput_event_gesture; + gestureEvent->device = m_nativeDevice; + gestureEvent->type = LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE; + gestureEvent->fingerCount = 2; + gestureEvent->time = 200u; + gestureEvent->delta = QPointF(2, 3); + + std::unique_ptr event(Event::create(gestureEvent)); + auto se = dynamic_cast(event.get()); + QVERIFY(se); + QCOMPARE(se->fingerCount(), gestureEvent->fingerCount); + QVERIFY(!se->isCancelled()); + QCOMPARE(se->time(), gestureEvent->time); + QCOMPARE(se->delta(), QPointF(2, 3)); +} + +void TestLibinputGestureEvent::testPinchUpdate() +{ + libinput_event_gesture *gestureEvent = new libinput_event_gesture; + gestureEvent->device = m_nativeDevice; + gestureEvent->type = LIBINPUT_EVENT_GESTURE_PINCH_UPDATE; + gestureEvent->fingerCount = 4; + gestureEvent->time = 600u; + gestureEvent->delta = QPointF(5, 4); + gestureEvent->scale = 2; + gestureEvent->angleDelta = -30; + + std::unique_ptr event(Event::create(gestureEvent)); + auto pe = dynamic_cast(event.get()); + QVERIFY(pe); + QCOMPARE(pe->fingerCount(), gestureEvent->fingerCount); + QVERIFY(!pe->isCancelled()); + QCOMPARE(pe->time(), gestureEvent->time); + QCOMPARE(pe->delta(), QPointF(5, 4)); + QCOMPARE(pe->scale(), gestureEvent->scale); + QCOMPARE(pe->angleDelta(), gestureEvent->angleDelta); +} + +void TestLibinputGestureEvent::testEnd_data() +{ + QTest::addColumn("type"); + QTest::addColumn("cancelled"); + + QTest::newRow("pinch/not cancelled") << LIBINPUT_EVENT_GESTURE_PINCH_END << false; + QTest::newRow("pinch/cancelled") << LIBINPUT_EVENT_GESTURE_PINCH_END << true; + QTest::newRow("swipe/not cancelled") << LIBINPUT_EVENT_GESTURE_SWIPE_END << false; + QTest::newRow("swipe/cancelled") << LIBINPUT_EVENT_GESTURE_SWIPE_END << true; +} + +void TestLibinputGestureEvent::testEnd() +{ + libinput_event_gesture *gestureEvent = new libinput_event_gesture; + gestureEvent->device = m_nativeDevice; + QFETCH(libinput_event_type, type); + gestureEvent->type = type; + gestureEvent->fingerCount = 4; + QFETCH(bool, cancelled); + gestureEvent->cancelled = cancelled; + gestureEvent->time = 300u; + gestureEvent->scale = 3; + + std::unique_ptr event(Event::create(gestureEvent)); + auto ge = dynamic_cast(event.get()); + QVERIFY(ge); + QCOMPARE(ge->fingerCount(), gestureEvent->fingerCount); + QCOMPARE(ge->isCancelled(), cancelled); + QCOMPARE(ge->time(), gestureEvent->time); + QCOMPARE(ge->delta(), QPointF(0, 0)); + if (ge->type() == LIBINPUT_EVENT_GESTURE_PINCH_END) { + auto pe = dynamic_cast(event.get()); + QCOMPARE(pe->scale(), gestureEvent->scale); + QCOMPARE(pe->angleDelta(), 0.0); + } +} + +QTEST_GUILESS_MAIN(TestLibinputGestureEvent) +#include "gesture_event_test.moc" diff --git a/autotests/libinput/input_event_test.cpp b/autotests/libinput/input_event_test.cpp new file mode 100644 index 0000000..f998534 --- /dev/null +++ b/autotests/libinput/input_event_test.cpp @@ -0,0 +1,179 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "mock_libinput.h" + +#include "backends/libinput/device.h" +#include "input_event.h" + +#include + +Q_DECLARE_METATYPE(KWin::SwitchEvent::State); + +using namespace KWin; +using namespace KWin::LibInput; + +class InputEventsTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testInitMouseEvent_data(); + void testInitMouseEvent(); + void testInitKeyEvent_data(); + void testInitKeyEvent(); + void testInitWheelEvent_data(); + void testInitWheelEvent(); + void testInitSwitchEvent_data(); + void testInitSwitchEvent(); +}; + +void InputEventsTest::testInitMouseEvent_data() +{ + QTest::addColumn("type"); + + QTest::newRow("Press") << QEvent::MouseButtonPress; + QTest::newRow("Release") << QEvent::MouseButtonRelease; + QTest::newRow("Move") << QEvent::MouseMove; +} + +void InputEventsTest::testInitMouseEvent() +{ + // this test verifies that a MouseEvent is constructed correctly + + // first create the test LibInput::Device + libinput_device device; + Device d(&device); + + QFETCH(QEvent::Type, type); + // now create our own event + MouseEvent event(type, QPointF(100, 200), Qt::LeftButton, Qt::LeftButton | Qt::RightButton, + Qt::ShiftModifier | Qt::ControlModifier, 300, QPointF(1, 2), QPointF(3, 4), quint64(-1), &d); + // and verify the contract of QMouseEvent + QCOMPARE(event.type(), type); + QCOMPARE(event.globalPos(), QPoint(100, 200)); + QCOMPARE(event.screenPos(), QPointF(100, 200)); + QCOMPARE(event.localPos(), QPointF(100, 200)); + QCOMPARE(event.button(), Qt::LeftButton); + QCOMPARE(event.buttons(), Qt::LeftButton | Qt::RightButton); + QCOMPARE(event.modifiers(), Qt::ShiftModifier | Qt::ControlModifier); + QCOMPARE(event.timestamp(), 300ul); + // and our custom argument + QCOMPARE(event.device(), &d); + QCOMPARE(event.delta(), QPointF(1, 2)); + QCOMPARE(event.deltaUnaccelerated(), QPointF(3, 4)); + QCOMPARE(event.timestampMicroseconds(), quint64(-1)); +} + +void InputEventsTest::testInitKeyEvent_data() +{ + QTest::addColumn("type"); + QTest::addColumn("autorepeat"); + + QTest::newRow("Press") << QEvent::KeyPress << false; + QTest::newRow("Repeat") << QEvent::KeyPress << true; + QTest::newRow("Release") << QEvent::KeyRelease << false; +} + +void InputEventsTest::testInitKeyEvent() +{ + // this test verifies that a KeyEvent is constructed correctly + + // first create the test LibInput::Device + libinput_device device; + Device d(&device); + + // setup event + QFETCH(QEvent::Type, type); + QFETCH(bool, autorepeat); + KeyEvent event(type, Qt::Key_Space, Qt::ShiftModifier | Qt::ControlModifier, 200, 300, + QStringLiteral(" "), autorepeat, 400, &d); + // and verify the contract of QKeyEvent + QCOMPARE(event.type(), type); + QCOMPARE(event.isAutoRepeat(), autorepeat); + QCOMPARE(event.key(), int(Qt::Key_Space)); + QCOMPARE(event.nativeScanCode(), 200u); + QCOMPARE(event.nativeVirtualKey(), 300u); + QCOMPARE(event.text(), QStringLiteral(" ")); + QCOMPARE(event.count(), 1); + QCOMPARE(event.nativeModifiers(), 0u); + QCOMPARE(event.modifiers(), Qt::ShiftModifier | Qt::ControlModifier); + QCOMPARE(event.timestamp(), 400ul); + // and our custom argument + QCOMPARE(event.device(), &d); +} + +void InputEventsTest::testInitWheelEvent_data() +{ + QTest::addColumn("orientation"); + QTest::addColumn("delta"); + QTest::addColumn("discreteDelta"); + QTest::addColumn("expectedAngleDelta"); + + QTest::newRow("horiz") << Qt::Horizontal << 3.3 << 1 << QPoint(3, 0); + QTest::newRow("vert") << Qt::Vertical << 2.4 << 2 << QPoint(0, 2); +} + +void InputEventsTest::testInitWheelEvent() +{ + // this test verifies that a WheelEvent is constructed correctly + + // first create the test LibInput::Device + libinput_device device; + Device d(&device); + + // setup event + QFETCH(Qt::Orientation, orientation); + QFETCH(qreal, delta); + QFETCH(qint32, discreteDelta); + WheelEvent event(QPointF(100, 200), delta, discreteDelta, orientation, Qt::LeftButton | Qt::RightButton, + Qt::ShiftModifier | Qt::ControlModifier, InputRedirection::PointerAxisSourceWheel, 300, &d); + // compare QWheelEvent contract + QCOMPARE(event.type(), QEvent::Wheel); + QCOMPARE(event.position(), QPointF(100, 200)); + QCOMPARE(event.globalPosition(), QPointF(100, 200)); + QCOMPARE(event.buttons(), Qt::LeftButton | Qt::RightButton); + QCOMPARE(event.modifiers(), Qt::ShiftModifier | Qt::ControlModifier); + QCOMPARE(event.timestamp(), 300ul); + QTEST(event.angleDelta(), "expectedAngleDelta"); + QTEST(event.orientation(), "orientation"); + QTEST(event.delta(), "delta"); + QTEST(event.discreteDelta(), "discreteDelta"); + QCOMPARE(event.axisSource(), InputRedirection::PointerAxisSourceWheel); + // and our custom argument + QCOMPARE(event.device(), &d); +} + +void InputEventsTest::testInitSwitchEvent_data() +{ + QTest::addColumn("state"); + QTest::addColumn("timestamp"); + QTest::addColumn("micro"); + + QTest::newRow("on") << SwitchEvent::State::On << 23u << quint64{23456790}; + QTest::newRow("off") << SwitchEvent::State::Off << 456892u << quint64{45689235987}; +} + +void InputEventsTest::testInitSwitchEvent() +{ + // this test verifies that a SwitchEvent is constructed correctly + libinput_device device; + Device d(&device); + + QFETCH(SwitchEvent::State, state); + QFETCH(quint32, timestamp); + QFETCH(quint64, micro); + SwitchEvent event(state, timestamp, micro, &d); + + QCOMPARE(event.state(), state); + QCOMPARE(event.timestamp(), ulong(timestamp)); + QCOMPARE(event.timestampMicroseconds(), micro); + QCOMPARE(event.device(), &d); +} + +QTEST_GUILESS_MAIN(InputEventsTest) +#include "input_event_test.moc" diff --git a/autotests/libinput/key_event_test.cpp b/autotests/libinput/key_event_test.cpp new file mode 100644 index 0000000..0b8e2c3 --- /dev/null +++ b/autotests/libinput/key_event_test.cpp @@ -0,0 +1,106 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "mock_libinput.h" + +#include "backends/libinput/device.h" +#include "backends/libinput/events.h" + +#include + +#include + +Q_DECLARE_METATYPE(libinput_key_state) + +using namespace KWin::LibInput; + +class TestLibinputKeyEvent : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void init(); + void cleanup(); + + void testCreate(); + void testEvent_data(); + void testEvent(); + +private: + libinput_device *m_nativeDevice = nullptr; + Device *m_device = nullptr; +}; + +void TestLibinputKeyEvent::init() +{ + m_nativeDevice = new libinput_device; + m_nativeDevice->keyboard = true; + m_device = new Device(m_nativeDevice); +} + +void TestLibinputKeyEvent::cleanup() +{ + delete m_device; + m_device = nullptr; + + delete m_nativeDevice; + m_nativeDevice = nullptr; +} + +void TestLibinputKeyEvent::testCreate() +{ + // this test verifies the initialisation of a KeyEvent and the parent Event class + libinput_event_keyboard *keyEvent = new libinput_event_keyboard; + keyEvent->device = m_nativeDevice; + + std::unique_ptr event{Event::create(keyEvent)}; + // API of event + QCOMPARE(event->type(), LIBINPUT_EVENT_KEYBOARD_KEY); + QCOMPARE(event->device(), m_device); + QCOMPARE(event->nativeDevice(), m_nativeDevice); + QCOMPARE((libinput_event *)(*event.get()), keyEvent); + // verify it's a key event + QVERIFY(dynamic_cast(event.get())); + QCOMPARE((libinput_event_keyboard *)(*dynamic_cast(event.get())), keyEvent); + + // verify that a nullptr passed to Event::create returns a nullptr + QVERIFY(!Event::create(nullptr)); +} + +void TestLibinputKeyEvent::testEvent_data() +{ + QTest::addColumn("keyState"); + QTest::addColumn("expectedKeyState"); + QTest::addColumn("key"); + QTest::addColumn("time"); + + QTest::newRow("pressed") << LIBINPUT_KEY_STATE_PRESSED << KWin::InputRedirection::KeyboardKeyPressed << quint32(KEY_A) << 100u; + QTest::newRow("released") << LIBINPUT_KEY_STATE_RELEASED << KWin::InputRedirection::KeyboardKeyReleased << quint32(KEY_B) << 200u; +} + +void TestLibinputKeyEvent::testEvent() +{ + // this test verifies the key press/release + libinput_event_keyboard *keyEvent = new libinput_event_keyboard; + keyEvent->device = m_nativeDevice; + QFETCH(libinput_key_state, keyState); + keyEvent->state = keyState; + QFETCH(quint32, key); + keyEvent->key = key; + QFETCH(quint32, time); + keyEvent->time = time; + + std::unique_ptr event(Event::create(keyEvent)); + auto ke = dynamic_cast(event.get()); + QVERIFY(ke); + QTEST(ke->state(), "expectedKeyState"); + QCOMPARE(ke->key(), key); + QCOMPARE(ke->time(), time); +} + +QTEST_GUILESS_MAIN(TestLibinputKeyEvent) +#include "key_event_test.moc" diff --git a/autotests/libinput/mock_libinput.cpp b/autotests/libinput/mock_libinput.cpp new file mode 100644 index 0000000..40c3aae --- /dev/null +++ b/autotests/libinput/mock_libinput.cpp @@ -0,0 +1,989 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include + +#include "mock_libinput.h" + +#include + +int libinput_device_keyboard_has_key(struct libinput_device *device, uint32_t code) +{ + return device->keys.contains(code); +} + +int libinput_device_has_capability(struct libinput_device *device, enum libinput_device_capability capability) +{ + switch (capability) { + case LIBINPUT_DEVICE_CAP_KEYBOARD: + return device->keyboard; + case LIBINPUT_DEVICE_CAP_POINTER: + return device->pointer; + case LIBINPUT_DEVICE_CAP_TOUCH: + return device->touch; + case LIBINPUT_DEVICE_CAP_GESTURE: + return device->gestureSupported; + case LIBINPUT_DEVICE_CAP_TABLET_TOOL: + return device->tabletTool; + case LIBINPUT_DEVICE_CAP_SWITCH: + return device->switchDevice; + default: + return 0; + } +} + +const char *libinput_device_get_name(struct libinput_device *device) +{ + return device->name.constData(); +} + +const char *libinput_device_get_sysname(struct libinput_device *device) +{ + return device->sysName.constData(); +} + +const char *libinput_device_get_output_name(struct libinput_device *device) +{ + return device->outputName.constData(); +} + +unsigned int libinput_device_get_id_product(struct libinput_device *device) +{ + return device->product; +} + +unsigned int libinput_device_get_id_vendor(struct libinput_device *device) +{ + return device->vendor; +} + +int libinput_device_config_tap_get_finger_count(struct libinput_device *device) +{ + return device->tapFingerCount; +} + +enum libinput_config_tap_state libinput_device_config_tap_get_enabled(struct libinput_device *device) +{ + if (device->tapToClick) { + return LIBINPUT_CONFIG_TAP_ENABLED; + } else { + return LIBINPUT_CONFIG_TAP_DISABLED; + } +} + +enum libinput_config_status libinput_device_config_tap_set_enabled(struct libinput_device *device, enum libinput_config_tap_state enable) +{ + if (device->setTapToClickReturnValue == 0) { + device->tapToClick = (enable == LIBINPUT_CONFIG_TAP_ENABLED); + return LIBINPUT_CONFIG_STATUS_SUCCESS; + } + return LIBINPUT_CONFIG_STATUS_INVALID; +} + +enum libinput_config_tap_state libinput_device_config_tap_get_default_enabled(struct libinput_device *device) +{ + if (device->tapEnabledByDefault) { + return LIBINPUT_CONFIG_TAP_ENABLED; + } else { + return LIBINPUT_CONFIG_TAP_DISABLED; + } +} + +enum libinput_config_drag_state libinput_device_config_tap_get_default_drag_enabled(struct libinput_device *device) +{ + if (device->tapAndDragEnabledByDefault) { + return LIBINPUT_CONFIG_DRAG_ENABLED; + } else { + return LIBINPUT_CONFIG_DRAG_DISABLED; + } +} + +enum libinput_config_drag_state libinput_device_config_tap_get_drag_enabled(struct libinput_device *device) +{ + if (device->tapAndDrag) { + return LIBINPUT_CONFIG_DRAG_ENABLED; + } else { + return LIBINPUT_CONFIG_DRAG_DISABLED; + } +} + +enum libinput_config_status libinput_device_config_tap_set_drag_enabled(struct libinput_device *device, enum libinput_config_drag_state enable) +{ + if (device->setTapAndDragReturnValue == 0) { + device->tapAndDrag = (enable == LIBINPUT_CONFIG_DRAG_ENABLED); + return LIBINPUT_CONFIG_STATUS_SUCCESS; + } + return LIBINPUT_CONFIG_STATUS_INVALID; +} + +enum libinput_config_drag_lock_state libinput_device_config_tap_get_default_drag_lock_enabled(struct libinput_device *device) +{ + if (device->tapDragLockEnabledByDefault) { + return LIBINPUT_CONFIG_DRAG_LOCK_ENABLED; + } else { + return LIBINPUT_CONFIG_DRAG_LOCK_DISABLED; + } +} + +enum libinput_config_drag_lock_state libinput_device_config_tap_get_drag_lock_enabled(struct libinput_device *device) +{ + if (device->tapDragLock) { + return LIBINPUT_CONFIG_DRAG_LOCK_ENABLED; + } else { + return LIBINPUT_CONFIG_DRAG_LOCK_DISABLED; + } +} + +enum libinput_config_status libinput_device_config_tap_set_drag_lock_enabled(struct libinput_device *device, enum libinput_config_drag_lock_state enable) +{ + if (device->setTapDragLockReturnValue == 0) { + device->tapDragLock = (enable == LIBINPUT_CONFIG_DRAG_LOCK_ENABLED); + return LIBINPUT_CONFIG_STATUS_SUCCESS; + } + return LIBINPUT_CONFIG_STATUS_INVALID; +} + +int libinput_device_config_dwt_is_available(struct libinput_device *device) +{ + return device->supportsDisableWhileTyping; +} + +enum libinput_config_status libinput_device_config_dwt_set_enabled(struct libinput_device *device, enum libinput_config_dwt_state state) +{ + if (device->setDisableWhileTypingReturnValue == 0) { + if (!device->supportsDisableWhileTyping) { + return LIBINPUT_CONFIG_STATUS_INVALID; + } + device->disableWhileTyping = state; + return LIBINPUT_CONFIG_STATUS_SUCCESS; + } + return LIBINPUT_CONFIG_STATUS_INVALID; +} + +enum libinput_config_dwt_state libinput_device_config_dwt_get_enabled(struct libinput_device *device) +{ + return device->disableWhileTyping; +} + +enum libinput_config_dwt_state libinput_device_config_dwt_get_default_enabled(struct libinput_device *device) +{ + return device->disableWhileTypingEnabledByDefault; +} + +int libinput_device_config_accel_is_available(struct libinput_device *device) +{ + return device->supportsPointerAcceleration; +} + +int libinput_device_config_calibration_has_matrix(struct libinput_device *device) +{ + return device->supportsCalibrationMatrix; +} + +enum libinput_config_status libinput_device_config_calibration_set_matrix(struct libinput_device *device, const float matrix[6]) +{ + for (std::size_t i = 0; i < 6; i++) { + device->calibrationMatrix[i] = matrix[i]; + } + return LIBINPUT_CONFIG_STATUS_SUCCESS; +} + +int libinput_device_config_calibration_get_default_matrix(struct libinput_device *device, float matrix[6]) +{ + for (std::size_t i = 0; i < 6; i++) { + matrix[i] = device->defaultCalibrationMatrix[i]; + } + return device->defaultCalibrationMatrixIsIdentity ? 0 : 1; +} + +int libinput_device_config_calibration_get_matrix(struct libinput_device *device, float matrix[6]) +{ + for (std::size_t i = 0; i < 6; i++) { + matrix[i] = device->calibrationMatrix[i]; + } + return device->calibrationMatrixIsIdentity ? 0 : 1; +} + +int libinput_device_config_left_handed_is_available(struct libinput_device *device) +{ + return device->supportsLeftHanded; +} + +uint32_t libinput_device_config_send_events_get_modes(struct libinput_device *device) +{ + uint32_t modes = LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; + if (device->supportsDisableEvents) { + modes |= LIBINPUT_CONFIG_SEND_EVENTS_DISABLED; + } + if (device->supportsDisableEventsOnExternalMouse) { + modes |= LIBINPUT_CONFIG_SEND_EVENTS_DISABLED_ON_EXTERNAL_MOUSE; + } + return modes; +} + +int libinput_device_config_left_handed_get(struct libinput_device *device) +{ + return device->leftHanded; +} + +double libinput_device_config_accel_get_default_speed(struct libinput_device *device) +{ + return device->defaultPointerAcceleration; +} + +int libinput_device_config_left_handed_get_default(struct libinput_device *device) +{ + return device->leftHandedEnabledByDefault; +} + +double libinput_device_config_accel_get_speed(struct libinput_device *device) +{ + return device->pointerAcceleration; +} + +uint32_t libinput_device_config_accel_get_profiles(struct libinput_device *device) +{ + return device->supportedPointerAccelerationProfiles; +} + +enum libinput_config_accel_profile libinput_device_config_accel_get_default_profile(struct libinput_device *device) +{ + return device->defaultPointerAccelerationProfile; +} + +enum libinput_config_status libinput_device_config_accel_set_profile(struct libinput_device *device, enum libinput_config_accel_profile profile) +{ + if (device->setPointerAccelerationProfileReturnValue == 0) { + if (!(device->supportedPointerAccelerationProfiles & profile) && profile != LIBINPUT_CONFIG_ACCEL_PROFILE_NONE) { + return LIBINPUT_CONFIG_STATUS_INVALID; + } + device->pointerAccelerationProfile = profile; + return LIBINPUT_CONFIG_STATUS_SUCCESS; + } + return LIBINPUT_CONFIG_STATUS_INVALID; +} + +enum libinput_config_accel_profile libinput_device_config_accel_get_profile(struct libinput_device *device) +{ + return device->pointerAccelerationProfile; +} + +uint32_t libinput_device_config_click_get_methods(struct libinput_device *device) +{ + return device->supportedClickMethods; +} + +enum libinput_config_click_method libinput_device_config_click_get_default_method(struct libinput_device *device) +{ + return device->defaultClickMethod; +} + +enum libinput_config_click_method libinput_device_config_click_get_method(struct libinput_device *device) +{ + return device->clickMethod; +} + +enum libinput_config_status libinput_device_config_click_set_method(struct libinput_device *device, enum libinput_config_click_method method) +{ + if (device->setClickMethodReturnValue == 0) { + if (!(device->supportedClickMethods & method) && method != LIBINPUT_CONFIG_CLICK_METHOD_NONE) { + return LIBINPUT_CONFIG_STATUS_INVALID; + } + device->clickMethod = method; + return LIBINPUT_CONFIG_STATUS_SUCCESS; + } + return LIBINPUT_CONFIG_STATUS_INVALID; +} + +uint32_t libinput_device_config_send_events_get_mode(struct libinput_device *device) +{ + if (device->enabled) { + return LIBINPUT_CONFIG_SEND_EVENTS_ENABLED; + } else { + // TODO: disabled on eternal mouse + return LIBINPUT_CONFIG_SEND_EVENTS_DISABLED; + } +} + +struct libinput_device *libinput_device_ref(struct libinput_device *device) +{ + return device; +} + +struct libinput_device *libinput_device_unref(struct libinput_device *device) +{ + return device; +} + +int libinput_device_get_size(struct libinput_device *device, double *width, double *height) +{ + if (device->deviceSizeReturnValue) { + return device->deviceSizeReturnValue; + } + if (width) { + *width = device->deviceSize.width(); + } + if (height) { + *height = device->deviceSize.height(); + } + return device->deviceSizeReturnValue; +} + +int libinput_device_pointer_has_button(struct libinput_device *device, uint32_t code) +{ + switch (code) { + case BTN_LEFT: + return device->supportedButtons.testFlag(Qt::LeftButton); + case BTN_MIDDLE: + return device->supportedButtons.testFlag(Qt::MiddleButton); + case BTN_RIGHT: + return device->supportedButtons.testFlag(Qt::RightButton); + case BTN_SIDE: + return device->supportedButtons.testFlag(Qt::ExtraButton1); + case BTN_EXTRA: + return device->supportedButtons.testFlag(Qt::ExtraButton2); + case BTN_BACK: + return device->supportedButtons.testFlag(Qt::BackButton); + case BTN_FORWARD: + return device->supportedButtons.testFlag(Qt::ForwardButton); + case BTN_TASK: + return device->supportedButtons.testFlag(Qt::TaskButton); + default: + return 0; + } +} + +enum libinput_config_status libinput_device_config_left_handed_set(struct libinput_device *device, int left_handed) +{ + if (device->setLeftHandedReturnValue == 0) { + device->leftHanded = left_handed; + return LIBINPUT_CONFIG_STATUS_SUCCESS; + } + return LIBINPUT_CONFIG_STATUS_INVALID; +} + +enum libinput_config_status libinput_device_config_accel_set_speed(struct libinput_device *device, double speed) +{ + if (device->setPointerAccelerationReturnValue == 0) { + device->pointerAcceleration = speed; + return LIBINPUT_CONFIG_STATUS_SUCCESS; + } + return LIBINPUT_CONFIG_STATUS_INVALID; +} + +enum libinput_config_status libinput_device_config_send_events_set_mode(struct libinput_device *device, uint32_t mode) +{ + if (device->setEnableModeReturnValue == 0) { + device->enabled = (mode == LIBINPUT_CONFIG_SEND_EVENTS_ENABLED); + return LIBINPUT_CONFIG_STATUS_SUCCESS; + } + return LIBINPUT_CONFIG_STATUS_INVALID; +} + +enum libinput_event_type libinput_event_get_type(struct libinput_event *event) +{ + return event->type; +} + +struct libinput_device *libinput_event_get_device(struct libinput_event *event) +{ + return event->device; +} + +void libinput_event_destroy(struct libinput_event *event) +{ + delete event; +} + +struct libinput_event_keyboard *libinput_event_get_keyboard_event(struct libinput_event *event) +{ + if (event->type == LIBINPUT_EVENT_KEYBOARD_KEY) { + return reinterpret_cast(event); + } + return nullptr; +} + +struct libinput_event_pointer *libinput_event_get_pointer_event(struct libinput_event *event) +{ + switch (event->type) { + case LIBINPUT_EVENT_POINTER_MOTION: + case LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: + case LIBINPUT_EVENT_POINTER_BUTTON: + case LIBINPUT_EVENT_POINTER_AXIS: + return reinterpret_cast(event); + default: + return nullptr; + } +} + +struct libinput_event_touch *libinput_event_get_touch_event(struct libinput_event *event) +{ + switch (event->type) { + case LIBINPUT_EVENT_TOUCH_DOWN: + case LIBINPUT_EVENT_TOUCH_UP: + case LIBINPUT_EVENT_TOUCH_MOTION: + case LIBINPUT_EVENT_TOUCH_CANCEL: + case LIBINPUT_EVENT_TOUCH_FRAME: + return reinterpret_cast(event); + default: + return nullptr; + } +} + +struct libinput_event_gesture *libinput_event_get_gesture_event(struct libinput_event *event) +{ + switch (event->type) { + case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN: + case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE: + case LIBINPUT_EVENT_GESTURE_PINCH_END: + case LIBINPUT_EVENT_GESTURE_SWIPE_BEGIN: + case LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE: + case LIBINPUT_EVENT_GESTURE_SWIPE_END: + return reinterpret_cast(event); + default: + return nullptr; + } +} + +int libinput_event_gesture_get_cancelled(struct libinput_event_gesture *event) +{ + if (event->type == LIBINPUT_EVENT_GESTURE_PINCH_END || event->type == LIBINPUT_EVENT_GESTURE_SWIPE_END) { + return event->cancelled; + } + return 0; +} + +uint32_t libinput_event_gesture_get_time(struct libinput_event_gesture *event) +{ + return event->time; +} + +int libinput_event_gesture_get_finger_count(struct libinput_event_gesture *event) +{ + return event->fingerCount; +} + +double libinput_event_gesture_get_dx(struct libinput_event_gesture *event) +{ + if (event->type == LIBINPUT_EVENT_GESTURE_PINCH_UPDATE || event->type == LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE) { + return event->delta.x(); + } + return 0.0; +} + +double libinput_event_gesture_get_dy(struct libinput_event_gesture *event) +{ + if (event->type == LIBINPUT_EVENT_GESTURE_PINCH_UPDATE || event->type == LIBINPUT_EVENT_GESTURE_SWIPE_UPDATE) { + return event->delta.y(); + } + return 0.0; +} + +double libinput_event_gesture_get_scale(struct libinput_event_gesture *event) +{ + switch (event->type) { + case LIBINPUT_EVENT_GESTURE_PINCH_BEGIN: + return 1.0; + case LIBINPUT_EVENT_GESTURE_PINCH_UPDATE: + case LIBINPUT_EVENT_GESTURE_PINCH_END: + return event->scale; + default: + return 0.0; + } +} + +double libinput_event_gesture_get_angle_delta(struct libinput_event_gesture *event) +{ + if (event->type == LIBINPUT_EVENT_GESTURE_PINCH_UPDATE) { + return event->angleDelta; + } + return 0.0; +} + +uint32_t libinput_event_keyboard_get_key(struct libinput_event_keyboard *event) +{ + return event->key; +} + +enum libinput_key_state libinput_event_keyboard_get_key_state(struct libinput_event_keyboard *event) +{ + return event->state; +} + +uint32_t libinput_event_keyboard_get_time(struct libinput_event_keyboard *event) +{ + return event->time; +} + +double libinput_event_pointer_get_absolute_x(struct libinput_event_pointer *event) +{ + return event->absolutePos.x(); +} + +double libinput_event_pointer_get_absolute_y(struct libinput_event_pointer *event) +{ + return event->absolutePos.y(); +} + +double libinput_event_pointer_get_absolute_x_transformed(struct libinput_event_pointer *event, uint32_t width) +{ + double deviceWidth = 0.0; + double deviceHeight = 0.0; + libinput_device_get_size(event->device, &deviceWidth, &deviceHeight); + return event->absolutePos.x() / deviceWidth * width; +} + +double libinput_event_pointer_get_absolute_y_transformed(struct libinput_event_pointer *event, uint32_t height) +{ + double deviceWidth = 0.0; + double deviceHeight = 0.0; + libinput_device_get_size(event->device, &deviceWidth, &deviceHeight); + return event->absolutePos.y() / deviceHeight * height; +} + +double libinput_event_pointer_get_dx(struct libinput_event_pointer *event) +{ + return event->delta.x(); +} + +double libinput_event_pointer_get_dy(struct libinput_event_pointer *event) +{ + return event->delta.y(); +} + +double libinput_event_pointer_get_dx_unaccelerated(struct libinput_event_pointer *event) +{ + return event->delta.x(); +} + +double libinput_event_pointer_get_dy_unaccelerated(struct libinput_event_pointer *event) +{ + return event->delta.y(); +} + +uint32_t libinput_event_pointer_get_time(struct libinput_event_pointer *event) +{ + return event->time; +} + +uint64_t libinput_event_pointer_get_time_usec(struct libinput_event_pointer *event) +{ + return quint64(event->time * 1000); +} + +uint32_t libinput_event_pointer_get_button(struct libinput_event_pointer *event) +{ + return event->button; +} + +enum libinput_button_state libinput_event_pointer_get_button_state(struct libinput_event_pointer *event) +{ + return event->buttonState; +} + +int libinput_event_pointer_has_axis(struct libinput_event_pointer *event, enum libinput_pointer_axis axis) +{ + if (axis == LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL) { + return event->verticalAxis; + } else { + return event->horizontalAxis; + } +} + +double libinput_event_pointer_get_axis_value(struct libinput_event_pointer *event, enum libinput_pointer_axis axis) +{ + if (axis == LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL) { + return event->verticalAxisValue; + } else { + return event->horizontalAxisValue; + } +} + +double libinput_event_pointer_get_axis_value_discrete(struct libinput_event_pointer *event, enum libinput_pointer_axis axis) +{ + if (axis == LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL) { + return event->verticalDiscreteAxisValue; + } else { + return event->horizontalDiscreteAxisValue; + } +} + +enum libinput_pointer_axis_source libinput_event_pointer_get_axis_source(struct libinput_event_pointer *event) +{ + return event->axisSource; +} + +uint32_t libinput_event_touch_get_time(struct libinput_event_touch *event) +{ + return event->time; +} + +double libinput_event_touch_get_x(struct libinput_event_touch *event) +{ + return event->absolutePos.x(); +} + +double libinput_event_touch_get_y(struct libinput_event_touch *event) +{ + return event->absolutePos.y(); +} + +double libinput_event_touch_get_x_transformed(struct libinput_event_touch *event, uint32_t width) +{ + double deviceWidth = 0.0; + double deviceHeight = 0.0; + libinput_device_get_size(event->device, &deviceWidth, &deviceHeight); + return event->absolutePos.x() / deviceWidth * width; +} + +double libinput_event_touch_get_y_transformed(struct libinput_event_touch *event, uint32_t height) +{ + double deviceWidth = 0.0; + double deviceHeight = 0.0; + libinput_device_get_size(event->device, &deviceWidth, &deviceHeight); + return event->absolutePos.y() / deviceHeight * height; +} + +int32_t libinput_event_touch_get_seat_slot(struct libinput_event_touch *event) +{ + return event->slot; +} + +struct libinput *libinput_udev_create_context(const struct libinput_interface *interface, void *user_data, struct udev *udev) +{ + if (!udev) { + return nullptr; + } + Q_UNUSED(interface) + Q_UNUSED(user_data) + return new libinput; +} + +void libinput_log_set_priority(struct libinput *libinput, enum libinput_log_priority priority) +{ + Q_UNUSED(libinput) + Q_UNUSED(priority) +} + +void libinput_log_set_handler(struct libinput *libinput, libinput_log_handler log_handler) +{ + Q_UNUSED(libinput) + Q_UNUSED(log_handler) +} + +struct libinput *libinput_unref(struct libinput *libinput) +{ + libinput->refCount--; + if (libinput->refCount == 0) { + delete libinput; + return nullptr; + } + return libinput; +} + +int libinput_udev_assign_seat(struct libinput *libinput, const char *seat_id) +{ + if (libinput->assignSeatRetVal == 0) { + libinput->seat = QByteArray(seat_id); + } + return libinput->assignSeatRetVal; +} + +int libinput_get_fd(struct libinput *libinput) +{ + Q_UNUSED(libinput) + return -1; +} + +int libinput_dispatch(struct libinput *libinput) +{ + Q_UNUSED(libinput) + return 0; +} + +struct libinput_event *libinput_get_event(struct libinput *libinput) +{ + Q_UNUSED(libinput) + return nullptr; +} + +void libinput_suspend(struct libinput *libinput) +{ + Q_UNUSED(libinput) +} + +int libinput_resume(struct libinput *libinput) +{ + Q_UNUSED(libinput) + return 0; +} + +int libinput_device_config_middle_emulation_is_available(struct libinput_device *device) +{ + return device->supportsMiddleEmulation; +} + +enum libinput_config_status libinput_device_config_middle_emulation_set_enabled(struct libinput_device *device, enum libinput_config_middle_emulation_state enable) +{ + if (device->setMiddleEmulationReturnValue == 0) { + if (!device->supportsMiddleEmulation) { + return LIBINPUT_CONFIG_STATUS_INVALID; + } + device->middleEmulation = (enable == LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED); + return LIBINPUT_CONFIG_STATUS_SUCCESS; + } + return LIBINPUT_CONFIG_STATUS_INVALID; +} + +enum libinput_config_middle_emulation_state libinput_device_config_middle_emulation_get_enabled(struct libinput_device *device) +{ + if (device->middleEmulation) { + return LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED; + } else { + return LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED; + } +} + +enum libinput_config_middle_emulation_state libinput_device_config_middle_emulation_get_default_enabled(struct libinput_device *device) +{ + if (device->middleEmulationEnabledByDefault) { + return LIBINPUT_CONFIG_MIDDLE_EMULATION_ENABLED; + } else { + return LIBINPUT_CONFIG_MIDDLE_EMULATION_DISABLED; + } +} + +int libinput_device_config_scroll_has_natural_scroll(struct libinput_device *device) +{ + return device->supportsNaturalScroll; +} + +enum libinput_config_status libinput_device_config_scroll_set_natural_scroll_enabled(struct libinput_device *device, int enable) +{ + if (device->setNaturalScrollReturnValue == 0) { + if (!device->supportsNaturalScroll) { + return LIBINPUT_CONFIG_STATUS_INVALID; + } + device->naturalScroll = enable; + return LIBINPUT_CONFIG_STATUS_SUCCESS; + } + return LIBINPUT_CONFIG_STATUS_INVALID; +} + +int libinput_device_config_scroll_get_natural_scroll_enabled(struct libinput_device *device) +{ + return device->naturalScroll; +} + +int libinput_device_config_scroll_get_default_natural_scroll_enabled(struct libinput_device *device) +{ + return device->naturalScrollEnabledByDefault; +} + +enum libinput_config_tap_button_map libinput_device_config_tap_get_default_button_map(struct libinput_device *device) +{ + return device->defaultTapButtonMap; +} + +enum libinput_config_status libinput_device_config_tap_set_button_map(struct libinput_device *device, enum libinput_config_tap_button_map map) +{ + if (device->setTapButtonMapReturnValue == 0) { + if (device->tapFingerCount == 0) { + return LIBINPUT_CONFIG_STATUS_INVALID; + } + device->tapButtonMap = map; + return LIBINPUT_CONFIG_STATUS_SUCCESS; + } + return LIBINPUT_CONFIG_STATUS_INVALID; +} + +enum libinput_config_tap_button_map libinput_device_config_tap_get_button_map(struct libinput_device *device) +{ + return device->tapButtonMap; +} + +uint32_t libinput_device_config_scroll_get_methods(struct libinput_device *device) +{ + return device->supportedScrollMethods; +} + +enum libinput_config_scroll_method libinput_device_config_scroll_get_default_method(struct libinput_device *device) +{ + return device->defaultScrollMethod; +} + +enum libinput_config_status libinput_device_config_scroll_set_method(struct libinput_device *device, enum libinput_config_scroll_method method) +{ + if (device->setScrollMethodReturnValue == 0) { + if (!(device->supportedScrollMethods & method) && method != LIBINPUT_CONFIG_SCROLL_NO_SCROLL) { + return LIBINPUT_CONFIG_STATUS_INVALID; + } + device->scrollMethod = method; + return LIBINPUT_CONFIG_STATUS_SUCCESS; + } + return LIBINPUT_CONFIG_STATUS_INVALID; +} + +enum libinput_config_scroll_method libinput_device_config_scroll_get_method(struct libinput_device *device) +{ + return device->scrollMethod; +} + +enum libinput_config_status libinput_device_config_scroll_set_button(struct libinput_device *device, uint32_t button) +{ + if (device->setScrollButtonReturnValue == 0) { + if (!(device->supportedScrollMethods & LIBINPUT_CONFIG_SCROLL_ON_BUTTON_DOWN)) { + return LIBINPUT_CONFIG_STATUS_UNSUPPORTED; + } + device->scrollButton = button; + return LIBINPUT_CONFIG_STATUS_SUCCESS; + } + return LIBINPUT_CONFIG_STATUS_INVALID; +} + +uint32_t libinput_device_config_scroll_get_button(struct libinput_device *device) +{ + return device->scrollButton; +} + +uint32_t libinput_device_config_scroll_get_default_button(struct libinput_device *device) +{ + return device->defaultScrollButton; +} + +int libinput_device_switch_has_switch(struct libinput_device *device, enum libinput_switch sw) +{ + switch (sw) { + case LIBINPUT_SWITCH_LID: + return device->lidSwitch; + case LIBINPUT_SWITCH_TABLET_MODE: + return device->tabletModeSwitch; + default: + Q_UNREACHABLE(); + } + return 0; +} + +struct libinput_event_switch *libinput_event_get_switch_event(struct libinput_event *event) +{ + if (event->type == LIBINPUT_EVENT_SWITCH_TOGGLE) { + return reinterpret_cast(event); + } else { + return nullptr; + } +} + +enum libinput_switch_state libinput_event_switch_get_switch_state(struct libinput_event_switch *event) +{ + switch (event->state) { + case libinput_event_switch::State::On: + return LIBINPUT_SWITCH_STATE_ON; + case libinput_event_switch::State::Off: + return LIBINPUT_SWITCH_STATE_OFF; + default: + Q_UNREACHABLE(); + } +} + +uint32_t libinput_event_switch_get_time(struct libinput_event_switch *event) +{ + return event->time; +} + +uint64_t libinput_event_switch_get_time_usec(struct libinput_event_switch *event) +{ + return event->timeMicroseconds; +} + +struct libinput_event_tablet_pad *libinput_event_get_tablet_pad_event(struct libinput_event *event) +{ + if (event->type == LIBINPUT_EVENT_TABLET_PAD_BUTTON) { + return reinterpret_cast(event); + } + return nullptr; +} + +struct libinput_event_tablet_tool * +libinput_event_get_tablet_tool_event(struct libinput_event *event) +{ + switch (event->type) { + case LIBINPUT_EVENT_TABLET_TOOL_AXIS: + case LIBINPUT_EVENT_TABLET_TOOL_PROXIMITY: + case LIBINPUT_EVENT_TABLET_TOOL_TIP: + return reinterpret_cast(event); + default: + return nullptr; + } +} + +int libinput_device_tablet_pad_get_num_strips(struct libinput_device *device) +{ + return device->stripCount; +} + +int libinput_device_tablet_pad_get_num_rings(struct libinput_device *device) +{ + return device->ringCount; +} + +int libinput_device_tablet_pad_get_num_buttons(struct libinput_device *device) +{ + return device->buttonCount; +} + +struct libinput_device_group * +libinput_device_get_device_group(struct libinput_device *device) +{ + Q_UNUSED(device); + return nullptr; +} + +void * +libinput_device_group_get_user_data(struct libinput_device_group *group) +{ + Q_UNUSED(group); + return nullptr; +} + +void libinput_device_led_update(struct libinput_device *device, + enum libinput_led leds) +{ + Q_UNUSED(device) + Q_UNUSED(leds) +} + +void libinput_device_set_user_data(struct libinput_device *device, void *user_data) +{ + device->userData = user_data; +} + +void * +libinput_device_get_user_data(struct libinput_device *device) +{ + return device->userData; +} + +double +libinput_event_tablet_tool_get_x_transformed(struct libinput_event_tablet_tool *event, + uint32_t width) +{ + Q_UNUSED(event) + Q_UNUSED(width) + + // it's unused at the moment, it doesn't really matter what we return + return 0; +} + +double +libinput_event_tablet_tool_get_y_transformed(struct libinput_event_tablet_tool *event, + uint32_t height) +{ + Q_UNUSED(event) + Q_UNUSED(height) + return 4; +} diff --git a/autotests/libinput/mock_libinput.h b/autotests/libinput/mock_libinput.h new file mode 100644 index 0000000..e0c44de --- /dev/null +++ b/autotests/libinput/mock_libinput.h @@ -0,0 +1,172 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#ifndef MOCK_LIBINPUT_H +#define MOCK_LIBINPUT_H +#include + +#include +#include +#include +#include + +#include + +struct libinput_device +{ + void *userData = nullptr; + bool keyboard = false; + bool pointer = false; + bool touch = false; + bool tabletTool = false; + bool gestureSupported = false; + bool switchDevice = false; + QByteArray name; + QByteArray sysName = QByteArrayLiteral("event0"); + QByteArray outputName; + quint32 product = 0; + quint32 vendor = 0; + int tapFingerCount = 0; + QSizeF deviceSize; + int deviceSizeReturnValue = 0; + bool tapEnabledByDefault = false; + bool tapToClick = false; + bool tapAndDragEnabledByDefault = false; + bool tapAndDrag = false; + bool tapDragLockEnabledByDefault = false; + bool tapDragLock = false; + bool supportsDisableWhileTyping = false; + bool supportsPointerAcceleration = false; + bool supportsLeftHanded = false; + bool supportsCalibrationMatrix = false; + bool supportsDisableEvents = false; + bool supportsDisableEventsOnExternalMouse = false; + bool supportsMiddleEmulation = false; + bool supportsNaturalScroll = false; + quint32 supportedScrollMethods = 0; + bool middleEmulationEnabledByDefault = false; + bool middleEmulation = false; + enum libinput_config_tap_button_map defaultTapButtonMap = LIBINPUT_CONFIG_TAP_MAP_LRM; + enum libinput_config_tap_button_map tapButtonMap = LIBINPUT_CONFIG_TAP_MAP_LRM; + int setTapButtonMapReturnValue = 0; + enum libinput_config_dwt_state disableWhileTypingEnabledByDefault = LIBINPUT_CONFIG_DWT_DISABLED; + enum libinput_config_dwt_state disableWhileTyping = LIBINPUT_CONFIG_DWT_DISABLED; + int setDisableWhileTypingReturnValue = 0; + qreal defaultPointerAcceleration = 0.0; + qreal pointerAcceleration = 0.0; + int setPointerAccelerationReturnValue = 0; + bool leftHandedEnabledByDefault = false; + bool leftHanded = false; + int setLeftHandedReturnValue = 0; + bool naturalScrollEnabledByDefault = false; + bool naturalScroll = false; + int setNaturalScrollReturnValue = 0; + enum libinput_config_scroll_method defaultScrollMethod = LIBINPUT_CONFIG_SCROLL_NO_SCROLL; + enum libinput_config_scroll_method scrollMethod = LIBINPUT_CONFIG_SCROLL_NO_SCROLL; + int setScrollMethodReturnValue = 0; + quint32 defaultScrollButton = 0; + quint32 scrollButton = 0; + int setScrollButtonReturnValue = 0; + Qt::MouseButtons supportedButtons; + QVector keys; + bool enabled = true; + int setEnableModeReturnValue = 0; + int setTapToClickReturnValue = 0; + int setTapAndDragReturnValue = 0; + int setTapDragLockReturnValue = 0; + int setMiddleEmulationReturnValue = 0; + quint32 supportedPointerAccelerationProfiles = 0; + enum libinput_config_accel_profile defaultPointerAccelerationProfile = LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; + enum libinput_config_accel_profile pointerAccelerationProfile = LIBINPUT_CONFIG_ACCEL_PROFILE_NONE; + bool setPointerAccelerationProfileReturnValue = 0; + std::array defaultCalibrationMatrix{{1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f}}; + std::array calibrationMatrix{{1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f}}; + bool defaultCalibrationMatrixIsIdentity = true; + bool calibrationMatrixIsIdentity = true; + + bool lidSwitch = false; + bool tabletModeSwitch = false; + quint32 supportedClickMethods = 0; + enum libinput_config_click_method defaultClickMethod = LIBINPUT_CONFIG_CLICK_METHOD_NONE; + enum libinput_config_click_method clickMethod = LIBINPUT_CONFIG_CLICK_METHOD_NONE; + bool setClickMethodReturnValue = 0; + uint32_t buttonCount = 0; + uint32_t stripCount = 0; + uint32_t ringCount = 0; +}; + +struct libinput_event +{ + virtual ~libinput_event() + { + } + libinput_device *device = nullptr; + libinput_event_type type = LIBINPUT_EVENT_NONE; + quint32 time = 0; +}; + +struct libinput_event_keyboard : libinput_event +{ + libinput_event_keyboard() + { + type = LIBINPUT_EVENT_KEYBOARD_KEY; + } + libinput_key_state state = LIBINPUT_KEY_STATE_RELEASED; + quint32 key = 0; +}; + +struct libinput_event_pointer : libinput_event +{ + libinput_button_state buttonState = LIBINPUT_BUTTON_STATE_RELEASED; + quint32 button = 0; + bool verticalAxis = false; + bool horizontalAxis = false; + qreal horizontalAxisValue = 0.0; + qreal verticalAxisValue = 0.0; + qreal horizontalDiscreteAxisValue = 0.0; + qreal verticalDiscreteAxisValue = 0.0; + libinput_pointer_axis_source axisSource = {}; + QPointF delta; + QPointF absolutePos; +}; + +struct libinput_event_touch : libinput_event +{ + qint32 slot = -1; + QPointF absolutePos; +}; + +struct libinput_event_gesture : libinput_event +{ + int fingerCount = 0; + bool cancelled = false; + QPointF delta = QPointF(0, 0); + qreal scale = 0.0; + qreal angleDelta = 0.0; +}; + +struct libinput_event_switch : libinput_event +{ + enum class State { + Off, + On + }; + State state = State::Off; + quint64 timeMicroseconds = 0; +}; + +struct libinput +{ + int refCount = 1; + QByteArray seat; + int assignSeatRetVal = 0; +}; + +#endif diff --git a/autotests/libinput/pointer_event_test.cpp b/autotests/libinput/pointer_event_test.cpp new file mode 100644 index 0000000..40e8492 --- /dev/null +++ b/autotests/libinput/pointer_event_test.cpp @@ -0,0 +1,222 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "mock_libinput.h" + +#include "backends/libinput/device.h" +#include "backends/libinput/events.h" + +#include + +#include + +Q_DECLARE_METATYPE(libinput_event_type) +Q_DECLARE_METATYPE(libinput_button_state) +Q_DECLARE_METATYPE(libinput_pointer_axis_source) + +using namespace KWin::LibInput; + +class TestLibinputPointerEvent : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void init(); + void cleanup(); + + void testType_data(); + void testType(); + void testButton_data(); + void testButton(); + void testAxis_data(); + void testAxis(); + void testMotion(); + void testAbsoluteMotion(); + +private: + libinput_device *m_nativeDevice = nullptr; + Device *m_device = nullptr; +}; + +void TestLibinputPointerEvent::init() +{ + m_nativeDevice = new libinput_device; + m_nativeDevice->pointer = true; + m_nativeDevice->deviceSize = QSizeF(12.5, 13.8); + m_device = new Device(m_nativeDevice); +} + +void TestLibinputPointerEvent::cleanup() +{ + delete m_device; + m_device = nullptr; + + delete m_nativeDevice; + m_nativeDevice = nullptr; +} + +void TestLibinputPointerEvent::testType_data() +{ + QTest::addColumn("type"); + + QTest::newRow("motion") << LIBINPUT_EVENT_POINTER_MOTION; + QTest::newRow("absolute motion") << LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE; + QTest::newRow("button") << LIBINPUT_EVENT_POINTER_BUTTON; + QTest::newRow("axis") << LIBINPUT_EVENT_POINTER_AXIS; +} + +void TestLibinputPointerEvent::testType() +{ + // this test verifies the initialization of a PointerEvent and the parent Event class + libinput_event_pointer *pointerEvent = new libinput_event_pointer; + QFETCH(libinput_event_type, type); + pointerEvent->type = type; + pointerEvent->device = m_nativeDevice; + + std::unique_ptr event(Event::create(pointerEvent)); + // API of event + QCOMPARE(event->type(), type); + QCOMPARE(event->device(), m_device); + QCOMPARE(event->nativeDevice(), m_nativeDevice); + QCOMPARE((libinput_event *)(*event.get()), pointerEvent); + // verify it's a pointer event + QVERIFY(dynamic_cast(event.get())); + QCOMPARE((libinput_event_pointer *)(*dynamic_cast(event.get())), pointerEvent); +} + +void TestLibinputPointerEvent::testButton_data() +{ + QTest::addColumn("buttonState"); + QTest::addColumn("expectedButtonState"); + QTest::addColumn("button"); + QTest::addColumn("time"); + + QTest::newRow("pressed") << LIBINPUT_BUTTON_STATE_RELEASED << KWin::InputRedirection::PointerButtonReleased << quint32(BTN_RIGHT) << 100u; + QTest::newRow("released") << LIBINPUT_BUTTON_STATE_PRESSED << KWin::InputRedirection::PointerButtonPressed << quint32(BTN_LEFT) << 200u; +} + +void TestLibinputPointerEvent::testButton() +{ + // this test verifies the button press/release + libinput_event_pointer *pointerEvent = new libinput_event_pointer; + pointerEvent->device = m_nativeDevice; + pointerEvent->type = LIBINPUT_EVENT_POINTER_BUTTON; + QFETCH(libinput_button_state, buttonState); + pointerEvent->buttonState = buttonState; + QFETCH(quint32, button); + pointerEvent->button = button; + QFETCH(quint32, time); + pointerEvent->time = time; + + std::unique_ptr event(Event::create(pointerEvent)); + auto pe = dynamic_cast(event.get()); + QVERIFY(pe); + QCOMPARE(pe->type(), LIBINPUT_EVENT_POINTER_BUTTON); + QTEST(pe->buttonState(), "expectedButtonState"); + QCOMPARE(pe->button(), button); + QCOMPARE(pe->time(), time); + QCOMPARE(pe->timeMicroseconds(), quint64(time * 1000)); +} + +void TestLibinputPointerEvent::testAxis_data() +{ + QTest::addColumn("horizontal"); + QTest::addColumn("vertical"); + QTest::addColumn("value"); + QTest::addColumn("discreteValue"); + QTest::addColumn("axisSource"); + QTest::addColumn("expectedAxisSource"); + QTest::addColumn("time"); + + QTest::newRow("wheel/horizontal") << true << false << QPointF(3.0, 0.0) << QPoint(1, 0) << LIBINPUT_POINTER_AXIS_SOURCE_WHEEL << KWin::InputRedirection::PointerAxisSourceWheel << 100u; + QTest::newRow("wheel/vertical") << false << true << QPointF(0.0, 2.5) << QPoint(0, 1) << LIBINPUT_POINTER_AXIS_SOURCE_WHEEL << KWin::InputRedirection::PointerAxisSourceWheel << 200u; + QTest::newRow("wheel/both") << true << true << QPointF(1.1, 4.2) << QPoint(1, 1) << LIBINPUT_POINTER_AXIS_SOURCE_WHEEL << KWin::InputRedirection::PointerAxisSourceWheel << 300u; + + QTest::newRow("finger/horizontal") << true << false << QPointF(3.0, 0.0) << QPoint(0, 0) << LIBINPUT_POINTER_AXIS_SOURCE_FINGER << KWin::InputRedirection::PointerAxisSourceFinger << 400u; + QTest::newRow("stop finger/horizontal") << true << false << QPointF(0.0, 0.0) << QPoint(0, 0) << LIBINPUT_POINTER_AXIS_SOURCE_FINGER << KWin::InputRedirection::PointerAxisSourceFinger << 500u; + QTest::newRow("finger/vertical") << false << true << QPointF(0.0, 2.5) << QPoint(0, 0) << LIBINPUT_POINTER_AXIS_SOURCE_FINGER << KWin::InputRedirection::PointerAxisSourceFinger << 600u; + QTest::newRow("stop finger/vertical") << false << true << QPointF(0.0, 0.0) << QPoint(0, 0) << LIBINPUT_POINTER_AXIS_SOURCE_FINGER << KWin::InputRedirection::PointerAxisSourceFinger << 700u; + QTest::newRow("finger/both") << true << true << QPointF(1.1, 4.2) << QPoint(0, 0) << LIBINPUT_POINTER_AXIS_SOURCE_FINGER << KWin::InputRedirection::PointerAxisSourceFinger << 800u; + QTest::newRow("stop finger/both") << true << true << QPointF(0.0, 0.0) << QPoint(0, 0) << LIBINPUT_POINTER_AXIS_SOURCE_FINGER << KWin::InputRedirection::PointerAxisSourceFinger << 900u; + + QTest::newRow("continuous/horizontal") << true << false << QPointF(3.0, 0.0) << QPoint(0, 0) << LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS << KWin::InputRedirection::PointerAxisSourceContinuous << 1000u; + QTest::newRow("continuous/vertical") << false << true << QPointF(0.0, 2.5) << QPoint(0, 0) << LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS << KWin::InputRedirection::PointerAxisSourceContinuous << 1100u; + QTest::newRow("continuous/both") << true << true << QPointF(1.1, 4.2) << QPoint(0, 0) << LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS << KWin::InputRedirection::PointerAxisSourceContinuous << 1200u; +} + +void TestLibinputPointerEvent::testAxis() +{ + // this test verifies pointer axis functionality + libinput_event_pointer *pointerEvent = new libinput_event_pointer; + pointerEvent->device = m_nativeDevice; + pointerEvent->type = LIBINPUT_EVENT_POINTER_AXIS; + QFETCH(bool, horizontal); + QFETCH(bool, vertical); + QFETCH(QPointF, value); + QFETCH(QPoint, discreteValue); + QFETCH(libinput_pointer_axis_source, axisSource); + QFETCH(quint32, time); + pointerEvent->horizontalAxis = horizontal; + pointerEvent->verticalAxis = vertical; + pointerEvent->horizontalAxisValue = value.x(); + pointerEvent->verticalAxisValue = value.y(); + pointerEvent->horizontalDiscreteAxisValue = discreteValue.x(); + pointerEvent->verticalDiscreteAxisValue = discreteValue.y(); + pointerEvent->axisSource = axisSource; + pointerEvent->time = time; + + std::unique_ptr event(Event::create(pointerEvent)); + auto pe = dynamic_cast(event.get()); + QVERIFY(pe); + QCOMPARE(pe->type(), LIBINPUT_EVENT_POINTER_AXIS); + QCOMPARE(pe->axis().contains(KWin::InputRedirection::PointerAxisHorizontal), horizontal); + QCOMPARE(pe->axis().contains(KWin::InputRedirection::PointerAxisVertical), vertical); + QCOMPARE(pe->axisValue(KWin::InputRedirection::PointerAxisHorizontal), value.x()); + QCOMPARE(pe->axisValue(KWin::InputRedirection::PointerAxisVertical), value.y()); + QCOMPARE(pe->discreteAxisValue(KWin::InputRedirection::PointerAxisHorizontal), discreteValue.x()); + QCOMPARE(pe->discreteAxisValue(KWin::InputRedirection::PointerAxisVertical), discreteValue.y()); + QTEST(pe->axisSource(), "expectedAxisSource"); + QCOMPARE(pe->time(), time); +} + +void TestLibinputPointerEvent::testMotion() +{ + // this test verifies pointer motion (delta) + libinput_event_pointer *pointerEvent = new libinput_event_pointer; + pointerEvent->device = m_nativeDevice; + pointerEvent->type = LIBINPUT_EVENT_POINTER_MOTION; + pointerEvent->delta = QPointF(2.1, 4.5); + pointerEvent->time = 500u; + + std::unique_ptr event(Event::create(pointerEvent)); + auto pe = dynamic_cast(event.get()); + QVERIFY(pe); + QCOMPARE(pe->type(), LIBINPUT_EVENT_POINTER_MOTION); + QCOMPARE(pe->time(), 500u); + QCOMPARE(pe->delta(), QPointF(2.1, 4.5)); +} + +void TestLibinputPointerEvent::testAbsoluteMotion() +{ + // this test verifies absolute pointer motion + libinput_event_pointer *pointerEvent = new libinput_event_pointer; + pointerEvent->device = m_nativeDevice; + pointerEvent->type = LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE; + pointerEvent->absolutePos = QPointF(6.25, 6.9); + pointerEvent->time = 500u; + + std::unique_ptr event(Event::create(pointerEvent)); + auto pe = dynamic_cast(event.get()); + QVERIFY(pe); + QCOMPARE(pe->type(), LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE); + QCOMPARE(pe->time(), 500u); + QCOMPARE(pe->absolutePos(), QPointF(6.25, 6.9)); + QCOMPARE(pe->absolutePos(QSize(1280, 1024)), QPointF(640, 512)); +} + +QTEST_GUILESS_MAIN(TestLibinputPointerEvent) +#include "pointer_event_test.moc" diff --git a/autotests/libinput/switch_event_test.cpp b/autotests/libinput/switch_event_test.cpp new file mode 100644 index 0000000..2d7a150 --- /dev/null +++ b/autotests/libinput/switch_event_test.cpp @@ -0,0 +1,89 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2017 Martin Flöser + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "mock_libinput.h" + +#include "backends/libinput/device.h" +#include "backends/libinput/events.h" + +#include + +#include + +Q_DECLARE_METATYPE(KWin::LibInput::SwitchEvent::State) + +using namespace KWin::LibInput; + +class TestLibinputSwitchEvent : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void init(); + void cleanup(); + + void testToggled_data(); + void testToggled(); + +private: + std::unique_ptr m_nativeDevice; + std::unique_ptr m_device; +}; + +void TestLibinputSwitchEvent::init() +{ + m_nativeDevice = std::make_unique(); + m_nativeDevice->switchDevice = true; + m_device = std::make_unique(m_nativeDevice.get()); +} + +void TestLibinputSwitchEvent::cleanup() +{ + m_device.reset(); + m_nativeDevice.reset(); +} + +void TestLibinputSwitchEvent::testToggled_data() +{ + QTest::addColumn("state"); + + QTest::newRow("on") << KWin::LibInput::SwitchEvent::State::On; + QTest::newRow("off") << KWin::LibInput::SwitchEvent::State::Off; +} + +void TestLibinputSwitchEvent::testToggled() +{ + libinput_event_switch *nativeEvent = new libinput_event_switch; + nativeEvent->type = LIBINPUT_EVENT_SWITCH_TOGGLE; + nativeEvent->device = m_nativeDevice.get(); + QFETCH(KWin::LibInput::SwitchEvent::State, state); + switch (state) { + case SwitchEvent::State::Off: + nativeEvent->state = libinput_event_switch::State::Off; + break; + case SwitchEvent::State::On: + nativeEvent->state = libinput_event_switch::State::On; + break; + default: + Q_UNREACHABLE(); + } + nativeEvent->time = 23; + nativeEvent->timeMicroseconds = 23456789; + + std::unique_ptr event(Event::create(nativeEvent)); + auto se = dynamic_cast(event.get()); + QVERIFY(se); + QCOMPARE(se->device(), m_device.get()); + QCOMPARE(se->nativeDevice(), m_nativeDevice.get()); + QCOMPARE(se->type(), LIBINPUT_EVENT_SWITCH_TOGGLE); + QCOMPARE(se->state(), state); + QCOMPARE(se->time(), 23u); + QCOMPARE(se->timeMicroseconds(), 23456789u); +} + +QTEST_GUILESS_MAIN(TestLibinputSwitchEvent) +#include "switch_event_test.moc" diff --git a/autotests/libinput/touch_event_test.cpp b/autotests/libinput/touch_event_test.cpp new file mode 100644 index 0000000..73d88ae --- /dev/null +++ b/autotests/libinput/touch_event_test.cpp @@ -0,0 +1,120 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "mock_libinput.h" + +#include "backends/libinput/device.h" +#include "backends/libinput/events.h" + +#include + +#include + +Q_DECLARE_METATYPE(libinput_event_type) + +using namespace KWin::LibInput; + +class TestLibinputTouchEvent : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void init(); + void cleanup(); + + void testType_data(); + void testType(); + void testAbsoluteMotion_data(); + void testAbsoluteMotion(); + +private: + libinput_device *m_nativeDevice = nullptr; + Device *m_device = nullptr; +}; + +void TestLibinputTouchEvent::init() +{ + m_nativeDevice = new libinput_device; + m_nativeDevice->touch = true; + m_nativeDevice->deviceSize = QSizeF(12.5, 13.8); + m_device = new Device(m_nativeDevice); +} + +void TestLibinputTouchEvent::cleanup() +{ + delete m_device; + m_device = nullptr; + + delete m_nativeDevice; + m_nativeDevice = nullptr; +} + +void TestLibinputTouchEvent::testType_data() +{ + QTest::addColumn("type"); + QTest::addColumn("hasId"); + + QTest::newRow("down") << LIBINPUT_EVENT_TOUCH_DOWN << true; + QTest::newRow("up") << LIBINPUT_EVENT_TOUCH_UP << true; + QTest::newRow("motion") << LIBINPUT_EVENT_TOUCH_MOTION << true; + QTest::newRow("cancel") << LIBINPUT_EVENT_TOUCH_CANCEL << false; + QTest::newRow("frame") << LIBINPUT_EVENT_TOUCH_FRAME << false; +} + +void TestLibinputTouchEvent::testType() +{ + // this test verifies the initialization of a PointerEvent and the parent Event class + libinput_event_touch *touchEvent = new libinput_event_touch; + QFETCH(libinput_event_type, type); + touchEvent->type = type; + touchEvent->device = m_nativeDevice; + touchEvent->slot = 0; + + std::unique_ptr event(Event::create(touchEvent)); + // API of event + QCOMPARE(event->type(), type); + QCOMPARE(event->device(), m_device); + QCOMPARE(event->nativeDevice(), m_nativeDevice); + QCOMPARE((libinput_event *)(*event.get()), touchEvent); + // verify it's a pointer event + QVERIFY(dynamic_cast(event.get())); + QCOMPARE((libinput_event_touch *)(*dynamic_cast(event.get())), touchEvent); + QFETCH(bool, hasId); + if (hasId) { + QCOMPARE(dynamic_cast(event.get())->id(), 0); + } +} + +void TestLibinputTouchEvent::testAbsoluteMotion_data() +{ + QTest::addColumn("type"); + QTest::newRow("down") << LIBINPUT_EVENT_TOUCH_DOWN; + QTest::newRow("motion") << LIBINPUT_EVENT_TOUCH_MOTION; +} + +void TestLibinputTouchEvent::testAbsoluteMotion() +{ + // this test verifies absolute touch points (either down or motion) + libinput_event_touch *touchEvent = new libinput_event_touch; + touchEvent->device = m_nativeDevice; + QFETCH(libinput_event_type, type); + touchEvent->type = type; + touchEvent->absolutePos = QPointF(6.25, 6.9); + touchEvent->time = 500u; + touchEvent->slot = 1; + + std::unique_ptr event(Event::create(touchEvent)); + auto te = dynamic_cast(event.get()); + QVERIFY(te); + QCOMPARE(te->type(), type); + QCOMPARE(te->time(), 500u); + QCOMPARE(te->absolutePos(), QPointF(6.25, 6.9)); + QCOMPARE(te->absolutePos(QSize(1280, 1024)), QPointF(640, 512)); +} + +QTEST_GUILESS_MAIN(TestLibinputTouchEvent) +#include "touch_event_test.moc" diff --git a/autotests/libkwineffects/CMakeLists.txt b/autotests/libkwineffects/CMakeLists.txt new file mode 100644 index 0000000..090d18f --- /dev/null +++ b/autotests/libkwineffects/CMakeLists.txt @@ -0,0 +1,23 @@ +include(ECMMarkAsTest) + +macro(KWINEFFECTS_UNIT_TESTS) + foreach(_testname ${ARGN}) + add_executable(${_testname} ${_testname}.cpp) + add_test(NAME kwineffects-${_testname} COMMAND ${_testname}) + target_link_libraries(${_testname} Qt::Test kwineffects) + ecm_mark_as_test(${_testname}) + endforeach() +endmacro() + +kwineffects_unit_tests( + windowquadlisttest + timelinetest +) + +add_executable(kwinglplatformtest kwinglplatformtest.cpp mock_gl.cpp ../../src/libkwineffects/kwinglplatform.cpp) +add_test(NAME kwineffects-kwinglplatformtest COMMAND kwinglplatformtest) +target_link_libraries(kwinglplatformtest Qt::Test Qt::Gui KF5::ConfigCore XCB::XCB) +if (QT_MAJOR_VERSION EQUAL "5") + target_link_libraries(kwinglplatformtest Qt::X11Extras) +endif() +ecm_mark_as_test(kwinglplatformtest) diff --git a/autotests/libkwineffects/data/glplatform/amd-catalyst-radeonhd-7700M-3.1.13399 b/autotests/libkwineffects/data/glplatform/amd-catalyst-radeonhd-7700M-3.1.13399 new file mode 100644 index 0000000..7567668 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/amd-catalyst-radeonhd-7700M-3.1.13399 @@ -0,0 +1,18 @@ +[Driver] +Vendor=ATI Technologies Inc. +Renderer=AMD Radeon HD 7700M Series +Version=3.1.13399 Compatibility Profile Context FireGL 15.201.1151 +ShadingLanguageVersion=4.40 + +[Settings] +LooseBinding=false +GLSL=true +TextureNPOT=true +Catalyst=true +Radeon=true +GLVersion=3,1,13399 +GLSLVersion=4,40 +DriverVersion=15,201,1151 +Driver=9 +ChipClass=999 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/amd-gallium-bonaire-3.0 b/autotests/libkwineffects/data/glplatform/amd-gallium-bonaire-3.0 new file mode 100644 index 0000000..deb1c4d --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/amd-gallium-bonaire-3.0 @@ -0,0 +1,21 @@ +[Driver] +Vendor=X.Org +Renderer=Gallium 0.4 on AMD BONAIRE (DRM 2.43.0, LLVM 3.8.0) +Version=3.0 Mesa 11.2.2 +ShadingLanguageVersion=1.30 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Gallium=true +Radeon=true +GLVersion=3,0 +GLSLVersion=1,30 +MesaVersion=11,2,2 +GalliumVersion=0,4 +DriverVersion=11,2,2 +Driver=16 +ChipClass=10 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/amd-gallium-cayman-gles-3.0 b/autotests/libkwineffects/data/glplatform/amd-gallium-cayman-gles-3.0 new file mode 100644 index 0000000..9d18d1a --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/amd-gallium-cayman-gles-3.0 @@ -0,0 +1,22 @@ +[Driver] +Vendor=X.Org +Renderer=Gallium 0.4 on AMD CAYMAN (DRM 2.43.0, LLVM 3.8.0) +Version=OpenGL ES 3.0 Mesa 11.2.2 +ShadingLanguageVersion=OpenGL ES GLSL ES 3.00 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Gallium=true +Radeon=true +GLVersion=3,0 +GLSLVersion=3,0 +GLES=true +MesaVersion=11,2,2 +GalliumVersion=0,4 +DriverVersion=11,2,2 +Driver=5 +ChipClass=8 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/amd-gallium-hawaii-3.0 b/autotests/libkwineffects/data/glplatform/amd-gallium-hawaii-3.0 new file mode 100644 index 0000000..87e0812 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/amd-gallium-hawaii-3.0 @@ -0,0 +1,21 @@ +[Driver] +Vendor=X.Org +Renderer=Gallium 0.4 on AMD HAWAII (DRM 2.43.0, LLVM 3.7.1) +Version=3.0 Mesa 11.1.2 +ShadingLanguageVersion=1.30 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Gallium=true +Radeon=true +GLVersion=3,0 +GLSLVersion=1,30 +MesaVersion=11,1,2 +GalliumVersion=0,4 +DriverVersion=11,1,2 +Driver=16 +ChipClass=10 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/amd-gallium-navi-4.5 b/autotests/libkwineffects/data/glplatform/amd-gallium-navi-4.5 new file mode 100644 index 0000000..69b289a --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/amd-gallium-navi-4.5 @@ -0,0 +1,21 @@ +[Driver] +Vendor=X.Org +Renderer=AMD NAVI10 (DRM 3.36.0, 5.5.1-arch1-1, LLVM 9.0.1) +Version=4.5 (Core Profile) Mesa 19.3.3 +ShadingLanguageVersion=4.50 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Gallium=true +Radeon=true +GLVersion=4,5 +GLSLVersion=4,50 +MesaVersion=19,3,3 +GalliumVersion=0,4 +DriverVersion=19,3,3 +Driver=16 +ChipClass=14 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/amd-gallium-radeon-r9-290-4.5 b/autotests/libkwineffects/data/glplatform/amd-gallium-radeon-r9-290-4.5 new file mode 100644 index 0000000..2423dda --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/amd-gallium-radeon-r9-290-4.5 @@ -0,0 +1,21 @@ +[Driver] +Vendor=X.Org +Renderer=AMD Radeon R9 200 Series (HAWAII DRM 3.26.0 4.18.9-92.current LLVM 6.0.1) +Version=4.5 Mesa 18.1.6 +ShadingLanguageVersion=4.50 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Gallium=true +Radeon=true +GLVersion=4,5 +GLSLVersion=4,50 +MesaVersion=18,1,6 +GalliumVersion=0,4 +DriverVersion=18,1,6 +Driver=16 +ChipClass=10 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-480-series-4.5 b/autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-480-series-4.5 new file mode 100644 index 0000000..d8a2a3d --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-480-series-4.5 @@ -0,0 +1,21 @@ +[Driver] +Vendor=X.Org +Renderer=AMD Radeon (TM) RX 480 Graphics (POLARIS10 / DRM 3.23.0 / 4.15.0-rc1-g516fb7f2e73d, LLVM 6.0.0) +Version=4.5 (Core Profile) Mesa 17.4.0-devel (git-b6b4b2c6d8) +ShadingLanguageVersion=4.50 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Gallium=true +Radeon=true +GLVersion=4,5 +GLSLVersion=4,50 +MesaVersion=17,4,0 +GalliumVersion=0,4 +DriverVersion=17,4,0 +Driver=16 +ChipClass=12 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-550-series-3.1 b/autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-550-series-3.1 new file mode 100644 index 0000000..a5bf537 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-550-series-3.1 @@ -0,0 +1,21 @@ +[Driver] +Vendor=X.Org +Renderer=Radeon RX 550 Series (POLARIS12, DRM 3.25.0, 4.17.0-rc6-GTW1+, LLVM 6.0.0) +Version=3.1 Mesa 18.1.0 +ShadingLanguageVersion=1.40 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Gallium=true +Radeon=true +GLVersion=3,1 +GLSLVersion=1,40 +MesaVersion=18,1,0 +GalliumVersion=0,4 +DriverVersion=18,1,0 +Driver=16 +ChipClass=12 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-5700-xt-4.6 b/autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-5700-xt-4.6 new file mode 100644 index 0000000..4e360b2 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-5700-xt-4.6 @@ -0,0 +1,21 @@ +[Driver] +Vendor=AMD +Renderer=AMD Radeon RX 5700 XT (NAVI10, DRM 3.40.0, 5.10.9-arch1-1, LLVM 11.0.1) +Version=4.6 (Compatibility Profile) Mesa 20.3.3 +ShadingLanguageVersion=4.60 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Gallium=true +Radeon=true +GLVersion=4,6 +GLSLVersion=4,60 +MesaVersion=20,3,3 +GalliumVersion=0,4 +DriverVersion=20,3,3 +Driver=16 +ChipClass=14 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-580-series-4.5 b/autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-580-series-4.5 new file mode 100644 index 0000000..3b4cd83 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-580-series-4.5 @@ -0,0 +1,21 @@ +[Driver] +Vendor=X.Org +Renderer=Radeon RX 580 Series (POLARIS10, DRM 3.27.0, 4.19.10-arch1-1-ARCH, LLVM 7.0.0) +Version=4.5 (Compatibility Profile) Mesa 18.3.1 +ShadingLanguageVersion=4.50 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Gallium=true +Radeon=true +GLVersion=4,5 +GLSLVersion=4,50 +MesaVersion=18,3,1 +GalliumVersion=0,4 +DriverVersion=18,3,1 +Driver=16 +ChipClass=12 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-vega-56-4.5 b/autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-vega-56-4.5 new file mode 100644 index 0000000..5606832 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-vega-56-4.5 @@ -0,0 +1,21 @@ +[Driver] +Vendor=X.Org +Renderer=Radeon RX Vega (VEGA10, DRM 3.25.0, 4.17.0-trunk-amd64, LLVM 6.0.0) +Version=4.5 (Core Profile) Mesa 18.1.2 +ShadingLanguageVersion=4.50 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Gallium=true +Radeon=true +GLVersion=4,5 +GLSLVersion=4,50 +MesaVersion=18,1,2 +GalliumVersion=0,4 +DriverVersion=18,1,2 +Driver=16 +ChipClass=13 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-vega-64-4.5 b/autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-vega-64-4.5 new file mode 100644 index 0000000..0a9071a --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/amd-gallium-radeon-rx-vega-64-4.5 @@ -0,0 +1,21 @@ +[Driver] +Vendor=X.Org +Renderer=Radeon RX Vega (VEGA10 / DRM 3.23.0 / 4.16.16-300.fc28.x86_64, LLVM 6.0.0) +Version=4.5 (Core Profile) Mesa 18.0.5 +ShadingLanguageVersion=4.50 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Gallium=true +Radeon=true +GLVersion=4,5 +GLSLVersion=4,50 +MesaVersion=18,0,5 +GalliumVersion=0,4 +DriverVersion=18,0,5 +Driver=16 +ChipClass=13 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/amd-gallium-redwood-3.0 b/autotests/libkwineffects/data/glplatform/amd-gallium-redwood-3.0 new file mode 100644 index 0000000..1ea71f5 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/amd-gallium-redwood-3.0 @@ -0,0 +1,21 @@ +[Driver] +Vendor=X.Org +Renderer=Gallium 0.4 on AMD REDWOOD (DRM 2.43.0 / 4.6.4-1-ARCH, LLVM 3.8.0) +Version=3.0 Mesa 12.0.1 +ShadingLanguageVersion=1.30 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Gallium=true +Radeon=true +GLVersion=3,0 +GLSLVersion=1,30 +MesaVersion=12,0,1 +GalliumVersion=0,4 +DriverVersion=12,0,1 +Driver=5 +ChipClass=7 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/amd-gallium-tonga-4.1 b/autotests/libkwineffects/data/glplatform/amd-gallium-tonga-4.1 new file mode 100644 index 0000000..d0e0a0c --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/amd-gallium-tonga-4.1 @@ -0,0 +1,21 @@ +[Driver] +Vendor=X.Org +Renderer=Gallium 0.4 on AMD TONGA (DRM 3.2.0 / 4.7.0-0-MANJARO, LLVM 3.8.0) +Version=4.1 (Core Profile) Mesa 12.0.1 +ShadingLanguageVersion=4.10 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Gallium=true +Radeon=true +GLVersion=4,1 +GLSLVersion=4,10 +MesaVersion=12,0,1 +GalliumVersion=0,4 +DriverVersion=12,0,1 +Driver=16 +ChipClass=11 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/intel-broadwell-gt2-3.3 b/autotests/libkwineffects/data/glplatform/intel-broadwell-gt2-3.3 new file mode 100644 index 0000000..4488f4a --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/intel-broadwell-gt2-3.3 @@ -0,0 +1,19 @@ +[Driver] +Vendor=Intel Open Source Technology Center +Renderer=Mesa DRI Intel(R) HD Graphics 5500 (Broadwell GT2) +Version=3.3 (Core Profile) Mesa 11.2.2 +ShadingLanguageVersion=3.30 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Intel=true +GLVersion=3,3 +GLSLVersion=3,30 +MesaVersion=11,2,2 +DriverVersion=11,2,2 +Driver=7 +ChipClass=2999 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/intel-haswell-mobile-3.3 b/autotests/libkwineffects/data/glplatform/intel-haswell-mobile-3.3 new file mode 100644 index 0000000..3c214b7 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/intel-haswell-mobile-3.3 @@ -0,0 +1,19 @@ +[Driver] +Vendor=Intel Open Source Technology Center +Renderer=Mesa DRI Intel(R) Haswell Mobile +Version=3.3 (Core Profile) Mesa 11.2.2 +ShadingLanguageVersion=3.30 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Intel=true +GLVersion=3,3 +GLSLVersion=3,30 +MesaVersion=11,2,2 +DriverVersion=11,2,2 +Driver=7 +ChipClass=2005 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/intel-ivybridge-desktop-3.0 b/autotests/libkwineffects/data/glplatform/intel-ivybridge-desktop-3.0 new file mode 100644 index 0000000..6e85932 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/intel-ivybridge-desktop-3.0 @@ -0,0 +1,20 @@ +[Driver] +Vendor=Intel Open Source Technology Center +Renderer=Mesa DRI Intel(R) Ivybridge Desktop +Version=3.0 Mesa 11.1.0 (git-525f3c2) +ShadingLanguageVersion=1.30 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Intel=true +GLVersion=3,0 +GLSLVersion=1,30 +MesaVersion=11,1,0 +DriverVersion=11,1,0 +Driver=7 +ChipClass=2004 +Compositor=1 + diff --git a/autotests/libkwineffects/data/glplatform/intel-ivybridge-desktop-3.3 b/autotests/libkwineffects/data/glplatform/intel-ivybridge-desktop-3.3 new file mode 100644 index 0000000..66fc3a7 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/intel-ivybridge-desktop-3.3 @@ -0,0 +1,19 @@ +[Driver] +Vendor=Intel Open Source Technology Center +Renderer=Mesa DRI Intel(R) Ivybridge Desktop +Version=3.3 (Core Profile) Mesa 11.2.2 +ShadingLanguageVersion=3.30 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Intel=true +GLVersion=3,3 +GLSLVersion=3,30 +MesaVersion=11,2,2 +DriverVersion=11,2,2 +Driver=7 +ChipClass=2004 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/intel-ivybridge-mobile-3.3 b/autotests/libkwineffects/data/glplatform/intel-ivybridge-mobile-3.3 new file mode 100644 index 0000000..8acf478 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/intel-ivybridge-mobile-3.3 @@ -0,0 +1,19 @@ +[Driver] +Vendor=Intel Open Source Technology Center +Renderer=Mesa DRI Intel(R) Ivybridge Mobile +Version=3.3 (Core Profile) Mesa 12.0.1 +ShadingLanguageVersion=3.30 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Intel=true +GLVersion=3,3 +GLSLVersion=3,30 +MesaVersion=12,0,1 +DriverVersion=12,0,1 +Driver=7 +ChipClass=2004 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/intel-kabylake-gt2-4.6 b/autotests/libkwineffects/data/glplatform/intel-kabylake-gt2-4.6 new file mode 100644 index 0000000..2bdc4a4 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/intel-kabylake-gt2-4.6 @@ -0,0 +1,19 @@ +[Driver] +Vendor=Intel +Renderer=Mesa Intel(R) UHD Graphics 620 (KBL GT2) +Version=4.6 (Compatibility Profile) Mesa 20.3.2 +ShadingLanguageVersion=4.60 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Intel=true +GLVersion=4,6 +GLSLVersion=4,60 +MesaVersion=20,3,2 +DriverVersion=20,3,2 +Driver=7 +ChipClass=2012 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/intel-sandybridge-mobile-3.3 b/autotests/libkwineffects/data/glplatform/intel-sandybridge-mobile-3.3 new file mode 100644 index 0000000..965d967 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/intel-sandybridge-mobile-3.3 @@ -0,0 +1,19 @@ +[Driver] +Vendor=Intel Open Source Technology Center +Renderer=Mesa DRI Intel(R) Sandybridge Mobile +Version=3.3 (Core Profile) Mesa 12.0.1 +ShadingLanguageVersion=3.30 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Intel=true +GLVersion=3,3 +GLSLVersion=3,30 +MesaVersion=12,0,1 +DriverVersion=12,0,1 +Driver=7 +ChipClass=2003 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/intel-skylake-gt2-3.0 b/autotests/libkwineffects/data/glplatform/intel-skylake-gt2-3.0 new file mode 100644 index 0000000..83acc24 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/intel-skylake-gt2-3.0 @@ -0,0 +1,19 @@ +[Driver] +Vendor=Intel Open Source Technology Center +Renderer=Mesa DRI Intel(R) HD Graphics 520 (Skylake GT2) +Version=3.0 Mesa 11.2.0 +ShadingLanguageVersion=1.30 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Intel=true +GLVersion=3,0 +GLSLVersion=1,30 +MesaVersion=11,2,0 +DriverVersion=11,2,0 +Driver=7 +ChipClass=2999 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/llvmpipe-10.0 b/autotests/libkwineffects/data/glplatform/llvmpipe-10.0 new file mode 100644 index 0000000..60047a8 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/llvmpipe-10.0 @@ -0,0 +1,22 @@ +[Driver] +Vendor=Mesa/X.org +Renderer=llvmpipe (LLVM 10.0.1, 256 bits) +Version=3.1 Mesa 20.2.1 +ShadingLanguageVersion=1.40 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Gallium=true +SoftwareEmulation=true +GLVersion=3,1 +GLSLVersion=1,40 +MesaVersion=20,2,1 +GalliumVersion=0,4 +DriverVersion=20,2,1 +Driver=12 +ChipClass=99999 +Compositor=1 + diff --git a/autotests/libkwineffects/data/glplatform/llvmpipe-3.0 b/autotests/libkwineffects/data/glplatform/llvmpipe-3.0 new file mode 100644 index 0000000..aaa8d56 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/llvmpipe-3.0 @@ -0,0 +1,22 @@ +[Driver] +Vendor=VMware, Inc. +Renderer=Gallium 0.4 on llvmpipe (LLVM 3.8, 256 bits) +Version=3.0 Mesa 11.2.0 +ShadingLanguageVersion=1.30 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Gallium=true +SoftwareEmulation=true +GLVersion=3,0 +GLSLVersion=1,30 +MesaVersion=11,2,0 +GalliumVersion=0,4 +DriverVersion=11,2,0 +Driver=12 +ChipClass=99999 +Compositor=1 + diff --git a/autotests/libkwineffects/data/glplatform/llvmpipe-5.0 b/autotests/libkwineffects/data/glplatform/llvmpipe-5.0 new file mode 100644 index 0000000..fac5420 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/llvmpipe-5.0 @@ -0,0 +1,22 @@ +[Driver] +Vendor=VMware, Inc. +Renderer=llvmpipe (LLVM 5.0, 256 bits) +Version=3.0 Mesa 17.2.6 +ShadingLanguageVersion=1.30 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Gallium=true +SoftwareEmulation=true +GLVersion=3,0 +GLSLVersion=1,30 +MesaVersion=17,2,6 +GalliumVersion=0,4 +DriverVersion=17,2,6 +Driver=12 +ChipClass=99999 +Compositor=1 + diff --git a/autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-560-4.5 b/autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-560-4.5 new file mode 100644 index 0000000..6496d03 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-560-4.5 @@ -0,0 +1,19 @@ +[Driver] +Vendor=NVIDIA Corporation +Renderer=GeForce GTX 560/PCIe/SSE2 +Version=4.5.0 NVIDIA 361.28 +ShadingLanguageVersion=4.50 NVIDIA + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Nvidia=true +PreferBufferSubData=true +GLVersion=4,5 +GLSLVersion=4,50 +DriverVersion=361,28 +Driver=8 +ChipClass=1005 +Compositor=1 + diff --git a/autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-660-3.1 b/autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-660-3.1 new file mode 100644 index 0000000..40f5f3b --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-660-3.1 @@ -0,0 +1,18 @@ +[Driver] +Vendor=NVIDIA Corporation +Renderer=GeForce GTX 660/PCIe/SSE2 +Version=3.1.0 NVIDIA 367.27 +ShadingLanguageVersion=1.40 NVIDIA via Cg compiler + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Nvidia=true +PreferBufferSubData=true +GLVersion=3,1 +GLSLVersion=1,40 +DriverVersion=367,27 +Driver=8 +ChipClass=1999 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-950-4.5 b/autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-950-4.5 new file mode 100644 index 0000000..6d5924f --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-950-4.5 @@ -0,0 +1,18 @@ +[Driver] +Vendor=NVIDIA Corporation +Renderer=GeForce GTX 950/PCIe/SSE2 +Version=4.5.0 NVIDIA 364.19 +ShadingLanguageVersion=4.50 NVIDIA + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Nvidia=true +PreferBufferSubData=true +GLVersion=4,5 +GLSLVersion=4,50 +DriverVersion=364,19 +Driver=8 +ChipClass=1999 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-970-3.1 b/autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-970-3.1 new file mode 100644 index 0000000..833d070 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-970-3.1 @@ -0,0 +1,18 @@ +[Driver] +Vendor=NVIDIA Corporation +Renderer=GeForce GTX 970/PCIe/SSE2 +Version=3.1.0 NVIDIA 367.35 +ShadingLanguageVersion=1.40 NVIDIA via Cg compiler + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Nvidia=true +PreferBufferSubData=true +GLVersion=3,1 +GLSLVersion=1,40 +DriverVersion=367,35 +Driver=8 +ChipClass=1999 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-970M-3.1 b/autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-970M-3.1 new file mode 100644 index 0000000..def61db --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-970M-3.1 @@ -0,0 +1,18 @@ +[Driver] +Vendor=NVIDIA Corporation +Renderer=GeForce GTX 970M/PCIe/SSE2 +Version=3.1.0 NVIDIA 364.12 +ShadingLanguageVersion=1.40 NVIDIA via Cg compiler + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Nvidia=true +PreferBufferSubData=true +GLVersion=3,1 +GLSLVersion=1,40 +DriverVersion=364,12 +Driver=8 +ChipClass=1999 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-980-3.1 b/autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-980-3.1 new file mode 100644 index 0000000..2b6ebc7 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/nvidia-geforce-gtx-980-3.1 @@ -0,0 +1,18 @@ +[Driver] +Vendor=NVIDIA Corporation +Renderer=GeForce GTX 980/PCIe/SSE2 +Version=3.1.0 NVIDIA 364.19 +ShadingLanguageVersion=1.40 NVIDIA via Cg compiler + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Nvidia=true +PreferBufferSubData=true +GLVersion=3,1 +GLSLVersion=1,40 +DriverVersion=364,19 +Driver=8 +ChipClass=1999 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/panfrost-malit860-desktop-3.0 b/autotests/libkwineffects/data/glplatform/panfrost-malit860-desktop-3.0 new file mode 100644 index 0000000..69a120d --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/panfrost-malit860-desktop-3.0 @@ -0,0 +1,19 @@ +[Driver] +Vendor=Panfrost +Renderer=Mali T860 (Panfrost) +Version=3.0 Mesa 19.1 +ShadingLanguageVersion=1.30 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Panfrost=true +GLVersion=3,0 +GLSLVersion=1,30 +MesaVersion=19,1 +DriverVersion=19,1 +Driver=18 +ChipClass=4001 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/qualcomm-adreno-330-libhybris-gles-3.0 b/autotests/libkwineffects/data/glplatform/qualcomm-adreno-330-libhybris-gles-3.0 new file mode 100644 index 0000000..1394c00 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/qualcomm-adreno-330-libhybris-gles-3.0 @@ -0,0 +1,16 @@ +[Driver] +Vendor=Qualcomm +Renderer=Adreno (TM) 330 +Version=OpenGL ES 2.0 (OpenGL ES 3.0 V@104.0 AU@ (GIT@Id3510ff6dc)) +ShadingLanguageVersion=OpenGL ES GLSL ES 3.00 + +[Settings] +GLSL=true +TextureNPOT=true +GLVersion=2,0 +GLSLVersion=3,0 +GLES=true +Adreno=true +Driver=15 +ChipClass=3002 +Compositor=1 diff --git a/autotests/libkwineffects/data/glplatform/virgl-3.1 b/autotests/libkwineffects/data/glplatform/virgl-3.1 new file mode 100644 index 0000000..3e66573 --- /dev/null +++ b/autotests/libkwineffects/data/glplatform/virgl-3.1 @@ -0,0 +1,22 @@ +[Driver] +Vendor=Red Hat +Renderer=virgl +Version=3.1 Mesa 19.0.8 +ShadingLanguageVersion=1.40 + +[Settings] +LooseBinding=true +GLSL=true +TextureNPOT=true +Mesa=true +Gallium=true +Virgl=true +VirtualMachine=true +GLVersion=3,1 +GLSLVersion=1,40 +MesaVersion=19,0,8 +GalliumVersion=0,4 +DriverVersion=19,0,8 +Driver=17 +ChipClass=99999 +Compositor=1 diff --git a/autotests/libkwineffects/kwinglplatformtest.cpp b/autotests/libkwineffects/kwinglplatformtest.cpp new file mode 100644 index 0000000..b776cfc --- /dev/null +++ b/autotests/libkwineffects/kwinglplatformtest.cpp @@ -0,0 +1,279 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "mock_gl.h" +#include +#include + +#include +#include + +Q_DECLARE_METATYPE(KWin::Driver) +Q_DECLARE_METATYPE(KWin::ChipClass) + +using namespace KWin; + +void KWin::cleanupGL() +{ + GLPlatform::cleanup(); +} + +class GLPlatformTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void cleanup(); + + void testDriverToString_data(); + void testDriverToString(); + void testChipClassToString_data(); + void testChipClassToString(); + void testPriorDetect(); + void testDetect_data(); + void testDetect(); +}; + +void GLPlatformTest::cleanup() +{ + cleanupGL(); + delete s_gl; + s_gl = nullptr; +} + +void GLPlatformTest::testDriverToString_data() +{ + QTest::addColumn("driver"); + QTest::addColumn("expected"); + + QTest::newRow("R100") << Driver_R100 << QStringLiteral("Radeon"); + QTest::newRow("R200") << Driver_R200 << QStringLiteral("R200"); + QTest::newRow("R300C") << Driver_R300C << QStringLiteral("R300C"); + QTest::newRow("R300G") << Driver_R300G << QStringLiteral("R300G"); + QTest::newRow("R600C") << Driver_R600C << QStringLiteral("R600C"); + QTest::newRow("R600G") << Driver_R600G << QStringLiteral("R600G"); + QTest::newRow("RadeonSI") << Driver_RadeonSI << QStringLiteral("RadeonSI"); + QTest::newRow("Nouveau") << Driver_Nouveau << QStringLiteral("Nouveau"); + QTest::newRow("Intel") << Driver_Intel << QStringLiteral("Intel"); + QTest::newRow("NVidia") << Driver_NVidia << QStringLiteral("NVIDIA"); + QTest::newRow("Catalyst") << Driver_Catalyst << QStringLiteral("Catalyst"); + QTest::newRow("Swrast") << Driver_Swrast << QStringLiteral("Software rasterizer"); + QTest::newRow("Softpipe") << Driver_Softpipe << QStringLiteral("softpipe"); + QTest::newRow("Llvmpipe") << Driver_Llvmpipe << QStringLiteral("LLVMpipe"); + QTest::newRow("VirtualBox") << Driver_VirtualBox << QStringLiteral("VirtualBox (Chromium)"); + QTest::newRow("VMware") << Driver_VMware << QStringLiteral("VMware (SVGA3D)"); + QTest::newRow("Qualcomm") << Driver_Qualcomm << QStringLiteral("Qualcomm"); + QTest::newRow("Virgl") << Driver_Virgl << QStringLiteral("Virgl (virtio-gpu, Qemu/KVM guest)"); + QTest::newRow("Panfrost") << Driver_Panfrost << QStringLiteral("Panfrost"); + QTest::newRow("Unknown") << Driver_Unknown << QStringLiteral("Unknown"); +} + +void GLPlatformTest::testDriverToString() +{ + QFETCH(Driver, driver); + QTEST(GLPlatform::driverToString(driver), "expected"); +} + +void GLPlatformTest::testChipClassToString_data() +{ + QTest::addColumn("chipClass"); + QTest::addColumn("expected"); + + QTest::newRow("R100") << R100 << QStringLiteral("R100"); + QTest::newRow("R200") << R200 << QStringLiteral("R200"); + QTest::newRow("R300") << R300 << QStringLiteral("R300"); + QTest::newRow("R400") << R400 << QStringLiteral("R400"); + QTest::newRow("R500") << R500 << QStringLiteral("R500"); + QTest::newRow("R600") << R600 << QStringLiteral("R600"); + QTest::newRow("R700") << R700 << QStringLiteral("R700"); + QTest::newRow("Evergreen") << Evergreen << QStringLiteral("EVERGREEN"); + QTest::newRow("NorthernIslands") << NorthernIslands << QStringLiteral("Northern Islands"); + QTest::newRow("SouthernIslands") << SouthernIslands << QStringLiteral("Southern Islands"); + QTest::newRow("SeaIslands") << SeaIslands << QStringLiteral("Sea Islands"); + QTest::newRow("VolcanicIslands") << VolcanicIslands << QStringLiteral("Volcanic Islands"); + QTest::newRow("Arctic Islands") << ArcticIslands << QStringLiteral("Arctic Islands"); + QTest::newRow("Vega") << Vega << QStringLiteral("Vega"); + QTest::newRow("UnknownRadeon") << UnknownRadeon << QStringLiteral("Unknown"); + QTest::newRow("NV10") << NV10 << QStringLiteral("NV10"); + QTest::newRow("NV20") << NV20 << QStringLiteral("NV20"); + QTest::newRow("NV30") << NV30 << QStringLiteral("NV30"); + QTest::newRow("NV40") << NV40 << QStringLiteral("NV40/G70"); + QTest::newRow("G80") << G80 << QStringLiteral("G80/G90"); + QTest::newRow("GF100") << GF100 << QStringLiteral("GF100"); + QTest::newRow("UnknownNVidia") << UnknownNVidia << QStringLiteral("Unknown"); + QTest::newRow("I8XX") << I8XX << QStringLiteral("i830/i835"); + QTest::newRow("I915") << I915 << QStringLiteral("i915/i945"); + QTest::newRow("I965") << I965 << QStringLiteral("i965"); + QTest::newRow("SandyBridge") << SandyBridge << QStringLiteral("SandyBridge"); + QTest::newRow("IvyBridge") << IvyBridge << QStringLiteral("IvyBridge"); + QTest::newRow("Haswell") << Haswell << QStringLiteral("Haswell"); + QTest::newRow("UnknownIntel") << UnknownIntel << QStringLiteral("Unknown"); + QTest::newRow("Adreno1XX") << Adreno1XX << QStringLiteral("Adreno 1xx series"); + QTest::newRow("Adreno2XX") << Adreno2XX << QStringLiteral("Adreno 2xx series"); + QTest::newRow("Adreno3XX") << Adreno3XX << QStringLiteral("Adreno 3xx series"); + QTest::newRow("Adreno4XX") << Adreno4XX << QStringLiteral("Adreno 4xx series"); + QTest::newRow("Adreno5XX") << Adreno5XX << QStringLiteral("Adreno 5xx series"); + QTest::newRow("UnknownAdreno") << UnknownAdreno << QStringLiteral("Unknown"); + QTest::newRow("MaliT7XX") << MaliT7XX << QStringLiteral("Mali T7xx series"); + QTest::newRow("MaliT8XX") << MaliT8XX << QStringLiteral("Mali T8xx series"); + QTest::newRow("MaliGXX") << MaliGXX << QStringLiteral("Mali Gxx series"); + QTest::newRow("UnknownPanfrost") << UnknownAdreno << QStringLiteral("Unknown"); + QTest::newRow("UnknownChipClass") << UnknownChipClass << QStringLiteral("Unknown"); +} + +void GLPlatformTest::testChipClassToString() +{ + QFETCH(ChipClass, chipClass); + QTEST(GLPlatform::chipClassToString(chipClass), "expected"); +} + +void GLPlatformTest::testPriorDetect() +{ + auto *gl = GLPlatform::instance(); + QVERIFY(gl); + QCOMPARE(gl->supports(LooseBinding), false); + QCOMPARE(gl->supports(GLSL), false); + QCOMPARE(gl->supports(LimitedGLSL), false); + QCOMPARE(gl->supports(TextureNPOT), false); + QCOMPARE(gl->supports(LimitedNPOT), false); + + QCOMPARE(gl->glVersion(), 0); + QCOMPARE(gl->glslVersion(), 0); + QCOMPARE(gl->mesaVersion(), 0); + QCOMPARE(gl->galliumVersion(), 0); + QCOMPARE(gl->serverVersion(), 0); + QCOMPARE(gl->kernelVersion(), 0); + QCOMPARE(gl->driverVersion(), 0); + + QCOMPARE(gl->driver(), Driver_Unknown); + QCOMPARE(gl->chipClass(), UnknownChipClass); + + QCOMPARE(gl->isMesaDriver(), false); + QCOMPARE(gl->isGalliumDriver(), false); + QCOMPARE(gl->isRadeon(), false); + QCOMPARE(gl->isNvidia(), false); + QCOMPARE(gl->isIntel(), false); + QCOMPARE(gl->isPanfrost(), false); + QCOMPARE(gl->isVirtualBox(), false); + QCOMPARE(gl->isVMware(), false); + + QCOMPARE(gl->isSoftwareEmulation(), false); + QCOMPARE(gl->isVirtualMachine(), false); + + QCOMPARE(gl->glVersionString(), QByteArray()); + QCOMPARE(gl->glRendererString(), QByteArray()); + QCOMPARE(gl->glVendorString(), QByteArray()); + QCOMPARE(gl->glShadingLanguageVersionString(), QByteArray()); + + QCOMPARE(gl->isLooseBinding(), false); + QCOMPARE(gl->isGLES(), false); + QCOMPARE(gl->recommendedCompositor(), QPainterCompositing); + QCOMPARE(gl->preferBufferSubData(), false); + QCOMPARE(gl->platformInterface(), NoOpenGLPlatformInterface); +} + +void GLPlatformTest::testDetect_data() +{ + QTest::addColumn("configFile"); + + QDir dir(QFINDTESTDATA("data/glplatform")); + const QStringList entries = dir.entryList(QDir::NoDotAndDotDot | QDir::Files); + + for (const QString &file : entries) { + QTest::newRow(file.toUtf8().constData()) << dir.absoluteFilePath(file); + } +} + +static qint64 readVersion(const KConfigGroup &group, const char *entry) +{ + const QStringList parts = group.readEntry(entry, QString()).split(','); + if (parts.count() < 2) { + return 0; + } + QVector versionParts; + for (int i = 0; i < parts.count(); ++i) { + bool ok = false; + const auto value = parts.at(i).toLongLong(&ok); + if (ok) { + versionParts << value; + } else { + versionParts << 0; + } + } + while (versionParts.count() < 3) { + versionParts << 0; + } + return kVersionNumber(versionParts.at(0), versionParts.at(1), versionParts.at(2)); +} + +void GLPlatformTest::testDetect() +{ + QFETCH(QString, configFile); + KConfig config(configFile); + const KConfigGroup driverGroup = config.group("Driver"); + s_gl = new MockGL; + s_gl->getString.vendor = driverGroup.readEntry("Vendor").toUtf8(); + s_gl->getString.renderer = driverGroup.readEntry("Renderer").toUtf8(); + s_gl->getString.version = driverGroup.readEntry("Version").toUtf8(); + s_gl->getString.shadingLanguageVersion = driverGroup.readEntry("ShadingLanguageVersion").toUtf8(); + s_gl->getString.extensions = QVector{QByteArrayLiteral("GL_ARB_shader_objects"), + QByteArrayLiteral("GL_ARB_fragment_shader"), + QByteArrayLiteral("GL_ARB_vertex_shader"), + QByteArrayLiteral("GL_ARB_texture_non_power_of_two")}; + s_gl->getString.extensionsString = QByteArray(); + + auto *gl = GLPlatform::instance(); + QVERIFY(gl); + gl->detect(EglPlatformInterface); + QCOMPARE(gl->platformInterface(), EglPlatformInterface); + + const KConfigGroup settingsGroup = config.group("Settings"); + + QCOMPARE(gl->supports(LooseBinding), settingsGroup.readEntry("LooseBinding", false)); + QCOMPARE(gl->supports(GLSL), settingsGroup.readEntry("GLSL", false)); + QCOMPARE(gl->supports(LimitedGLSL), settingsGroup.readEntry("LimitedGLSL", false)); + QCOMPARE(gl->supports(TextureNPOT), settingsGroup.readEntry("TextureNPOT", false)); + QCOMPARE(gl->supports(LimitedNPOT), settingsGroup.readEntry("LimitedNPOT", false)); + + QCOMPARE(gl->glVersion(), readVersion(settingsGroup, "GLVersion")); + QCOMPARE(gl->glslVersion(), readVersion(settingsGroup, "GLSLVersion")); + QCOMPARE(gl->mesaVersion(), readVersion(settingsGroup, "MesaVersion")); + QCOMPARE(gl->galliumVersion(), readVersion(settingsGroup, "GalliumVersion")); + QCOMPARE(gl->serverVersion(), 0); + QEXPECT_FAIL("amd-catalyst-radeonhd-7700M-3.1.13399", "Detects GL version instead of driver version", Continue); + QCOMPARE(gl->driverVersion(), readVersion(settingsGroup, "DriverVersion")); + + QCOMPARE(gl->driver(), Driver(settingsGroup.readEntry("Driver", int(Driver_Unknown)))); + QCOMPARE(gl->chipClass(), ChipClass(settingsGroup.readEntry("ChipClass", int(UnknownChipClass)))); + + QCOMPARE(gl->isMesaDriver(), settingsGroup.readEntry("Mesa", false)); + QCOMPARE(gl->isGalliumDriver(), settingsGroup.readEntry("Gallium", false)); + QCOMPARE(gl->isRadeon(), settingsGroup.readEntry("Radeon", false)); + QCOMPARE(gl->isNvidia(), settingsGroup.readEntry("Nvidia", false)); + QCOMPARE(gl->isIntel(), settingsGroup.readEntry("Intel", false)); + QCOMPARE(gl->isVirtualBox(), settingsGroup.readEntry("VirtualBox", false)); + QCOMPARE(gl->isVMware(), settingsGroup.readEntry("VMware", false)); + QCOMPARE(gl->isAdreno(), settingsGroup.readEntry("Adreno", false)); + QCOMPARE(gl->isPanfrost(), settingsGroup.readEntry("Panfrost", false)); + QCOMPARE(gl->isVirgl(), settingsGroup.readEntry("Virgl", false)); + + QCOMPARE(gl->isSoftwareEmulation(), settingsGroup.readEntry("SoftwareEmulation", false)); + QCOMPARE(gl->isVirtualMachine(), settingsGroup.readEntry("VirtualMachine", false)); + + QCOMPARE(gl->glVersionString(), s_gl->getString.version); + QCOMPARE(gl->glRendererString(), s_gl->getString.renderer); + QCOMPARE(gl->glVendorString(), s_gl->getString.vendor); + QCOMPARE(gl->glShadingLanguageVersionString(), s_gl->getString.shadingLanguageVersion); + + QCOMPARE(gl->isLooseBinding(), settingsGroup.readEntry("LooseBinding", false)); + QCOMPARE(gl->isGLES(), settingsGroup.readEntry("GLES", false)); + QCOMPARE(gl->recommendedCompositor(), CompositingType(settingsGroup.readEntry("Compositor", int(NoCompositing)))); + QCOMPARE(gl->preferBufferSubData(), settingsGroup.readEntry("PreferBufferSubData", false)); +} + +QTEST_GUILESS_MAIN(GLPlatformTest) +#include "kwinglplatformtest.moc" diff --git a/autotests/libkwineffects/mock_gl.cpp b/autotests/libkwineffects/mock_gl.cpp new file mode 100644 index 0000000..13a258c --- /dev/null +++ b/autotests/libkwineffects/mock_gl.cpp @@ -0,0 +1,59 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "mock_gl.h" +#include + +MockGL *s_gl = nullptr; + +static const GLubyte *mock_glGetString(GLenum name) +{ + if (!s_gl) { + return nullptr; + } + switch (name) { + case GL_VENDOR: + return (const GLubyte *)s_gl->getString.vendor.constData(); + case GL_RENDERER: + return (const GLubyte *)s_gl->getString.renderer.constData(); + case GL_VERSION: + return (const GLubyte *)s_gl->getString.version.constData(); + case GL_EXTENSIONS: + return (const GLubyte *)s_gl->getString.extensionsString.constData(); + case GL_SHADING_LANGUAGE_VERSION: + return (const GLubyte *)s_gl->getString.shadingLanguageVersion.constData(); + default: + return nullptr; + } +} + +static const GLubyte *mock_glGetStringi(GLenum name, GLuint index) +{ + if (!s_gl) { + return nullptr; + } + if (name == GL_EXTENSIONS && index < uint(s_gl->getString.extensions.count())) { + return (const GLubyte *)s_gl->getString.extensions.at(index).constData(); + } + return nullptr; +} + +static void mock_glGetIntegerv(GLenum pname, GLint *data) +{ + Q_UNUSED(pname) + Q_UNUSED(data) + if (pname == GL_NUM_EXTENSIONS) { + if (data && s_gl) { + *data = s_gl->getString.extensions.count(); + } + } +} + +PFNGLGETSTRINGPROC epoxy_glGetString = mock_glGetString; +PFNGLGETSTRINGIPROC epoxy_glGetStringi = mock_glGetStringi; +PFNGLGETINTEGERVPROC epoxy_glGetIntegerv = mock_glGetIntegerv; diff --git a/autotests/libkwineffects/mock_gl.h b/autotests/libkwineffects/mock_gl.h new file mode 100644 index 0000000..ff71581 --- /dev/null +++ b/autotests/libkwineffects/mock_gl.h @@ -0,0 +1,30 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2016 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#ifndef MOCK_GL_H +#define MOCK_GL_H + +#include +#include + +struct MockGL +{ + struct + { + QByteArray vendor; + QByteArray renderer; + QByteArray version; + QVector extensions; + QByteArray extensionsString; + QByteArray shadingLanguageVersion; + } getString; +}; + +extern MockGL *s_gl; + +#endif diff --git a/autotests/libkwineffects/timelinetest.cpp b/autotests/libkwineffects/timelinetest.cpp new file mode 100644 index 0000000..16dc32a --- /dev/null +++ b/autotests/libkwineffects/timelinetest.cpp @@ -0,0 +1,415 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2018 Vlad Zahorodnii + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include + +#include + +using namespace std::chrono_literals; + +// FIXME: Delete it in the future. +Q_DECLARE_METATYPE(std::chrono::milliseconds) + +class TimeLineTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testUpdateForward(); + void testUpdateBackward(); + void testUpdateFinished(); + void testToggleDirection(); + void testReset(); + void testSetElapsed_data(); + void testSetElapsed(); + void testSetDuration(); + void testSetDurationRetargeting(); + void testSetDurationRetargetingSmallDuration(); + void testRunning(); + void testStrictRedirectSourceMode_data(); + void testStrictRedirectSourceMode(); + void testRelaxedRedirectSourceMode_data(); + void testRelaxedRedirectSourceMode(); + void testStrictRedirectTargetMode_data(); + void testStrictRedirectTargetMode(); + void testRelaxedRedirectTargetMode_data(); + void testRelaxedRedirectTargetMode(); +}; + +void TimeLineTest::testUpdateForward() +{ + KWin::TimeLine timeLine(1000ms, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + + // 0/1000 + timeLine.advance(0ms); + QCOMPARE(timeLine.value(), 0.0); + QVERIFY(!timeLine.done()); + + // 100/1000 + timeLine.advance(100ms); + QCOMPARE(timeLine.value(), 0.1); + QVERIFY(!timeLine.done()); + + // 400/1000 + timeLine.advance(400ms); + QCOMPARE(timeLine.value(), 0.4); + QVERIFY(!timeLine.done()); + + // 900/1000 + timeLine.advance(900ms); + QCOMPARE(timeLine.value(), 0.9); + QVERIFY(!timeLine.done()); + + // 1000/1000 + timeLine.advance(3000ms); + QCOMPARE(timeLine.value(), 1.0); + QVERIFY(timeLine.done()); +} + +void TimeLineTest::testUpdateBackward() +{ + KWin::TimeLine timeLine(1000ms, KWin::TimeLine::Backward); + timeLine.setEasingCurve(QEasingCurve::Linear); + + // 0/1000 + timeLine.advance(0ms); + QCOMPARE(timeLine.value(), 1.0); + QVERIFY(!timeLine.done()); + + // 100/1000 + timeLine.advance(100ms); + QCOMPARE(timeLine.value(), 0.9); + QVERIFY(!timeLine.done()); + + // 400/1000 + timeLine.advance(400ms); + QCOMPARE(timeLine.value(), 0.6); + QVERIFY(!timeLine.done()); + + // 900/1000 + timeLine.advance(900ms); + QCOMPARE(timeLine.value(), 0.1); + QVERIFY(!timeLine.done()); + + // 1000/1000 + timeLine.advance(3000ms); + QCOMPARE(timeLine.value(), 0.0); + QVERIFY(timeLine.done()); +} + +void TimeLineTest::testUpdateFinished() +{ + KWin::TimeLine timeLine(1000ms, KWin::TimeLine::Forward); + timeLine.advance(0ms); + timeLine.setEasingCurve(QEasingCurve::Linear); + + timeLine.advance(1000ms); + QCOMPARE(timeLine.value(), 1.0); + QVERIFY(timeLine.done()); + + timeLine.advance(1042ms); + QCOMPARE(timeLine.value(), 1.0); + QVERIFY(timeLine.done()); +} + +void TimeLineTest::testToggleDirection() +{ + KWin::TimeLine timeLine(1000ms, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + + timeLine.advance(0ms); + QCOMPARE(timeLine.value(), 0.0); + QVERIFY(!timeLine.done()); + + timeLine.advance(600ms); + QCOMPARE(timeLine.value(), 0.6); + QVERIFY(!timeLine.done()); + + timeLine.toggleDirection(); + QCOMPARE(timeLine.value(), 0.6); + QVERIFY(!timeLine.done()); + + timeLine.advance(800ms); + QCOMPARE(timeLine.value(), 0.4); + QVERIFY(!timeLine.done()); + + timeLine.advance(3000ms); + QCOMPARE(timeLine.value(), 0.0); + QVERIFY(timeLine.done()); +} + +void TimeLineTest::testReset() +{ + KWin::TimeLine timeLine(1000ms, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + timeLine.advance(0ms); + + timeLine.advance(1000ms); + QCOMPARE(timeLine.value(), 1.0); + QVERIFY(timeLine.done()); + + timeLine.reset(); + QCOMPARE(timeLine.value(), 0.0); + QVERIFY(!timeLine.done()); +} + +void TimeLineTest::testSetElapsed_data() +{ + QTest::addColumn("duration"); + QTest::addColumn("elapsed"); + QTest::addColumn("expectedElapsed"); + QTest::addColumn("expectedDone"); + QTest::addColumn("initiallyDone"); + + QTest::newRow("Less than duration, not finished") << 1000ms << 300ms << 300ms << false << false; + QTest::newRow("Less than duration, finished") << 1000ms << 300ms << 300ms << false << true; + QTest::newRow("Greater than duration, not finished") << 1000ms << 3000ms << 1000ms << true << false; + QTest::newRow("Greater than duration, finished") << 1000ms << 3000ms << 1000ms << true << true; + QTest::newRow("Equal to duration, not finished") << 1000ms << 1000ms << 1000ms << true << false; + QTest::newRow("Equal to duration, finished") << 1000ms << 1000ms << 1000ms << true << true; +} + +void TimeLineTest::testSetElapsed() +{ + QFETCH(std::chrono::milliseconds, duration); + QFETCH(std::chrono::milliseconds, elapsed); + QFETCH(std::chrono::milliseconds, expectedElapsed); + QFETCH(bool, expectedDone); + QFETCH(bool, initiallyDone); + + KWin::TimeLine timeLine(duration, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + timeLine.advance(0ms); + + if (initiallyDone) { + timeLine.advance(duration); + QVERIFY(timeLine.done()); + } + + timeLine.setElapsed(elapsed); + QCOMPARE(timeLine.elapsed(), expectedElapsed); + QCOMPARE(timeLine.done(), expectedDone); +} + +void TimeLineTest::testSetDuration() +{ + KWin::TimeLine timeLine(1000ms, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + + QCOMPARE(timeLine.duration(), 1000ms); + + timeLine.setDuration(3000ms); + QCOMPARE(timeLine.duration(), 3000ms); +} + +void TimeLineTest::testSetDurationRetargeting() +{ + KWin::TimeLine timeLine(1000ms, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + timeLine.advance(0ms); + + timeLine.advance(500ms); + QCOMPARE(timeLine.value(), 0.5); + QVERIFY(!timeLine.done()); + + timeLine.setDuration(3000ms); + QCOMPARE(timeLine.value(), 0.5); + QVERIFY(!timeLine.done()); +} + +void TimeLineTest::testSetDurationRetargetingSmallDuration() +{ + KWin::TimeLine timeLine(1000ms, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + timeLine.advance(0ms); + + timeLine.advance(999ms); + QCOMPARE(timeLine.value(), 0.999); + QVERIFY(!timeLine.done()); + + timeLine.setDuration(3ms); + QCOMPARE(timeLine.value(), 1.0); + QVERIFY(timeLine.done()); +} + +void TimeLineTest::testRunning() +{ + KWin::TimeLine timeLine(1000ms, KWin::TimeLine::Forward); + timeLine.setEasingCurve(QEasingCurve::Linear); + timeLine.advance(0ms); + + QVERIFY(!timeLine.running()); + QVERIFY(!timeLine.done()); + + timeLine.advance(100ms); + QVERIFY(timeLine.running()); + QVERIFY(!timeLine.done()); + + timeLine.advance(1000ms); + QVERIFY(!timeLine.running()); + QVERIFY(timeLine.done()); +} + +void TimeLineTest::testStrictRedirectSourceMode_data() +{ + QTest::addColumn("initialDirection"); + QTest::addColumn("initialValue"); + QTest::addColumn("finalDirection"); + QTest::addColumn("finalValue"); + + QTest::newRow("forward -> backward") << KWin::TimeLine::Forward << 0.0 << KWin::TimeLine::Backward << 0.0; + QTest::newRow("backward -> forward") << KWin::TimeLine::Backward << 1.0 << KWin::TimeLine::Forward << 1.0; +} + +void TimeLineTest::testStrictRedirectSourceMode() +{ + QFETCH(KWin::TimeLine::Direction, initialDirection); + KWin::TimeLine timeLine(1000ms, initialDirection); + timeLine.setEasingCurve(QEasingCurve::Linear); + timeLine.setSourceRedirectMode(KWin::TimeLine::RedirectMode::Strict); + + QTEST(timeLine.direction(), "initialDirection"); + QTEST(timeLine.value(), "initialValue"); + QCOMPARE(timeLine.sourceRedirectMode(), KWin::TimeLine::RedirectMode::Strict); + QVERIFY(!timeLine.running()); + QVERIFY(!timeLine.done()); + + QFETCH(KWin::TimeLine::Direction, finalDirection); + timeLine.setDirection(finalDirection); + + QTEST(timeLine.direction(), "finalDirection"); + QTEST(timeLine.value(), "finalValue"); + QCOMPARE(timeLine.sourceRedirectMode(), KWin::TimeLine::RedirectMode::Strict); + QVERIFY(!timeLine.running()); + QVERIFY(timeLine.done()); +} + +void TimeLineTest::testRelaxedRedirectSourceMode_data() +{ + QTest::addColumn("initialDirection"); + QTest::addColumn("initialValue"); + QTest::addColumn("finalDirection"); + QTest::addColumn("finalValue"); + + QTest::newRow("forward -> backward") << KWin::TimeLine::Forward << 0.0 << KWin::TimeLine::Backward << 1.0; + QTest::newRow("backward -> forward") << KWin::TimeLine::Backward << 1.0 << KWin::TimeLine::Forward << 0.0; +} + +void TimeLineTest::testRelaxedRedirectSourceMode() +{ + QFETCH(KWin::TimeLine::Direction, initialDirection); + KWin::TimeLine timeLine(1000ms, initialDirection); + timeLine.setEasingCurve(QEasingCurve::Linear); + timeLine.setSourceRedirectMode(KWin::TimeLine::RedirectMode::Relaxed); + + QTEST(timeLine.direction(), "initialDirection"); + QTEST(timeLine.value(), "initialValue"); + QCOMPARE(timeLine.sourceRedirectMode(), KWin::TimeLine::RedirectMode::Relaxed); + QVERIFY(!timeLine.running()); + QVERIFY(!timeLine.done()); + + QFETCH(KWin::TimeLine::Direction, finalDirection); + timeLine.setDirection(finalDirection); + + QTEST(timeLine.direction(), "finalDirection"); + QTEST(timeLine.value(), "finalValue"); + QCOMPARE(timeLine.sourceRedirectMode(), KWin::TimeLine::RedirectMode::Relaxed); + QVERIFY(!timeLine.running()); + QVERIFY(!timeLine.done()); +} + +void TimeLineTest::testStrictRedirectTargetMode_data() +{ + QTest::addColumn("initialDirection"); + QTest::addColumn("initialValue"); + QTest::addColumn("finalDirection"); + QTest::addColumn("finalValue"); + + QTest::newRow("forward -> backward") << KWin::TimeLine::Forward << 0.0 << KWin::TimeLine::Backward << 1.0; + QTest::newRow("backward -> forward") << KWin::TimeLine::Backward << 1.0 << KWin::TimeLine::Forward << 0.0; +} + +void TimeLineTest::testStrictRedirectTargetMode() +{ + QFETCH(KWin::TimeLine::Direction, initialDirection); + KWin::TimeLine timeLine(1000ms, initialDirection); + timeLine.setEasingCurve(QEasingCurve::Linear); + timeLine.setTargetRedirectMode(KWin::TimeLine::RedirectMode::Strict); + timeLine.advance(0ms); + + QTEST(timeLine.direction(), "initialDirection"); + QTEST(timeLine.value(), "initialValue"); + QCOMPARE(timeLine.targetRedirectMode(), KWin::TimeLine::RedirectMode::Strict); + QVERIFY(!timeLine.running()); + QVERIFY(!timeLine.done()); + + timeLine.advance(1000ms); + QTEST(timeLine.value(), "finalValue"); + QVERIFY(!timeLine.running()); + QVERIFY(timeLine.done()); + + QFETCH(KWin::TimeLine::Direction, finalDirection); + timeLine.setDirection(finalDirection); + + QTEST(timeLine.direction(), "finalDirection"); + QTEST(timeLine.value(), "finalValue"); + QVERIFY(!timeLine.running()); + QVERIFY(timeLine.done()); +} + +void TimeLineTest::testRelaxedRedirectTargetMode_data() +{ + QTest::addColumn("initialDirection"); + QTest::addColumn("initialValue"); + QTest::addColumn("finalDirection"); + QTest::addColumn("finalValue"); + + QTest::newRow("forward -> backward") << KWin::TimeLine::Forward << 0.0 << KWin::TimeLine::Backward << 1.0; + QTest::newRow("backward -> forward") << KWin::TimeLine::Backward << 1.0 << KWin::TimeLine::Forward << 0.0; +} + +void TimeLineTest::testRelaxedRedirectTargetMode() +{ + QFETCH(KWin::TimeLine::Direction, initialDirection); + KWin::TimeLine timeLine(1000ms, initialDirection); + timeLine.setEasingCurve(QEasingCurve::Linear); + timeLine.setTargetRedirectMode(KWin::TimeLine::RedirectMode::Relaxed); + timeLine.advance(0ms); + + QTEST(timeLine.direction(), "initialDirection"); + QTEST(timeLine.value(), "initialValue"); + QCOMPARE(timeLine.targetRedirectMode(), KWin::TimeLine::RedirectMode::Relaxed); + QVERIFY(!timeLine.running()); + QVERIFY(!timeLine.done()); + + timeLine.advance(1000ms); + QTEST(timeLine.value(), "finalValue"); + QVERIFY(!timeLine.running()); + QVERIFY(timeLine.done()); + + QFETCH(KWin::TimeLine::Direction, finalDirection); + timeLine.setDirection(finalDirection); + timeLine.advance(1000ms); + + QTEST(timeLine.direction(), "finalDirection"); + QTEST(timeLine.value(), "finalValue"); + QVERIFY(!timeLine.running()); + QVERIFY(!timeLine.done()); + + timeLine.advance(2000ms); + QTEST(timeLine.direction(), "finalDirection"); + QTEST(timeLine.value(), "initialValue"); + QVERIFY(!timeLine.running()); + QVERIFY(timeLine.done()); +} + +QTEST_MAIN(TimeLineTest) + +#include "timelinetest.moc" diff --git a/autotests/libkwineffects/windowquadlisttest.cpp b/autotests/libkwineffects/windowquadlisttest.cpp new file mode 100644 index 0000000..e7c755a --- /dev/null +++ b/autotests/libkwineffects/windowquadlisttest.cpp @@ -0,0 +1,212 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2013 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include +#include + +Q_DECLARE_METATYPE(KWin::WindowQuadList) + +class WindowQuadListTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testMakeGrid_data(); + void testMakeGrid(); + void testMakeRegularGrid_data(); + void testMakeRegularGrid(); + +private: + KWin::WindowQuad makeQuad(const QRectF &rect); +}; + +KWin::WindowQuad WindowQuadListTest::makeQuad(const QRectF &r) +{ + KWin::WindowQuad quad; + quad[0] = KWin::WindowVertex(r.x(), r.y(), r.x(), r.y()); + quad[1] = KWin::WindowVertex(r.x() + r.width(), r.y(), r.x() + r.width(), r.y()); + quad[2] = KWin::WindowVertex(r.x() + r.width(), r.y() + r.height(), r.x() + r.width(), r.y() + r.height()); + quad[3] = KWin::WindowVertex(r.x(), r.y() + r.height(), r.x(), r.y() + r.height()); + return quad; +} + +void WindowQuadListTest::testMakeGrid_data() +{ + QTest::addColumn("orig"); + QTest::addColumn("quadSize"); + QTest::addColumn("expectedCount"); + QTest::addColumn("expected"); + + KWin::WindowQuadList orig; + KWin::WindowQuadList expected; + + QTest::newRow("empty") << orig << 10 << 0 << expected; + + orig.append(makeQuad(QRectF(0, 0, 10, 10))); + expected.append(makeQuad(QRectF(0, 0, 10, 10))); + QTest::newRow("quadSizeTooLarge") << orig << 10 << 1 << expected; + + expected.clear(); + expected.append(makeQuad(QRectF(0, 0, 5, 5))); + expected.append(makeQuad(QRectF(0, 5, 5, 5))); + expected.append(makeQuad(QRectF(5, 0, 5, 5))); + expected.append(makeQuad(QRectF(5, 5, 5, 5))); + QTest::newRow("regularGrid") << orig << 5 << 4 << expected; + + expected.clear(); + expected.append(makeQuad(QRectF(0, 0, 9, 9))); + expected.append(makeQuad(QRectF(0, 9, 9, 1))); + expected.append(makeQuad(QRectF(9, 0, 1, 9))); + expected.append(makeQuad(QRectF(9, 9, 1, 1))); + QTest::newRow("irregularGrid") << orig << 9 << 4 << expected; + + orig.append(makeQuad(QRectF(0, 10, 4, 3))); + expected.clear(); + expected.append(makeQuad(QRectF(0, 0, 4, 4))); + expected.append(makeQuad(QRectF(0, 4, 4, 4))); + expected.append(makeQuad(QRectF(0, 8, 4, 2))); + expected.append(makeQuad(QRectF(0, 10, 4, 2))); + expected.append(makeQuad(QRectF(0, 12, 4, 1))); + expected.append(makeQuad(QRectF(4, 0, 4, 4))); + expected.append(makeQuad(QRectF(4, 4, 4, 4))); + expected.append(makeQuad(QRectF(4, 8, 4, 2))); + expected.append(makeQuad(QRectF(8, 0, 2, 4))); + expected.append(makeQuad(QRectF(8, 4, 2, 4))); + expected.append(makeQuad(QRectF(8, 8, 2, 2))); + QTest::newRow("irregularGrid2") << orig << 4 << 11 << expected; +} + +void WindowQuadListTest::testMakeGrid() +{ + QFETCH(KWin::WindowQuadList, orig); + QFETCH(int, quadSize); + KWin::WindowQuadList actual = orig.makeGrid(quadSize); + QTEST(actual.count(), "expectedCount"); + + QFETCH(KWin::WindowQuadList, expected); + for (auto it = actual.constBegin(); it != actual.constEnd(); ++it) { + bool found = false; + const KWin::WindowQuad &actualQuad = (*it); + for (auto it2 = expected.constBegin(); it2 != expected.constEnd(); ++it2) { + const KWin::WindowQuad &expectedQuad = (*it2); + auto vertexTest = [actualQuad, expectedQuad](int index) { + const KWin::WindowVertex &actualVertex = actualQuad[index]; + const KWin::WindowVertex &expectedVertex = expectedQuad[index]; + if (actualVertex.x() != expectedVertex.x()) { + return false; + } + if (actualVertex.y() != expectedVertex.y()) { + return false; + } + if (!qFuzzyIsNull(actualVertex.u() - expectedVertex.u())) { + return false; + } + if (!qFuzzyIsNull(actualVertex.v() - expectedVertex.v())) { + return false; + } + return true; + }; + found = vertexTest(0) && vertexTest(1) && vertexTest(2) && vertexTest(3); + if (found) { + break; + } + } + QVERIFY2(found, qPrintable(QStringLiteral("%0, %1 / %2, %3").arg(QString::number(actualQuad.left()), QString::number(actualQuad.top()), QString::number(actualQuad.right()), QString::number(actualQuad.bottom())))); + } +} + +void WindowQuadListTest::testMakeRegularGrid_data() +{ + QTest::addColumn("orig"); + QTest::addColumn("xSubdivisions"); + QTest::addColumn("ySubdivisions"); + QTest::addColumn("expectedCount"); + QTest::addColumn("expected"); + + KWin::WindowQuadList orig; + KWin::WindowQuadList expected; + + QTest::newRow("empty") << orig << 1 << 1 << 0 << expected; + + orig.append(makeQuad(QRectF(0, 0, 10, 10))); + expected.append(makeQuad(QRectF(0, 0, 10, 10))); + QTest::newRow("noSplit") << orig << 1 << 1 << 1 << expected; + + expected.clear(); + expected.append(makeQuad(QRectF(0, 0, 5, 10))); + expected.append(makeQuad(QRectF(5, 0, 5, 10))); + QTest::newRow("xSplit") << orig << 2 << 1 << 2 << expected; + + expected.clear(); + expected.append(makeQuad(QRectF(0, 0, 10, 5))); + expected.append(makeQuad(QRectF(0, 5, 10, 5))); + QTest::newRow("ySplit") << orig << 1 << 2 << 2 << expected; + + expected.clear(); + expected.append(makeQuad(QRectF(0, 0, 5, 5))); + expected.append(makeQuad(QRectF(5, 0, 5, 5))); + expected.append(makeQuad(QRectF(0, 5, 5, 5))); + expected.append(makeQuad(QRectF(5, 5, 5, 5))); + QTest::newRow("xySplit") << orig << 2 << 2 << 4 << expected; + + orig.append(makeQuad(QRectF(0, 10, 4, 2))); + expected.clear(); + expected.append(makeQuad(QRectF(0, 0, 5, 3))); + expected.append(makeQuad(QRectF(5, 0, 5, 3))); + expected.append(makeQuad(QRectF(0, 3, 5, 3))); + expected.append(makeQuad(QRectF(5, 3, 5, 3))); + expected.append(makeQuad(QRectF(0, 6, 5, 3))); + expected.append(makeQuad(QRectF(5, 6, 5, 3))); + expected.append(makeQuad(QRectF(0, 9, 5, 1))); + expected.append(makeQuad(QRectF(0, 10, 4, 2))); + expected.append(makeQuad(QRectF(5, 9, 5, 1))); + QTest::newRow("multipleQuads") << orig << 2 << 4 << 9 << expected; +} + +void WindowQuadListTest::testMakeRegularGrid() +{ + QFETCH(KWin::WindowQuadList, orig); + QFETCH(int, xSubdivisions); + QFETCH(int, ySubdivisions); + KWin::WindowQuadList actual = orig.makeRegularGrid(xSubdivisions, ySubdivisions); + QTEST(actual.count(), "expectedCount"); + + QFETCH(KWin::WindowQuadList, expected); + for (auto it = actual.constBegin(); it != actual.constEnd(); ++it) { + bool found = false; + const KWin::WindowQuad &actualQuad = (*it); + for (auto it2 = expected.constBegin(); it2 != expected.constEnd(); ++it2) { + const KWin::WindowQuad &expectedQuad = (*it2); + auto vertexTest = [actualQuad, expectedQuad](int index) { + const KWin::WindowVertex &actualVertex = actualQuad[index]; + const KWin::WindowVertex &expectedVertex = expectedQuad[index]; + if (actualVertex.x() != expectedVertex.x()) { + return false; + } + if (actualVertex.y() != expectedVertex.y()) { + return false; + } + if (!qFuzzyIsNull(actualVertex.u() - expectedVertex.u())) { + return false; + } + if (!qFuzzyIsNull(actualVertex.v() - expectedVertex.v())) { + return false; + } + return true; + }; + found = vertexTest(0) && vertexTest(1) && vertexTest(2) && vertexTest(3); + if (found) { + break; + } + } + QVERIFY2(found, qPrintable(QStringLiteral("%0, %1 / %2, %3").arg(QString::number(actualQuad.left()), QString::number(actualQuad.top()), QString::number(actualQuad.right()), QString::number(actualQuad.bottom())))); + } +} + +QTEST_MAIN(WindowQuadListTest) + +#include "windowquadlisttest.moc" diff --git a/autotests/onscreennotificationtest.cpp b/autotests/onscreennotificationtest.cpp new file mode 100644 index 0000000..40c2df7 --- /dev/null +++ b/autotests/onscreennotificationtest.cpp @@ -0,0 +1,124 @@ +/* + SPDX-FileCopyrightText: 2016 Martin Graesslin + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +*/ + +#include "onscreennotificationtest.h" + +#include "input.h" +#include "onscreennotification.h" + +#include +#include + +#include +#include +#include + +QTEST_MAIN(OnScreenNotificationTest); + +namespace KWin +{ + +void InputRedirection::installInputEventSpy(InputEventSpy *spy) +{ + Q_UNUSED(spy); +} + +void InputRedirection::uninstallInputEventSpy(InputEventSpy *spy) +{ + Q_UNUSED(spy); +} + +InputRedirection *InputRedirection::s_self = nullptr; + +} + +using KWin::OnScreenNotification; + +void OnScreenNotificationTest::show() +{ + OnScreenNotification notification; + auto config = KSharedConfig::openConfig(QString(), KSharedConfig::SimpleConfig); + KConfigGroup group = config->group("OnScreenNotification"); + group.writeEntry(QStringLiteral("QmlPath"), QString("/does/not/exist.qml")); + group.sync(); + notification.setConfig(config); + notification.setEngine(new QQmlEngine(¬ification)); + notification.setMessage(QStringLiteral("Some text so that we see it in the test")); + + QSignalSpy visibleChangedSpy(¬ification, &OnScreenNotification::visibleChanged); + QCOMPARE(notification.isVisible(), false); + notification.setVisible(true); + QCOMPARE(notification.isVisible(), true); + QCOMPARE(visibleChangedSpy.count(), 1); + + // show again should not trigger + notification.setVisible(true); + QCOMPARE(visibleChangedSpy.count(), 1); + + // timer should not have hidden + QTest::qWait(500); + QCOMPARE(notification.isVisible(), true); + + // hide again + notification.setVisible(false); + QCOMPARE(notification.isVisible(), false); + QCOMPARE(visibleChangedSpy.count(), 2); + + // now show with timer + notification.setTimeout(250); + notification.setVisible(true); + QCOMPARE(notification.isVisible(), true); + QCOMPARE(visibleChangedSpy.count(), 3); + QVERIFY(visibleChangedSpy.wait()); + QCOMPARE(notification.isVisible(), false); + QCOMPARE(visibleChangedSpy.count(), 4); +} + +void OnScreenNotificationTest::timeout() +{ + OnScreenNotification notification; + QSignalSpy timeoutChangedSpy(¬ification, &OnScreenNotification::timeoutChanged); + QCOMPARE(notification.timeout(), 0); + notification.setTimeout(1000); + QCOMPARE(notification.timeout(), 1000); + QCOMPARE(timeoutChangedSpy.count(), 1); + notification.setTimeout(1000); + QCOMPARE(timeoutChangedSpy.count(), 1); + notification.setTimeout(0); + QCOMPARE(notification.timeout(), 0); + QCOMPARE(timeoutChangedSpy.count(), 2); +} + +void OnScreenNotificationTest::iconName() +{ + OnScreenNotification notification; + QSignalSpy iconNameChangedSpy(¬ification, &OnScreenNotification::iconNameChanged); + QCOMPARE(notification.iconName(), QString()); + notification.setIconName(QStringLiteral("foo")); + QCOMPARE(notification.iconName(), QStringLiteral("foo")); + QCOMPARE(iconNameChangedSpy.count(), 1); + notification.setIconName(QStringLiteral("foo")); + QCOMPARE(iconNameChangedSpy.count(), 1); + notification.setIconName(QStringLiteral("bar")); + QCOMPARE(notification.iconName(), QStringLiteral("bar")); + QCOMPARE(iconNameChangedSpy.count(), 2); +} + +void OnScreenNotificationTest::message() +{ + OnScreenNotification notification; + QSignalSpy messageChangedSpy(¬ification, &OnScreenNotification::messageChanged); + QCOMPARE(notification.message(), QString()); + notification.setMessage(QStringLiteral("foo")); + QCOMPARE(notification.message(), QStringLiteral("foo")); + QCOMPARE(messageChangedSpy.count(), 1); + notification.setMessage(QStringLiteral("foo")); + QCOMPARE(messageChangedSpy.count(), 1); + notification.setMessage(QStringLiteral("bar")); + QCOMPARE(notification.message(), QStringLiteral("bar")); + QCOMPARE(messageChangedSpy.count(), 2); +} diff --git a/autotests/onscreennotificationtest.h b/autotests/onscreennotificationtest.h new file mode 100644 index 0000000..6c1583c --- /dev/null +++ b/autotests/onscreennotificationtest.h @@ -0,0 +1,24 @@ +/* + SPDX-FileCopyrightText: 2016 Martin Graesslin + + SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +*/ + +#ifndef ONSCREENNOTIFICATIONTEST_H +#define ONSCREENNOTIFICATIONTEST_H + +#include + +class OnScreenNotificationTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + + void show(); + void timeout(); + void iconName(); + void message(); +}; + +#endif // ONSCREENNOTIFICATIONTEST_H diff --git a/autotests/opengl_context_attribute_builder_test.cpp b/autotests/opengl_context_attribute_builder_test.cpp new file mode 100644 index 0000000..d7eeb72 --- /dev/null +++ b/autotests/opengl_context_attribute_builder_test.cpp @@ -0,0 +1,465 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2017 Martin Flöser + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "utils/abstract_opengl_context_attribute_builder.h" +#include "utils/egl_context_attribute_builder.h" +#include +#include + +#include +#if HAVE_EPOXY_GLX +#include "../src/backends/x11/standalone/x11_standalone_glx_context_attribute_builder.h" +#include + +#ifndef GLX_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV +#define GLX_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV 0x20F7 +#endif +#endif + +using namespace KWin; + +class OpenGLContextAttributeBuilderTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testCtor(); + void testRobust(); + void testForwardCompatible(); + void testProfile(); + void testResetOnVideoMemoryPurge(); + void testVersionMajor(); + void testVersionMajorAndMinor(); + void testHighPriority(); + void testEgl_data(); + void testEgl(); + void testGles_data(); + void testGles(); + void testGlx_data(); + void testGlx(); +}; + +class MockOpenGLContextAttributeBuilder : public AbstractOpenGLContextAttributeBuilder +{ +public: + std::vector build() const override; +}; + +std::vector MockOpenGLContextAttributeBuilder::build() const +{ + return std::vector(); +} + +void OpenGLContextAttributeBuilderTest::testCtor() +{ + MockOpenGLContextAttributeBuilder builder; + QCOMPARE(builder.isVersionRequested(), false); + QCOMPARE(builder.majorVersion(), 0); + QCOMPARE(builder.minorVersion(), 0); + QCOMPARE(builder.isRobust(), false); + QCOMPARE(builder.isForwardCompatible(), false); + QCOMPARE(builder.isCoreProfile(), false); + QCOMPARE(builder.isCompatibilityProfile(), false); + QCOMPARE(builder.isResetOnVideoMemoryPurge(), false); + QCOMPARE(builder.isHighPriority(), false); +} + +void OpenGLContextAttributeBuilderTest::testRobust() +{ + MockOpenGLContextAttributeBuilder builder; + QCOMPARE(builder.isRobust(), false); + builder.setRobust(true); + QCOMPARE(builder.isRobust(), true); + builder.setRobust(false); + QCOMPARE(builder.isRobust(), false); +} + +void OpenGLContextAttributeBuilderTest::testForwardCompatible() +{ + MockOpenGLContextAttributeBuilder builder; + QCOMPARE(builder.isForwardCompatible(), false); + builder.setForwardCompatible(true); + QCOMPARE(builder.isForwardCompatible(), true); + builder.setForwardCompatible(false); + QCOMPARE(builder.isForwardCompatible(), false); +} + +void OpenGLContextAttributeBuilderTest::testProfile() +{ + MockOpenGLContextAttributeBuilder builder; + QCOMPARE(builder.isCoreProfile(), false); + QCOMPARE(builder.isCompatibilityProfile(), false); + builder.setCoreProfile(true); + QCOMPARE(builder.isCoreProfile(), true); + QCOMPARE(builder.isCompatibilityProfile(), false); + builder.setCompatibilityProfile(true); + QCOMPARE(builder.isCoreProfile(), false); + QCOMPARE(builder.isCompatibilityProfile(), true); + builder.setCoreProfile(true); + QCOMPARE(builder.isCoreProfile(), true); + QCOMPARE(builder.isCompatibilityProfile(), false); +} + +void OpenGLContextAttributeBuilderTest::testResetOnVideoMemoryPurge() +{ + MockOpenGLContextAttributeBuilder builder; + QCOMPARE(builder.isResetOnVideoMemoryPurge(), false); + builder.setResetOnVideoMemoryPurge(true); + QCOMPARE(builder.isResetOnVideoMemoryPurge(), true); + builder.setResetOnVideoMemoryPurge(false); + QCOMPARE(builder.isResetOnVideoMemoryPurge(), false); +} + +void OpenGLContextAttributeBuilderTest::testHighPriority() +{ + MockOpenGLContextAttributeBuilder builder; + QCOMPARE(builder.isHighPriority(), false); + builder.setHighPriority(true); + QCOMPARE(builder.isHighPriority(), true); + builder.setHighPriority(false); + QCOMPARE(builder.isHighPriority(), false); +} + +void OpenGLContextAttributeBuilderTest::testVersionMajor() +{ + MockOpenGLContextAttributeBuilder builder; + builder.setVersion(2); + QCOMPARE(builder.isVersionRequested(), true); + QCOMPARE(builder.majorVersion(), 2); + QCOMPARE(builder.minorVersion(), 0); + builder.setVersion(3); + QCOMPARE(builder.isVersionRequested(), true); + QCOMPARE(builder.majorVersion(), 3); + QCOMPARE(builder.minorVersion(), 0); +} + +void OpenGLContextAttributeBuilderTest::testVersionMajorAndMinor() +{ + MockOpenGLContextAttributeBuilder builder; + builder.setVersion(2, 1); + QCOMPARE(builder.isVersionRequested(), true); + QCOMPARE(builder.majorVersion(), 2); + QCOMPARE(builder.minorVersion(), 1); + builder.setVersion(3, 2); + QCOMPARE(builder.isVersionRequested(), true); + QCOMPARE(builder.majorVersion(), 3); + QCOMPARE(builder.minorVersion(), 2); +} + +void OpenGLContextAttributeBuilderTest::testEgl_data() +{ + QTest::addColumn("requestVersion"); + QTest::addColumn("major"); + QTest::addColumn("minor"); + QTest::addColumn("robust"); + QTest::addColumn("forwardCompatible"); + QTest::addColumn("coreProfile"); + QTest::addColumn("compatibilityProfile"); + QTest::addColumn("highPriority"); + QTest::addColumn>("expectedAttribs"); + + QTest::newRow("fallback") << false << 0 << 0 << false << false << false << false << false << std::vector{EGL_NONE}; + QTest::newRow("legacy/robust") + << false << 0 << 0 << true << false << false << false << false + << std::vector{ + EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR, EGL_LOSE_CONTEXT_ON_RESET_KHR, + EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR, + EGL_NONE}; + QTest::newRow("legacy/robust/high priority") + << false << 0 << 0 << true << false << false << false << true + << std::vector{ + EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR, EGL_LOSE_CONTEXT_ON_RESET_KHR, + EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR, + EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG, + EGL_NONE}; + QTest::newRow("core") + << true << 3 << 1 << false << false << false << false << false + << std::vector{ + EGL_CONTEXT_MAJOR_VERSION_KHR, 3, + EGL_CONTEXT_MINOR_VERSION_KHR, 1, + EGL_NONE}; + QTest::newRow("core/high priority") + << true << 3 << 1 << false << false << false << false << true + << std::vector{ + EGL_CONTEXT_MAJOR_VERSION_KHR, 3, + EGL_CONTEXT_MINOR_VERSION_KHR, 1, + EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG, + EGL_NONE}; + QTest::newRow("core/robust") + << true << 3 << 1 << true << false << false << false << false + << std::vector{ + EGL_CONTEXT_MAJOR_VERSION_KHR, 3, + EGL_CONTEXT_MINOR_VERSION_KHR, 1, + EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR, EGL_LOSE_CONTEXT_ON_RESET_KHR, + EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR, + EGL_NONE}; + QTest::newRow("core/robust/high priority") + << true << 3 << 1 << true << false << false << false << true + << std::vector{ + EGL_CONTEXT_MAJOR_VERSION_KHR, 3, + EGL_CONTEXT_MINOR_VERSION_KHR, 1, + EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR, EGL_LOSE_CONTEXT_ON_RESET_KHR, + EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR, + EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG, + EGL_NONE}; + QTest::newRow("core/robust/forward compatible") + << true << 3 << 1 << true << true << false << false << false + << std::vector{ + EGL_CONTEXT_MAJOR_VERSION_KHR, 3, + EGL_CONTEXT_MINOR_VERSION_KHR, 1, + EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR, EGL_LOSE_CONTEXT_ON_RESET_KHR, + EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR | EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR, + EGL_NONE}; + QTest::newRow("core/robust/forward compatible/high priority") + << true << 3 << 1 << true << true << false << false << true + << std::vector{ + EGL_CONTEXT_MAJOR_VERSION_KHR, 3, + EGL_CONTEXT_MINOR_VERSION_KHR, 1, + EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR, EGL_LOSE_CONTEXT_ON_RESET_KHR, + EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR | EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR, + EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG, + EGL_NONE}; + QTest::newRow("core/forward compatible") + << true << 3 << 1 << false << true << false << false << false + << std::vector{ + EGL_CONTEXT_MAJOR_VERSION_KHR, 3, + EGL_CONTEXT_MINOR_VERSION_KHR, 1, + EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR, + EGL_NONE}; + QTest::newRow("core/forward compatible/high priority") + << true << 3 << 1 << false << true << false << false << true + << std::vector{ + EGL_CONTEXT_MAJOR_VERSION_KHR, 3, + EGL_CONTEXT_MINOR_VERSION_KHR, 1, + EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR, + EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG, + EGL_NONE}; + QTest::newRow("core profile/forward compatible") + << true << 3 << 2 << false << true << true << false << false + << std::vector{ + EGL_CONTEXT_MAJOR_VERSION_KHR, 3, + EGL_CONTEXT_MINOR_VERSION_KHR, 2, + EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR, + EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR, + EGL_NONE}; + QTest::newRow("core profile/forward compatible/high priority") + << true << 3 << 2 << false << true << true << false << true + << std::vector{ + EGL_CONTEXT_MAJOR_VERSION_KHR, 3, + EGL_CONTEXT_MINOR_VERSION_KHR, 2, + EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR, + EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR, + EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG, + EGL_NONE}; + QTest::newRow("compatibility profile/forward compatible") + << true << 3 << 2 << false << true << false << true << false + << std::vector{ + EGL_CONTEXT_MAJOR_VERSION_KHR, 3, + EGL_CONTEXT_MINOR_VERSION_KHR, 2, + EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR, + EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR, + EGL_NONE}; + QTest::newRow("compatibility profile/forward compatible/high priority") + << true << 3 << 2 << false << true << false << true << true + << std::vector{ + EGL_CONTEXT_MAJOR_VERSION_KHR, 3, + EGL_CONTEXT_MINOR_VERSION_KHR, 2, + EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR, + EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR, + EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG, + EGL_NONE}; + QTest::newRow("core profile/robust/forward compatible") + << true << 3 << 2 << true << true << true << false << false + << std::vector{ + EGL_CONTEXT_MAJOR_VERSION_KHR, 3, + EGL_CONTEXT_MINOR_VERSION_KHR, 2, + EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR, EGL_LOSE_CONTEXT_ON_RESET_KHR, + EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR | EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR, + EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR, + EGL_NONE}; + QTest::newRow("core profile/robust/forward compatible/high priority") + << true << 3 << 2 << true << true << true << false << true + << std::vector{ + EGL_CONTEXT_MAJOR_VERSION_KHR, 3, + EGL_CONTEXT_MINOR_VERSION_KHR, 2, + EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR, EGL_LOSE_CONTEXT_ON_RESET_KHR, + EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR | EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR, + EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT_KHR, + EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG, + EGL_NONE}; + QTest::newRow("compatibility profile/robust/forward compatible") + << true << 3 << 2 << true << true << false << true << false + << std::vector{ + EGL_CONTEXT_MAJOR_VERSION_KHR, 3, + EGL_CONTEXT_MINOR_VERSION_KHR, 2, + EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR, EGL_LOSE_CONTEXT_ON_RESET_KHR, + EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR | EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR, + EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR, + EGL_NONE}; + QTest::newRow("compatibility profile/robust/forward compatible/high priority") + << true << 3 << 2 << true << true << false << true << true + << std::vector{ + EGL_CONTEXT_MAJOR_VERSION_KHR, 3, + EGL_CONTEXT_MINOR_VERSION_KHR, 2, + EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_KHR, EGL_LOSE_CONTEXT_ON_RESET_KHR, + EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_ROBUST_ACCESS_BIT_KHR | EGL_CONTEXT_OPENGL_FORWARD_COMPATIBLE_BIT_KHR, + EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR, + EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG, + EGL_NONE}; +} + +void OpenGLContextAttributeBuilderTest::testEgl() +{ + QFETCH(bool, requestVersion); + QFETCH(int, major); + QFETCH(int, minor); + QFETCH(bool, robust); + QFETCH(bool, forwardCompatible); + QFETCH(bool, coreProfile); + QFETCH(bool, compatibilityProfile); + QFETCH(bool, highPriority); + + EglContextAttributeBuilder builder; + if (requestVersion) { + builder.setVersion(major, minor); + } + builder.setRobust(robust); + builder.setForwardCompatible(forwardCompatible); + builder.setCoreProfile(coreProfile); + builder.setCompatibilityProfile(compatibilityProfile); + builder.setHighPriority(highPriority); + + auto attribs = builder.build(); + QTEST(attribs, "expectedAttribs"); +} + +void OpenGLContextAttributeBuilderTest::testGles_data() +{ + QTest::addColumn("robust"); + QTest::addColumn("highPriority"); + QTest::addColumn>("expectedAttribs"); + + QTest::newRow("robust") + << true << false + << std::vector{ + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT, EGL_TRUE, + EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT, EGL_LOSE_CONTEXT_ON_RESET_EXT, + EGL_NONE}; + QTest::newRow("robust/high priority") + << true << true + << std::vector{ + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_CONTEXT_OPENGL_ROBUST_ACCESS_EXT, EGL_TRUE, + EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT, EGL_LOSE_CONTEXT_ON_RESET_EXT, + EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG, + EGL_NONE}; + QTest::newRow("normal") + << false << false + << std::vector{ + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE}; + QTest::newRow("normal/high priority") + << false << true + << std::vector{ + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_CONTEXT_PRIORITY_LEVEL_IMG, EGL_CONTEXT_PRIORITY_HIGH_IMG, + EGL_NONE}; +} + +void OpenGLContextAttributeBuilderTest::testGles() +{ + QFETCH(bool, robust); + QFETCH(bool, highPriority); + + EglOpenGLESContextAttributeBuilder builder; + builder.setVersion(2); + builder.setRobust(robust); + builder.setHighPriority(highPriority); + + auto attribs = builder.build(); + QTEST(attribs, "expectedAttribs"); +} + +void OpenGLContextAttributeBuilderTest::testGlx_data() +{ +#if HAVE_EPOXY_GLX + QTest::addColumn("requestVersion"); + QTest::addColumn("major"); + QTest::addColumn("minor"); + QTest::addColumn("robust"); + QTest::addColumn("videoPurge"); + QTest::addColumn>("expectedAttribs"); + + QTest::newRow("fallback") + << true << 2 << 1 << false << false + << std::vector{ + GLX_CONTEXT_MAJOR_VERSION_ARB, 2, + GLX_CONTEXT_MINOR_VERSION_ARB, 1, + 0}; + QTest::newRow("legacy/robust") + << false << 0 << 0 << true << false + << std::vector{ + GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB, + GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, GLX_LOSE_CONTEXT_ON_RESET_ARB, + 0}; + QTest::newRow("legacy/robust/videoPurge") + << false << 0 << 0 << true << true + << std::vector{ + GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB, + GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, GLX_LOSE_CONTEXT_ON_RESET_ARB, + GLX_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV, GL_TRUE, + 0}; + QTest::newRow("core") + << true << 3 << 1 << false << false + << std::vector{ + GLX_CONTEXT_MAJOR_VERSION_ARB, 3, + GLX_CONTEXT_MINOR_VERSION_ARB, 1, + 0}; + QTest::newRow("core/robust") + << true << 3 << 1 << true << false + << std::vector{ + GLX_CONTEXT_MAJOR_VERSION_ARB, 3, + GLX_CONTEXT_MINOR_VERSION_ARB, 1, + GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB, + GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, GLX_LOSE_CONTEXT_ON_RESET_ARB, + 0}; + QTest::newRow("core/robust/videoPurge") + << true << 3 << 1 << true << true + << std::vector{ + GLX_CONTEXT_MAJOR_VERSION_ARB, 3, + GLX_CONTEXT_MINOR_VERSION_ARB, 1, + GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_ROBUST_ACCESS_BIT_ARB, + GLX_CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB, GLX_LOSE_CONTEXT_ON_RESET_ARB, + GLX_GENERATE_RESET_ON_VIDEO_MEMORY_PURGE_NV, GL_TRUE, + 0}; +#endif +} + +void OpenGLContextAttributeBuilderTest::testGlx() +{ +#if HAVE_EPOXY_GLX + QFETCH(bool, requestVersion); + QFETCH(int, major); + QFETCH(int, minor); + QFETCH(bool, robust); + QFETCH(bool, videoPurge); + + GlxContextAttributeBuilder builder; + if (requestVersion) { + builder.setVersion(major, minor); + } + builder.setRobust(robust); + builder.setResetOnVideoMemoryPurge(videoPurge); + + auto attribs = builder.build(); + QTEST(attribs, "expectedAttribs"); +#endif +} + +QTEST_GUILESS_MAIN(OpenGLContextAttributeBuilderTest) +#include "opengl_context_attribute_builder_test.moc" diff --git a/autotests/tabbox/CMakeLists.txt b/autotests/tabbox/CMakeLists.txt new file mode 100644 index 0000000..0d7f7b9 --- /dev/null +++ b/autotests/tabbox/CMakeLists.txt @@ -0,0 +1,104 @@ +include_directories(${KWin_SOURCE_DIR}/src) +add_definitions(-DKWIN_UNIT_TEST) +######################################################## +# Test TabBox::ClientModel +######################################################## +set(testTabBoxClientModel_SRCS + ../../src/tabbox/clientmodel.cpp + ../../src/tabbox/desktopmodel.cpp + ../../src/tabbox/tabbox_logging.cpp + ../../src/tabbox/tabboxconfig.cpp + ../../src/tabbox/tabboxhandler.cpp + mock_tabboxclient.cpp + mock_tabboxhandler.cpp + test_tabbox_clientmodel.cpp +) + +add_executable(testTabBoxClientModel ${testTabBoxClientModel_SRCS}) +set_target_properties(testTabBoxClientModel PROPERTIES COMPILE_DEFINITIONS "NO_NONE_WINDOW") +target_link_libraries(testTabBoxClientModel + Qt::Core + Qt::DBus + Qt::Quick + Qt::Test + Qt::Widgets + Qt::GuiPrivate + + KF5::ConfigCore + KF5::I18n + KF5::Package + KF5::WindowSystem + + XCB::XCB +) +if (QT_MAJOR_VERSION EQUAL "5") + target_link_libraries(testTabBoxClientModel Qt::X11Extras) +endif() +add_test(NAME kwin-testTabBoxClientModel COMMAND testTabBoxClientModel) +ecm_mark_as_test(testTabBoxClientModel) + +######################################################## +# Test TabBox::TabBoxHandler +######################################################## +set(testTabBoxHandler_SRCS + ../../src/tabbox/clientmodel.cpp + ../../src/tabbox/desktopmodel.cpp + ../../src/tabbox/tabbox_logging.cpp + ../../src/tabbox/tabboxconfig.cpp + ../../src/tabbox/tabboxhandler.cpp + mock_tabboxclient.cpp + mock_tabboxhandler.cpp + test_tabbox_handler.cpp +) + +add_executable(testTabBoxHandler ${testTabBoxHandler_SRCS}) +set_target_properties(testTabBoxHandler PROPERTIES COMPILE_DEFINITIONS "NO_NONE_WINDOW") +target_link_libraries(testTabBoxHandler + Qt::Core + Qt::DBus + Qt::Quick + Qt::Test + Qt::Widgets + Qt::GuiPrivate + + KF5::ConfigCore + KF5::I18n + KF5::Package + KF5::WindowSystem + + XCB::XCB +) +if (QT_MAJOR_VERSION EQUAL "5") + target_link_libraries(testTabBoxHandler Qt::X11Extras) +endif() +add_test(NAME kwin-testTabBoxHandler COMMAND testTabBoxHandler) +ecm_mark_as_test(testTabBoxHandler) + +######################################################## +# Test TabBox::TabBoxConfig +######################################################## +set(testTabBoxConfig_SRCS + ../../src/tabbox/tabbox_logging.cpp + ../../src/tabbox/tabboxconfig.cpp + test_tabbox_config.cpp +) + +add_executable(testTabBoxConfig ${testTabBoxConfig_SRCS}) +target_link_libraries(testTabBoxConfig Qt::Core Qt::Test) +add_test(NAME kwin-testTabBoxConfig COMMAND testTabBoxConfig) +ecm_mark_as_test(testTabBoxConfig) + + +######################################################## +# Test TabBox::DesktopChainManager +######################################################## +set(testDesktopChain_SRCS + ../../src/tabbox/desktopchain.cpp + ../../src/tabbox/tabbox_logging.cpp + test_desktopchain.cpp +) + +add_executable(testDesktopChain ${testDesktopChain_SRCS}) +target_link_libraries(testDesktopChain Qt::Core Qt::Test) +add_test(NAME kwin-testDesktopChain COMMAND testDesktopChain) +ecm_mark_as_test(testDesktopChain) diff --git a/autotests/tabbox/mock_tabboxclient.cpp b/autotests/tabbox/mock_tabboxclient.cpp new file mode 100644 index 0000000..d4c2277 --- /dev/null +++ b/autotests/tabbox/mock_tabboxclient.cpp @@ -0,0 +1,26 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2012 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "mock_tabboxclient.h" +#include "mock_tabboxhandler.h" + +namespace KWin +{ + +MockTabBoxClient::MockTabBoxClient(QString caption) + : TabBoxClient() + , m_caption(caption) +{ +} + +void MockTabBoxClient::close() +{ + static_cast(TabBox::tabBox)->closeWindow(this); +} + +} // namespace KWin diff --git a/autotests/tabbox/mock_tabboxclient.h b/autotests/tabbox/mock_tabboxclient.h new file mode 100644 index 0000000..9c82905 --- /dev/null +++ b/autotests/tabbox/mock_tabboxclient.h @@ -0,0 +1,74 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2012 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#ifndef KWIN_MOCK_TABBOX_CLIENT_H +#define KWIN_MOCK_TABBOX_CLIENT_H + +#include "tabbox/tabboxhandler.h" + +#include +#include + +namespace KWin +{ +class MockTabBoxClient : public TabBox::TabBoxClient +{ +public: + explicit MockTabBoxClient(QString caption); + bool isMinimized() const override + { + return false; + } + QString caption() const override + { + return m_caption; + } + void close() override; + int height() const override + { + return 100; + } + virtual QPixmap icon(const QSize &size = QSize(32, 32)) const + { + return QPixmap(size); + } + bool isCloseable() const override + { + return true; + } + bool isFirstInTabBox() const override + { + return false; + } + int width() const override + { + return 100; + } + int x() const override + { + return 0; + } + int y() const override + { + return 0; + } + QIcon icon() const override + { + return QIcon(); + } + + QUuid internalId() const override + { + return QUuid{}; + } + +private: + QString m_caption; +}; +} // namespace KWin +#endif diff --git a/autotests/tabbox/mock_tabboxhandler.cpp b/autotests/tabbox/mock_tabboxhandler.cpp new file mode 100644 index 0000000..d01c326 --- /dev/null +++ b/autotests/tabbox/mock_tabboxhandler.cpp @@ -0,0 +1,111 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2012 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "mock_tabboxhandler.h" +#include "mock_tabboxclient.h" + +namespace KWin +{ + +MockTabBoxHandler::MockTabBoxHandler(QObject *parent) + : TabBoxHandler(parent) +{ +} + +MockTabBoxHandler::~MockTabBoxHandler() +{ +} + +void MockTabBoxHandler::grabbedKeyEvent(QKeyEvent *event) const +{ + Q_UNUSED(event) +} + +QWeakPointer MockTabBoxHandler::activeClient() const +{ + return m_activeClient; +} + +void MockTabBoxHandler::setActiveClient(const QWeakPointer &client) +{ + m_activeClient = client; +} + +QWeakPointer MockTabBoxHandler::clientToAddToList(TabBox::TabBoxClient *client, int desktop) const +{ + Q_UNUSED(desktop) + QList>::const_iterator it = m_windows.constBegin(); + for (; it != m_windows.constEnd(); ++it) { + if ((*it).get() == client) { + return QWeakPointer(*it); + } + } + return QWeakPointer(); +} + +QWeakPointer MockTabBoxHandler::nextClientFocusChain(TabBox::TabBoxClient *client) const +{ + QList>::const_iterator it = m_windows.constBegin(); + for (; it != m_windows.constEnd(); ++it) { + if ((*it).get() == client) { + ++it; + if (it == m_windows.constEnd()) { + return QWeakPointer(m_windows.first()); + } else { + return QWeakPointer(*it); + } + } + } + if (!m_windows.isEmpty()) { + return QWeakPointer(m_windows.last()); + } + return QWeakPointer(); +} + +QWeakPointer MockTabBoxHandler::firstClientFocusChain() const +{ + if (m_windows.isEmpty()) { + return QWeakPointer(); + } + return m_windows.first(); +} + +bool MockTabBoxHandler::isInFocusChain(TabBox::TabBoxClient *client) const +{ + if (!client) { + return false; + } + QList>::const_iterator it = m_windows.constBegin(); + for (; it != m_windows.constEnd(); ++it) { + if ((*it).get() == client) { + return true; + } + } + return false; +} + +QWeakPointer MockTabBoxHandler::createMockWindow(const QString &caption) +{ + QSharedPointer client(new MockTabBoxClient(caption)); + m_windows.append(client); + m_activeClient = client; + return QWeakPointer(client); +} + +void MockTabBoxHandler::closeWindow(TabBox::TabBoxClient *client) +{ + QList>::iterator it = m_windows.begin(); + for (; it != m_windows.end(); ++it) { + if ((*it).get() == client) { + m_windows.erase(it); + return; + } + } +} + +} // namespace KWin diff --git a/autotests/tabbox/mock_tabboxhandler.h b/autotests/tabbox/mock_tabboxhandler.h new file mode 100644 index 0000000..02038a2 --- /dev/null +++ b/autotests/tabbox/mock_tabboxhandler.h @@ -0,0 +1,118 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2012 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#ifndef KWIN_MOCK_TABBOX_HANDLER_H +#define KWIN_MOCK_TABBOX_HANDLER_H + +#include "tabbox/tabboxhandler.h" +namespace KWin +{ +class MockTabBoxHandler : public TabBox::TabBoxHandler +{ + Q_OBJECT +public: + MockTabBoxHandler(QObject *parent = nullptr); + ~MockTabBoxHandler() override; + void activateAndClose() override + { + } + QWeakPointer activeClient() const override; + void setActiveClient(const QWeakPointer &client); + int activeScreen() const override + { + return 0; + } + QWeakPointer clientToAddToList(TabBox::TabBoxClient *client, int desktop) const override; + int currentDesktop() const override + { + return 1; + } + QWeakPointer desktopClient() const override + { + return QWeakPointer(); + } + QString desktopName(int desktop) const override + { + Q_UNUSED(desktop) + return "desktop 1"; + } + QString desktopName(TabBox::TabBoxClient *client) const override + { + Q_UNUSED(client) + return "desktop"; + } + void elevateClient(TabBox::TabBoxClient *c, QWindow *tabbox, bool elevate) const override + { + Q_UNUSED(c) + Q_UNUSED(tabbox) + Q_UNUSED(elevate) + } + void shadeClient(TabBox::TabBoxClient *c, bool b) const override + { + Q_UNUSED(c) + Q_UNUSED(b) + } + virtual void hideOutline() + { + } + QWeakPointer nextClientFocusChain(TabBox::TabBoxClient *client) const override; + QWeakPointer firstClientFocusChain() const override; + bool isInFocusChain(TabBox::TabBoxClient *client) const override; + int nextDesktopFocusChain(int desktop) const override + { + Q_UNUSED(desktop) + return 1; + } + int numberOfDesktops() const override + { + return 1; + } + bool isKWinCompositing() const override + { + return false; + } + void raiseClient(TabBox::TabBoxClient *c) const override + { + Q_UNUSED(c) + } + void restack(TabBox::TabBoxClient *c, TabBox::TabBoxClient *under) override + { + Q_UNUSED(c) + Q_UNUSED(under) + } + virtual void showOutline(const QRect &outline) + { + Q_UNUSED(outline) + } + TabBox::TabBoxClientList stackingOrder() const override + { + return TabBox::TabBoxClientList(); + } + void grabbedKeyEvent(QKeyEvent *event) const override; + + void highlightWindows(TabBox::TabBoxClient *window = nullptr, QWindow *controller = nullptr) override + { + Q_UNUSED(window) + Q_UNUSED(controller) + } + + bool noModifierGrab() const override + { + return false; + } + + // mock methods + QWeakPointer createMockWindow(const QString &caption); + void closeWindow(TabBox::TabBoxClient *client); + +private: + QList> m_windows; + QWeakPointer m_activeClient; +}; +} // namespace KWin +#endif diff --git a/autotests/tabbox/test_desktopchain.cpp b/autotests/tabbox/test_desktopchain.cpp new file mode 100644 index 0000000..c758d82 --- /dev/null +++ b/autotests/tabbox/test_desktopchain.cpp @@ -0,0 +1,252 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2013 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +// KWin +#include "tabbox/desktopchain.h" + +#include + +using namespace KWin::TabBox; + +class TestDesktopChain : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void chainInit_data(); + void chainInit(); + void chainAdd_data(); + void chainAdd(); + void resize_data(); + void resize(); + void resizeAdd(); + void useChain(); +}; + +void TestDesktopChain::chainInit_data() +{ + QTest::addColumn("size"); + QTest::addColumn("next"); + QTest::addColumn("result"); + + QTest::newRow("0/1") << (uint)0 << (uint)1 << (uint)1; + QTest::newRow("0/5") << (uint)0 << (uint)5 << (uint)1; + QTest::newRow("1/1") << (uint)1 << (uint)1 << (uint)1; + QTest::newRow("1/2") << (uint)1 << (uint)2 << (uint)1; + QTest::newRow("4/1") << (uint)4 << (uint)1 << (uint)2; + QTest::newRow("4/2") << (uint)4 << (uint)2 << (uint)3; + QTest::newRow("4/3") << (uint)4 << (uint)3 << (uint)4; + QTest::newRow("4/4") << (uint)4 << (uint)4 << (uint)1; + QTest::newRow("4/5") << (uint)4 << (uint)5 << (uint)1; + QTest::newRow("4/7") << (uint)4 << (uint)7 << (uint)1; +} + +void TestDesktopChain::chainInit() +{ + QFETCH(uint, size); + QFETCH(uint, next); + DesktopChain chain(size); + QTEST(chain.next(next), "result"); + + DesktopChainManager manager(this); + manager.resize(0, size); + QTEST(manager.next(next), "result"); +} + +void TestDesktopChain::chainAdd_data() +{ + QTest::addColumn("size"); + QTest::addColumn("add"); + QTest::addColumn("next"); + QTest::addColumn("result"); + + // invalid size, should not crash + QTest::newRow("0/1/1/1") << (uint)0 << (uint)1 << (uint)1 << (uint)1; + // moving first element to the front, shouldn't change the chain + QTest::newRow("4/1/1/2") << (uint)4 << (uint)1 << (uint)1 << (uint)2; + QTest::newRow("4/1/2/3") << (uint)4 << (uint)1 << (uint)2 << (uint)3; + QTest::newRow("4/1/3/4") << (uint)4 << (uint)1 << (uint)3 << (uint)4; + QTest::newRow("4/1/4/1") << (uint)4 << (uint)1 << (uint)4 << (uint)1; + // moving an element from middle to front, should reorder + QTest::newRow("4/3/1/1") << (uint)4 << (uint)3 << (uint)1 << (uint)2; + QTest::newRow("4/3/2/4") << (uint)4 << (uint)3 << (uint)2 << (uint)4; + QTest::newRow("4/3/3/1") << (uint)4 << (uint)3 << (uint)3 << (uint)1; + QTest::newRow("4/3/4/3") << (uint)4 << (uint)3 << (uint)4 << (uint)3; + // adding an element which does not exist - should leave the chain untouched + QTest::newRow("4/5/1/2") << (uint)4 << (uint)5 << (uint)1 << (uint)2; + QTest::newRow("4/5/2/3") << (uint)4 << (uint)5 << (uint)2 << (uint)3; + QTest::newRow("4/5/3/4") << (uint)4 << (uint)5 << (uint)3 << (uint)4; + QTest::newRow("4/5/4/1") << (uint)4 << (uint)5 << (uint)4 << (uint)1; +} + +void TestDesktopChain::chainAdd() +{ + QFETCH(uint, size); + QFETCH(uint, add); + QFETCH(uint, next); + DesktopChain chain(size); + chain.add(add); + QTEST(chain.next(next), "result"); + + DesktopChainManager manager(this); + manager.resize(0, size); + manager.addDesktop(0, add); + QTEST(manager.next(next), "result"); +} + +void TestDesktopChain::resize_data() +{ + QTest::addColumn("size"); + QTest::addColumn("add"); + QTest::addColumn("newSize"); + QTest::addColumn("next"); + QTest::addColumn("result"); + + // basic test - increment by one + QTest::newRow("1->2/1") << (uint)1 << (uint)1 << (uint)2 << (uint)1 << (uint)2; + QTest::newRow("1->2/2") << (uint)1 << (uint)1 << (uint)2 << (uint)2 << (uint)1; + // more complex test - increment by three, keep chain untouched + QTest::newRow("3->6/1") << (uint)3 << (uint)1 << (uint)6 << (uint)1 << (uint)2; + QTest::newRow("3->6/2") << (uint)3 << (uint)1 << (uint)6 << (uint)2 << (uint)3; + QTest::newRow("3->6/3") << (uint)3 << (uint)1 << (uint)6 << (uint)3 << (uint)4; + QTest::newRow("3->6/4") << (uint)3 << (uint)1 << (uint)6 << (uint)4 << (uint)5; + QTest::newRow("3->6/5") << (uint)3 << (uint)1 << (uint)6 << (uint)5 << (uint)6; + QTest::newRow("3->6/6") << (uint)3 << (uint)1 << (uint)6 << (uint)6 << (uint)1; + // increment by three, but change it before + QTest::newRow("3->6/3/1") << (uint)3 << (uint)3 << (uint)6 << (uint)1 << (uint)2; + QTest::newRow("3->6/3/2") << (uint)3 << (uint)3 << (uint)6 << (uint)2 << (uint)4; + QTest::newRow("3->6/3/3") << (uint)3 << (uint)3 << (uint)6 << (uint)3 << (uint)1; + QTest::newRow("3->6/3/4") << (uint)3 << (uint)3 << (uint)6 << (uint)4 << (uint)5; + QTest::newRow("3->6/3/5") << (uint)3 << (uint)3 << (uint)6 << (uint)5 << (uint)6; + QTest::newRow("3->6/3/6") << (uint)3 << (uint)3 << (uint)6 << (uint)6 << (uint)3; + + // basic test - decrement by one + QTest::newRow("2->1/1") << (uint)2 << (uint)1 << (uint)1 << (uint)1 << (uint)1; + QTest::newRow("2->1/2") << (uint)2 << (uint)2 << (uint)1 << (uint)1 << (uint)1; + // more complex test - decrement by three, keep chain untouched + QTest::newRow("6->3/1") << (uint)6 << (uint)1 << (uint)3 << (uint)1 << (uint)2; + QTest::newRow("6->3/2") << (uint)6 << (uint)1 << (uint)3 << (uint)2 << (uint)3; + QTest::newRow("6->3/3") << (uint)6 << (uint)1 << (uint)3 << (uint)3 << (uint)1; + // more complex test - decrement by three, move element to front + QTest::newRow("6->3/6/1") << (uint)6 << (uint)6 << (uint)3 << (uint)1 << (uint)2; + QTest::newRow("6->3/6/2") << (uint)6 << (uint)6 << (uint)3 << (uint)2 << (uint)3; + QTest::newRow("6->3/6/3") << (uint)6 << (uint)6 << (uint)3 << (uint)3 << (uint)1; +} + +void TestDesktopChain::resize() +{ + QFETCH(uint, size); + DesktopChain chain(size); + QFETCH(uint, add); + chain.add(add); + QFETCH(uint, newSize); + chain.resize(size, newSize); + QFETCH(uint, next); + QTEST(chain.next(next), "result"); + + DesktopChainManager manager(this); + manager.resize(0, size); + manager.addDesktop(0, add); + manager.resize(size, newSize); + QTEST(manager.next(next), "result"); +} + +void TestDesktopChain::resizeAdd() +{ + // test that verifies that add works after shrinking the chain + DesktopChain chain(6); + DesktopChainManager manager(this); + manager.resize(0, 6); + chain.add(4); + manager.addDesktop(0, 4); + chain.add(5); + manager.addDesktop(4, 5); + chain.add(6); + manager.addDesktop(5, 6); + QCOMPARE(chain.next(6), (uint)5); + QCOMPARE(manager.next(6), (uint)5); + QCOMPARE(chain.next(5), (uint)4); + QCOMPARE(manager.next(5), (uint)4); + QCOMPARE(chain.next(4), (uint)1); + QCOMPARE(manager.next(4), (uint)1); + chain.resize(6, 3); + manager.resize(6, 3); + QCOMPARE(chain.next(3), (uint)3); + QCOMPARE(manager.next(3), (uint)3); + QCOMPARE(chain.next(1), (uint)3); + QCOMPARE(manager.next(1), (uint)3); + QCOMPARE(chain.next(2), (uint)3); + QCOMPARE(manager.next(2), (uint)3); + // add + chain.add(1); + manager.addDesktop(3, 1); + QCOMPARE(chain.next(3), (uint)3); + QCOMPARE(manager.next(3), (uint)3); + QCOMPARE(chain.next(1), (uint)3); + QCOMPARE(manager.next(1), (uint)3); + chain.add(2); + manager.addDesktop(1, 2); + QCOMPARE(chain.next(1), (uint)3); + QCOMPARE(manager.next(1), (uint)3); + QCOMPARE(chain.next(2), (uint)1); + QCOMPARE(manager.next(2), (uint)1); + QCOMPARE(chain.next(3), (uint)2); + QCOMPARE(manager.next(3), (uint)2); +} + +void TestDesktopChain::useChain() +{ + DesktopChainManager manager(this); + manager.resize(0, 4); + manager.addDesktop(0, 3); + // creating the first chain, should keep it unchanged + manager.useChain(QStringLiteral("test")); + QCOMPARE(manager.next(3), (uint)1); + QCOMPARE(manager.next(1), (uint)2); + QCOMPARE(manager.next(2), (uint)4); + QCOMPARE(manager.next(4), (uint)3); + // but creating a second chain, should create an empty one + manager.useChain(QStringLiteral("second chain")); + QCOMPARE(manager.next(1), (uint)2); + QCOMPARE(manager.next(2), (uint)3); + QCOMPARE(manager.next(3), (uint)4); + QCOMPARE(manager.next(4), (uint)1); + // adding a desktop should only affect the currently used one + manager.addDesktop(3, 2); + QCOMPARE(manager.next(1), (uint)3); + QCOMPARE(manager.next(2), (uint)1); + QCOMPARE(manager.next(3), (uint)4); + QCOMPARE(manager.next(4), (uint)2); + // verify by switching back + manager.useChain(QStringLiteral("test")); + QCOMPARE(manager.next(3), (uint)1); + QCOMPARE(manager.next(1), (uint)2); + QCOMPARE(manager.next(2), (uint)4); + QCOMPARE(manager.next(4), (uint)3); + manager.addDesktop(3, 1); + // use second chain again and put 4th desktop to front + manager.useChain(QStringLiteral("second chain")); + manager.addDesktop(3, 4); + // just for the fun a third chain, and let's shrink it + manager.useChain(QStringLiteral("third chain")); + manager.resize(4, 3); + QCOMPARE(manager.next(1), (uint)2); + QCOMPARE(manager.next(2), (uint)3); + // it must have affected all chains + manager.useChain(QStringLiteral("test")); + QCOMPARE(manager.next(1), (uint)3); + QCOMPARE(manager.next(3), (uint)2); + QCOMPARE(manager.next(2), (uint)1); + manager.useChain(QStringLiteral("second chain")); + QCOMPARE(manager.next(3), (uint)2); + QCOMPARE(manager.next(1), (uint)3); + QCOMPARE(manager.next(2), (uint)1); +} + +QTEST_MAIN(TestDesktopChain) +#include "test_desktopchain.moc" diff --git a/autotests/tabbox/test_tabbox_clientmodel.cpp b/autotests/tabbox/test_tabbox_clientmodel.cpp new file mode 100644 index 0000000..32e3091 --- /dev/null +++ b/autotests/tabbox/test_tabbox_clientmodel.cpp @@ -0,0 +1,87 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2012 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "test_tabbox_clientmodel.h" +#include "../testutils.h" +#include "clientmodel.h" +#include "mock_tabboxhandler.h" + +#include +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#include +#else +#include +#endif +using namespace KWin; + +void TestTabBoxClientModel::initTestCase() +{ + qApp->setProperty("x11Connection", QVariant::fromValue(QX11Info::connection())); +} + +void TestTabBoxClientModel::testLongestCaptionWithNullClient() +{ + MockTabBoxHandler tabboxhandler; + TabBox::ClientModel *clientModel = new TabBox::ClientModel(&tabboxhandler); + clientModel->createClientList(); + QCOMPARE(clientModel->longestCaption(), QString()); + // add a window to the mock + tabboxhandler.createMockWindow(QString("test")); + clientModel->createClientList(); + QCOMPARE(clientModel->longestCaption(), QString("test")); + // delete the one client in the list + QModelIndex index = clientModel->index(0, 0); + QVERIFY(index.isValid()); + TabBox::TabBoxClient *client = static_cast(clientModel->data(index, TabBox::ClientModel::ClientRole).value()); + client->close(); + // internal model of ClientModel now contains a deleted pointer + // longestCaption should behave just as if the window were not in the list + QCOMPARE(clientModel->longestCaption(), QString()); +} + +void TestTabBoxClientModel::testCreateClientListNoActiveClient() +{ + MockTabBoxHandler tabboxhandler; + tabboxhandler.setConfig(TabBox::TabBoxConfig()); + TabBox::ClientModel *clientModel = new TabBox::ClientModel(&tabboxhandler); + clientModel->createClientList(); + QCOMPARE(clientModel->rowCount(), 0); + // create two windows, rowCount() should go to two + QWeakPointer client = tabboxhandler.createMockWindow(QString("test")); + tabboxhandler.createMockWindow(QString("test2")); + clientModel->createClientList(); + QCOMPARE(clientModel->rowCount(), 2); + // let's ensure there is no active client + tabboxhandler.setActiveClient(QWeakPointer()); + // now it should still have two members in the list + clientModel->createClientList(); + QCOMPARE(clientModel->rowCount(), 2); +} + +void TestTabBoxClientModel::testCreateClientListActiveClientNotInFocusChain() +{ + MockTabBoxHandler tabboxhandler; + tabboxhandler.setConfig(TabBox::TabBoxConfig()); + TabBox::ClientModel *clientModel = new TabBox::ClientModel(&tabboxhandler); + // create two windows, rowCount() should go to two + QWeakPointer client = tabboxhandler.createMockWindow(QString("test")); + client = tabboxhandler.createMockWindow(QString("test2")); + clientModel->createClientList(); + QCOMPARE(clientModel->rowCount(), 2); + + // simulate that the active client is not in the focus chain + // for that we use the closeWindow of the MockTabBoxHandler which + // removes the Client from the Focus Chain but leaves the active window as it is + QSharedPointer clientOwner = client.toStrongRef(); + tabboxhandler.closeWindow(clientOwner.get()); + clientModel->createClientList(); + QCOMPARE(clientModel->rowCount(), 1); +} + +Q_CONSTRUCTOR_FUNCTION(forceXcb) +QTEST_MAIN(TestTabBoxClientModel) diff --git a/autotests/tabbox/test_tabbox_clientmodel.h b/autotests/tabbox/test_tabbox_clientmodel.h new file mode 100644 index 0000000..924bb80 --- /dev/null +++ b/autotests/tabbox/test_tabbox_clientmodel.h @@ -0,0 +1,42 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2012 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#ifndef TEST_TABBOX_CLIENT_MODEL_H +#define TEST_TABBOX_CLIENT_MODEL_H +#include + +class TestTabBoxClientModel : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + /** + * Tests that calculating the longest caption does not + * crash in case the internal m_clientList contains a weak + * pointer to a deleted TabBoxClient. + * + * See bug #303840 + */ + void testLongestCaptionWithNullClient(); + /** + * Tests the creation of the Client list for the case that + * there is no active Client, but that Clients actually exist. + * + * See BUG: 305449 + */ + void testCreateClientListNoActiveClient(); + /** + * Tests the creation of the Client list for the case that + * the active Client is not in the Focus chain. + * + * See BUG: 306260 + */ + void testCreateClientListActiveClientNotInFocusChain(); +}; + +#endif diff --git a/autotests/tabbox/test_tabbox_config.cpp b/autotests/tabbox/test_tabbox_config.cpp new file mode 100644 index 0000000..a1887fe --- /dev/null +++ b/autotests/tabbox/test_tabbox_config.cpp @@ -0,0 +1,77 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2012 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "tabbox/tabboxconfig.h" +#include +using namespace KWin; +using namespace KWin::TabBox; + +class TestTabBoxConfig : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testDefaultCtor(); + void testAssignmentOperator(); +}; + +void TestTabBoxConfig::testDefaultCtor() +{ + TabBoxConfig config; + QCOMPARE(config.isShowTabBox(), TabBoxConfig::defaultShowTabBox()); + QCOMPARE(config.isHighlightWindows(), TabBoxConfig::defaultHighlightWindow()); + QCOMPARE(config.tabBoxMode(), TabBoxConfig::ClientTabBox); + QCOMPARE(config.clientDesktopMode(), TabBoxConfig::defaultDesktopMode()); + QCOMPARE(config.clientActivitiesMode(), TabBoxConfig::defaultActivitiesMode()); + QCOMPARE(config.clientApplicationsMode(), TabBoxConfig::defaultApplicationsMode()); + QCOMPARE(config.orderMinimizedMode(), TabBoxConfig::defaultOrderMinimizedMode()); + QCOMPARE(config.clientMinimizedMode(), TabBoxConfig::defaultMinimizedMode()); + QCOMPARE(config.showDesktopMode(), TabBoxConfig::defaultShowDesktopMode()); + QCOMPARE(config.clientMultiScreenMode(), TabBoxConfig::defaultMultiScreenMode()); + QCOMPARE(config.clientSwitchingMode(), TabBoxConfig::defaultSwitchingMode()); + QCOMPARE(config.desktopSwitchingMode(), TabBoxConfig::MostRecentlyUsedDesktopSwitching); + QCOMPARE(config.layoutName(), TabBoxConfig::defaultLayoutName()); +} + +void TestTabBoxConfig::testAssignmentOperator() +{ + TabBoxConfig config; + // changing all values of the config object + config.setShowTabBox(!TabBoxConfig::defaultShowTabBox()); + config.setHighlightWindows(!TabBoxConfig::defaultHighlightWindow()); + config.setTabBoxMode(TabBoxConfig::DesktopTabBox); + config.setClientDesktopMode(TabBoxConfig::AllDesktopsClients); + config.setClientActivitiesMode(TabBoxConfig::AllActivitiesClients); + config.setClientApplicationsMode(TabBoxConfig::OneWindowPerApplication); + config.setOrderMinimizedMode(TabBoxConfig::GroupByMinimized); + config.setClientMinimizedMode(TabBoxConfig::ExcludeMinimizedClients); + config.setShowDesktopMode(TabBoxConfig::ShowDesktopClient); + config.setClientMultiScreenMode(TabBoxConfig::ExcludeCurrentScreenClients); + config.setClientSwitchingMode(TabBoxConfig::StackingOrderSwitching); + config.setDesktopSwitchingMode(TabBoxConfig::StaticDesktopSwitching); + config.setLayoutName(QStringLiteral("grid")); + TabBoxConfig config2; + config2 = config; + // verify the config2 values + QCOMPARE(config2.isShowTabBox(), !TabBoxConfig::defaultShowTabBox()); + QCOMPARE(config2.isHighlightWindows(), !TabBoxConfig::defaultHighlightWindow()); + QCOMPARE(config2.tabBoxMode(), TabBoxConfig::DesktopTabBox); + QCOMPARE(config2.clientDesktopMode(), TabBoxConfig::AllDesktopsClients); + QCOMPARE(config2.clientActivitiesMode(), TabBoxConfig::AllActivitiesClients); + QCOMPARE(config2.clientApplicationsMode(), TabBoxConfig::OneWindowPerApplication); + QCOMPARE(config2.orderMinimizedMode(), TabBoxConfig::GroupByMinimized); + QCOMPARE(config2.clientMinimizedMode(), TabBoxConfig::ExcludeMinimizedClients); + QCOMPARE(config2.showDesktopMode(), TabBoxConfig::ShowDesktopClient); + QCOMPARE(config2.clientMultiScreenMode(), TabBoxConfig::ExcludeCurrentScreenClients); + QCOMPARE(config2.clientSwitchingMode(), TabBoxConfig::StackingOrderSwitching); + QCOMPARE(config2.desktopSwitchingMode(), TabBoxConfig::StaticDesktopSwitching); + QCOMPARE(config2.layoutName(), QStringLiteral("grid")); +} + +QTEST_MAIN(TestTabBoxConfig) + +#include "test_tabbox_config.moc" diff --git a/autotests/tabbox/test_tabbox_handler.cpp b/autotests/tabbox/test_tabbox_handler.cpp new file mode 100644 index 0000000..c696376 --- /dev/null +++ b/autotests/tabbox/test_tabbox_handler.cpp @@ -0,0 +1,56 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2012 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "../testutils.h" +#include "clientmodel.h" +#include "mock_tabboxhandler.h" +#include +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#include +#else +#include +#endif + +using namespace KWin; + +class TestTabBoxHandler : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + /** + * Test to verify that update outline does not crash + * if the ModelIndex for which the outline should be + * shown is not valid. That is accessing the Pointer + * to the Client returns an invalid QVariant. + * BUG: 304620 + */ + void testDontCrashUpdateOutlineNullClient(); +}; + +void TestTabBoxHandler::initTestCase() +{ + qApp->setProperty("x11Connection", QVariant::fromValue(QX11Info::connection())); +} + +void TestTabBoxHandler::testDontCrashUpdateOutlineNullClient() +{ + MockTabBoxHandler tabboxhandler; + TabBox::TabBoxConfig config; + config.setTabBoxMode(TabBox::TabBoxConfig::ClientTabBox); + config.setShowTabBox(false); + config.setHighlightWindows(false); + tabboxhandler.setConfig(config); + // now show the tabbox which will attempt to show the outline + tabboxhandler.show(); +} + +Q_CONSTRUCTOR_FUNCTION(forceXcb) +QTEST_MAIN(TestTabBoxHandler) + +#include "test_tabbox_handler.moc" diff --git a/autotests/test_client_machine.cpp b/autotests/test_client_machine.cpp new file mode 100644 index 0000000..8288ad4 --- /dev/null +++ b/autotests/test_client_machine.cpp @@ -0,0 +1,150 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2013 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "testutils.h" +// KWin +#include "client_machine.h" +#include "utils/xcbutils.h" +// Qt +#include +#include +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#include +#else +#include +#endif +// xcb +#include +// system +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(KWIN_CORE, "kwin_core") + +using namespace KWin; + +class TestClientMachine : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void hostName_data(); + void hostName(); + void emptyHostName(); + +private: + void setClientMachineProperty(xcb_window_t window, const QByteArray &hostname); + QByteArray m_hostName; + QByteArray m_fqdn; +}; + +void TestClientMachine::setClientMachineProperty(xcb_window_t window, const QByteArray &hostname) +{ + xcb_change_property(connection(), XCB_PROP_MODE_REPLACE, window, + XCB_ATOM_WM_CLIENT_MACHINE, XCB_ATOM_STRING, 8, + hostname.length(), hostname.constData()); +} + +void TestClientMachine::initTestCase() +{ +#ifdef HOST_NAME_MAX + char hostnamebuf[HOST_NAME_MAX]; +#else + char hostnamebuf[256]; +#endif + if (gethostname(hostnamebuf, sizeof hostnamebuf) >= 0) { + hostnamebuf[sizeof(hostnamebuf) - 1] = 0; + m_hostName = hostnamebuf; + } + addrinfo *res; + addrinfo addressHints; + memset(&addressHints, 0, sizeof(addressHints)); + addressHints.ai_family = PF_UNSPEC; + addressHints.ai_socktype = SOCK_STREAM; + addressHints.ai_flags |= AI_CANONNAME; + if (getaddrinfo(m_hostName.constData(), nullptr, &addressHints, &res) == 0) { + if (res->ai_canonname) { + m_fqdn = QByteArray(res->ai_canonname); + } + } + freeaddrinfo(res); + + qApp->setProperty("x11RootWindow", QVariant::fromValue(QX11Info::appRootWindow())); + qApp->setProperty("x11Connection", QVariant::fromValue(QX11Info::connection())); +} + +void TestClientMachine::cleanupTestCase() +{ +} + +void TestClientMachine::hostName_data() +{ + QTest::addColumn("hostName"); + QTest::addColumn("expectedHost"); + QTest::addColumn("local"); + + QTest::newRow("empty") << QByteArray() << QByteArray("localhost") << true; + QTest::newRow("localhost") << QByteArray("localhost") << QByteArray("localhost") << true; + QTest::newRow("hostname") << m_hostName << m_hostName << true; + QTest::newRow("HOSTNAME") << m_hostName.toUpper() << m_hostName.toUpper() << true; + QByteArray cutted(m_hostName); + cutted.remove(0, 1); + QTest::newRow("ostname") << cutted << cutted << false; + QByteArray domain("random.name.not.exist.tld"); + QTest::newRow("domain") << domain << domain << false; + QTest::newRow("fqdn") << m_fqdn << m_fqdn << true; + QTest::newRow("FQDN") << m_fqdn.toUpper() << m_fqdn.toUpper() << true; + cutted = m_fqdn; + cutted.remove(0, 1); + QTest::newRow("qdn") << cutted << cutted << false; +} + +void TestClientMachine::hostName() +{ + const QRect geometry(0, 0, 10, 10); + const uint32_t values[] = {true}; + Xcb::Window window(geometry, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_CW_OVERRIDE_REDIRECT, values); + QFETCH(QByteArray, hostName); + QFETCH(bool, local); + setClientMachineProperty(window, hostName); + + ClientMachine clientMachine; + QSignalSpy spy(&clientMachine, &ClientMachine::localhostChanged); + clientMachine.resolve(window, XCB_WINDOW_NONE); + QTEST(clientMachine.hostName(), "expectedHost"); + + int i = 0; + while (clientMachine.isResolving() && i++ < 50) { + // name is being resolved in an external thread, so let's wait a little bit + QTest::qWait(250); + } + + QCOMPARE(clientMachine.isLocal(), local); + QCOMPARE(spy.isEmpty(), !local); +} + +void TestClientMachine::emptyHostName() +{ + const QRect geometry(0, 0, 10, 10); + const uint32_t values[] = {true}; + Xcb::Window window(geometry, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_CW_OVERRIDE_REDIRECT, values); + ClientMachine clientMachine; + QSignalSpy spy(&clientMachine, &ClientMachine::localhostChanged); + clientMachine.resolve(window, XCB_WINDOW_NONE); + QCOMPARE(clientMachine.hostName(), ClientMachine::localhost()); + QVERIFY(clientMachine.isLocal()); + // should be local + QCOMPARE(spy.isEmpty(), false); +} + +Q_CONSTRUCTOR_FUNCTION(forceXcb) +QTEST_MAIN(TestClientMachine) +#include "test_client_machine.moc" diff --git a/autotests/test_ftrace.cpp b/autotests/test_ftrace.cpp new file mode 100644 index 0000000..d460e4b --- /dev/null +++ b/autotests/test_ftrace.cpp @@ -0,0 +1,72 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2021 David Edmundson + + SPDX-License-Identifier: LGPL-2.0-or-later +*/ + +#include +#include +#include + +#include "ftrace.h" + +class TestFTrace : public QObject +{ + Q_OBJECT +public: + TestFTrace(); +private Q_SLOTS: + void benchmarkTraceOff(); + void benchmarkTraceDurationOff(); + void enable(); + +private: + QTemporaryFile m_tempFile; +}; + +TestFTrace::TestFTrace() +{ + m_tempFile.open(); + qputenv("KWIN_PERF_FTRACE_FILE", m_tempFile.fileName().toLatin1()); + + KWin::FTraceLogger::create(); +} + +void TestFTrace::benchmarkTraceOff() +{ + // this macro should no-op, so take no time at all + QBENCHMARK { + fTrace("BENCH", 123, "foo"); + } +} + +void TestFTrace::benchmarkTraceDurationOff() +{ + QBENCHMARK { + fTraceDuration("BENCH", 123, "foo"); + } +} + +void TestFTrace::enable() +{ + KWin::FTraceLogger::self()->setEnabled(true); + QVERIFY(KWin::FTraceLogger::self()->isEnabled()); + + { + fTrace("TEST", 123, "foo"); + fTraceDuration("TEST_DURATION", "boo"); + fTrace("TEST", 123, "foo"); + } + + QCOMPARE(m_tempFile.readLine(), "TEST123foo\n"); + QCOMPARE(m_tempFile.readLine(), "TEST_DURATIONboo begin_ctx=1\n"); + QCOMPARE(m_tempFile.readLine(), "TEST123foo\n"); + QCOMPARE(m_tempFile.readLine(), "TEST_DURATIONboo end_ctx=1\n"); +} + +QTEST_MAIN(TestFTrace) + +#include "test_ftrace.moc" diff --git a/autotests/test_gestures.cpp b/autotests/test_gestures.cpp new file mode 100644 index 0000000..0554174 --- /dev/null +++ b/autotests/test_gestures.cpp @@ -0,0 +1,689 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2017 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "gestures.h" + +#include +#include +#include +#include + +using namespace KWin; + +class GestureTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testSwipeMinFinger_data(); + void testSwipeMinFinger(); + void testPinchMinFinger_data(); + void testPinchMinFinger(); + + void testSwipeMaxFinger_data(); + void testSwipeMaxFinger(); + void testPinchMaxFinger_data(); + void testPinchMaxFinger(); + + void testSwipeDirection_data(); + void testSwipeDirection(); + void testPinchDirection_data(); + void testPinchDirection(); + + // swipe only + void testMinimumX_data(); + void testMinimumX(); + void testMinimumY_data(); + void testMinimumY(); + void testMaximumX_data(); + void testMaximumX(); + void testMaximumY_data(); + void testMaximumY(); + void testStartGeometry(); + + // swipe and pinch + void testSetMinimumDelta(); + void testMinimumDeltaReached_data(); + void testMinimumDeltaReached(); + void testMinimumScaleDelta(); + void testUnregisterSwipeCancels(); + void testUnregisterPinchCancels(); + void testDeleteSwipeCancels(); + void testSwipeCancel_data(); + void testSwipeCancel(); + void testSwipeUpdateTrigger_data(); + void testSwipeUpdateTrigger(); + + // both + void testSwipeMinFingerStart_data(); + void testSwipeMinFingerStart(); + void testSwipeMaxFingerStart_data(); + void testSwipeMaxFingerStart(); + void testNotEmitCallbacksBeforeDirectionDecided(); + + // swipe only + void testSwipeGeometryStart_data(); + void testSwipeGeometryStart(); +}; + +void GestureTest::testSwipeMinFinger_data() +{ + QTest::addColumn("count"); + QTest::addColumn("expectedCount"); + + QTest::newRow("0") << 0u << 0u; + QTest::newRow("1") << 1u << 1u; + QTest::newRow("10") << 10u << 10u; +} + +void GestureTest::testSwipeMinFinger() +{ + SwipeGesture swipeGesture; + QCOMPARE(swipeGesture.minimumFingerCountIsRelevant(), false); + QCOMPARE(swipeGesture.minimumFingerCount(), 0u); + QFETCH(uint, count); + swipeGesture.setMinimumFingerCount(count); + QCOMPARE(swipeGesture.minimumFingerCountIsRelevant(), true); + QTEST(swipeGesture.minimumFingerCount(), "expectedCount"); + swipeGesture.setMinimumFingerCount(0); + QCOMPARE(swipeGesture.minimumFingerCountIsRelevant(), true); + QCOMPARE(swipeGesture.minimumFingerCount(), 0u); +} + +void GestureTest::testPinchMinFinger_data() +{ + QTest::addColumn("count"); + QTest::addColumn("expectedCount"); + + QTest::newRow("0") << 0u << 0u; + QTest::newRow("1") << 1u << 1u; + QTest::newRow("10") << 10u << 10u; +} + +void GestureTest::testPinchMinFinger() +{ + PinchGesture pinchGesture; + QCOMPARE(pinchGesture.minimumFingerCountIsRelevant(), false); + QCOMPARE(pinchGesture.minimumFingerCount(), 0u); + QFETCH(uint, count); + pinchGesture.setMinimumFingerCount(count); + QCOMPARE(pinchGesture.minimumFingerCountIsRelevant(), true); + QTEST(pinchGesture.minimumFingerCount(), "expectedCount"); + pinchGesture.setMinimumFingerCount(0); + QCOMPARE(pinchGesture.minimumFingerCountIsRelevant(), true); + QCOMPARE(pinchGesture.minimumFingerCount(), 0u); +} + +void GestureTest::testSwipeMaxFinger_data() +{ + QTest::addColumn("count"); + QTest::addColumn("expectedCount"); + + QTest::newRow("0") << 0u << 0u; + QTest::newRow("1") << 1u << 1u; + QTest::newRow("10") << 10u << 10u; +} + +void GestureTest::testSwipeMaxFinger() +{ + SwipeGesture gesture; + QCOMPARE(gesture.maximumFingerCountIsRelevant(), false); + QCOMPARE(gesture.maximumFingerCount(), 0u); + QFETCH(uint, count); + gesture.setMaximumFingerCount(count); + QCOMPARE(gesture.maximumFingerCountIsRelevant(), true); + QTEST(gesture.maximumFingerCount(), "expectedCount"); + gesture.setMaximumFingerCount(0); + QCOMPARE(gesture.maximumFingerCountIsRelevant(), true); + QCOMPARE(gesture.maximumFingerCount(), 0u); +} + +void GestureTest::testPinchMaxFinger_data() +{ + QTest::addColumn("count"); + QTest::addColumn("expectedCount"); + + QTest::newRow("0") << 0u << 0u; + QTest::newRow("1") << 1u << 1u; + QTest::newRow("10") << 10u << 10u; +} + +void GestureTest::testPinchMaxFinger() +{ + PinchGesture gesture; + QCOMPARE(gesture.maximumFingerCountIsRelevant(), false); + QCOMPARE(gesture.maximumFingerCount(), 0u); + QFETCH(uint, count); + gesture.setMaximumFingerCount(count); + QCOMPARE(gesture.maximumFingerCountIsRelevant(), true); + QTEST(gesture.maximumFingerCount(), "expectedCount"); + gesture.setMaximumFingerCount(0); + QCOMPARE(gesture.maximumFingerCountIsRelevant(), true); + QCOMPARE(gesture.maximumFingerCount(), 0u); +} + +void GestureTest::testSwipeDirection_data() +{ + QTest::addColumn("swipe_direction"); + + QTest::newRow("Up") << KWin::SwipeGesture::Direction::Up; + QTest::newRow("Left") << KWin::SwipeGesture::Direction::Left; + QTest::newRow("Right") << KWin::SwipeGesture::Direction::Right; + QTest::newRow("Down") << KWin::SwipeGesture::Direction::Down; +} + +void GestureTest::testSwipeDirection() +{ + SwipeGesture gesture; + QCOMPARE(gesture.direction(), SwipeGesture::Direction::Down); + QFETCH(KWin::SwipeGesture::Direction, swipe_direction); + gesture.setDirection(swipe_direction); + QCOMPARE(gesture.direction(), swipe_direction); + // back to down + gesture.setDirection(SwipeGesture::Direction::Down); + QCOMPARE(gesture.direction(), SwipeGesture::Direction::Down); +} + +void GestureTest::testPinchDirection_data() +{ + QTest::addColumn("pinch_direction"); + + QTest::newRow("Contracting") << KWin::PinchGesture::Direction::Contracting; + QTest::newRow("Expanding") << KWin::PinchGesture::Direction::Expanding; +} + +void GestureTest::testPinchDirection() +{ + PinchGesture gesture; + QCOMPARE(gesture.direction(), PinchGesture::Direction::Expanding); + QFETCH(KWin::PinchGesture::Direction, pinch_direction); + gesture.setDirection(pinch_direction); + QCOMPARE(gesture.direction(), pinch_direction); + // back to down + gesture.setDirection(PinchGesture::Direction::Expanding); + QCOMPARE(gesture.direction(), PinchGesture::Direction::Expanding); +} + +void GestureTest::testMinimumX_data() +{ + QTest::addColumn("min"); + + QTest::newRow("0") << 0; + QTest::newRow("-1") << -1; + QTest::newRow("1") << 1; +} + +void GestureTest::testMinimumX() +{ + SwipeGesture gesture; + QCOMPARE(gesture.minimumX(), 0); + QCOMPARE(gesture.minimumXIsRelevant(), false); + QFETCH(int, min); + gesture.setMinimumX(min); + QCOMPARE(gesture.minimumX(), min); + QCOMPARE(gesture.minimumXIsRelevant(), true); +} + +void GestureTest::testMinimumY_data() +{ + QTest::addColumn("min"); + + QTest::newRow("0") << 0; + QTest::newRow("-1") << -1; + QTest::newRow("1") << 1; +} + +void GestureTest::testMinimumY() +{ + SwipeGesture gesture; + QCOMPARE(gesture.minimumY(), 0); + QCOMPARE(gesture.minimumYIsRelevant(), false); + QFETCH(int, min); + gesture.setMinimumY(min); + QCOMPARE(gesture.minimumY(), min); + QCOMPARE(gesture.minimumYIsRelevant(), true); +} + +void GestureTest::testMaximumX_data() +{ + QTest::addColumn("max"); + + QTest::newRow("0") << 0; + QTest::newRow("-1") << -1; + QTest::newRow("1") << 1; +} + +void GestureTest::testMaximumX() +{ + SwipeGesture gesture; + QCOMPARE(gesture.maximumX(), 0); + QCOMPARE(gesture.maximumXIsRelevant(), false); + QFETCH(int, max); + gesture.setMaximumX(max); + QCOMPARE(gesture.maximumX(), max); + QCOMPARE(gesture.maximumXIsRelevant(), true); +} + +void GestureTest::testMaximumY_data() +{ + QTest::addColumn("max"); + + QTest::newRow("0") << 0; + QTest::newRow("-1") << -1; + QTest::newRow("1") << 1; +} + +void GestureTest::testMaximumY() +{ + SwipeGesture gesture; + QCOMPARE(gesture.maximumY(), 0); + QCOMPARE(gesture.maximumYIsRelevant(), false); + QFETCH(int, max); + gesture.setMaximumY(max); + QCOMPARE(gesture.maximumY(), max); + QCOMPARE(gesture.maximumYIsRelevant(), true); +} + +void GestureTest::testStartGeometry() +{ + SwipeGesture gesture; + gesture.setStartGeometry(QRect(1, 2, 20, 30)); + QCOMPARE(gesture.minimumXIsRelevant(), true); + QCOMPARE(gesture.minimumYIsRelevant(), true); + QCOMPARE(gesture.maximumXIsRelevant(), true); + QCOMPARE(gesture.maximumYIsRelevant(), true); + QCOMPARE(gesture.minimumX(), 1); + QCOMPARE(gesture.minimumY(), 2); + QCOMPARE(gesture.maximumX(), 21); + QCOMPARE(gesture.maximumY(), 32); +} + +void GestureTest::testSetMinimumDelta() +{ + SwipeGesture swipeGesture; + QCOMPARE(swipeGesture.isMinimumDeltaRelevant(), false); + QCOMPARE(swipeGesture.minimumDelta(), QPointF()); + QCOMPARE(swipeGesture.minimumDeltaReached(QPointF()), true); + swipeGesture.setMinimumDelta(QPointF(2, 3)); + QCOMPARE(swipeGesture.isMinimumDeltaRelevant(), true); + QCOMPARE(swipeGesture.minimumDelta(), QPointF(2, 3)); + QCOMPARE(swipeGesture.minimumDeltaReached(QPointF()), false); + QCOMPARE(swipeGesture.minimumDeltaReached(QPointF(2, 3)), true); + + PinchGesture pinchGesture; + QCOMPARE(pinchGesture.isMinimumScaleDeltaRelevant(), false); + QCOMPARE(pinchGesture.minimumScaleDelta(), DEFAULT_UNIT_SCALE_DELTA); + QCOMPARE(pinchGesture.minimumScaleDeltaReached(1.25), true); + pinchGesture.setMinimumScaleDelta(.5); + QCOMPARE(pinchGesture.isMinimumScaleDeltaRelevant(), true); + QCOMPARE(pinchGesture.minimumScaleDelta(), .5); + QCOMPARE(pinchGesture.minimumScaleDeltaReached(1.24), false); + QCOMPARE(pinchGesture.minimumScaleDeltaReached(1.5), true); +} + +void GestureTest::testMinimumDeltaReached_data() +{ + QTest::addColumn("direction"); + QTest::addColumn("minimumDelta"); + QTest::addColumn("delta"); + QTest::addColumn("reached"); + QTest::addColumn("progress"); + + QTest::newRow("Up (more)") << KWin::SwipeGesture::Direction::Up << QPointF(0, -30) << QPointF(0, -40) << true << 1.0; + QTest::newRow("Up (exact)") << KWin::SwipeGesture::Direction::Up << QPointF(0, -30) << QPointF(0, -30) << true << 1.0; + QTest::newRow("Up (less)") << KWin::SwipeGesture::Direction::Up << QPointF(0, -30) << QPointF(0, -29) << false << 29.0 / 30.0; + QTest::newRow("Left (more)") << KWin::SwipeGesture::Direction::Left << QPointF(-30, -30) << QPointF(-40, 20) << true << 1.0; + QTest::newRow("Left (exact)") << KWin::SwipeGesture::Direction::Left << QPointF(-30, -40) << QPointF(-30, 0) << true << 1.0; + QTest::newRow("Left (less)") << KWin::SwipeGesture::Direction::Left << QPointF(-30, -30) << QPointF(-29, 0) << false << 29.0 / 30.0; + QTest::newRow("Right (more)") << KWin::SwipeGesture::Direction::Right << QPointF(30, -30) << QPointF(40, 20) << true << 1.0; + QTest::newRow("Right (exact)") << KWin::SwipeGesture::Direction::Right << QPointF(30, -40) << QPointF(30, 0) << true << 1.0; + QTest::newRow("Right (less)") << KWin::SwipeGesture::Direction::Right << QPointF(30, -30) << QPointF(29, 0) << false << 29.0 / 30.0; + QTest::newRow("Down (more)") << KWin::SwipeGesture::Direction::Down << QPointF(0, 30) << QPointF(0, 40) << true << 1.0; + QTest::newRow("Down (exact)") << KWin::SwipeGesture::Direction::Down << QPointF(0, 30) << QPointF(0, 30) << true << 1.0; + QTest::newRow("Down (less)") << KWin::SwipeGesture::Direction::Down << QPointF(0, 30) << QPointF(0, 29) << false << 29.0 / 30.0; +} + +void GestureTest::testMinimumDeltaReached() +{ + GestureRecognizer recognizer; + + // swipe gesture + SwipeGesture gesture; + QFETCH(SwipeGesture::Direction, direction); + gesture.setDirection(direction); + QFETCH(QPointF, minimumDelta); + gesture.setMinimumDelta(minimumDelta); + QFETCH(QPointF, delta); + QFETCH(bool, reached); + QCOMPARE(gesture.minimumDeltaReached(delta), reached); + + recognizer.registerSwipeGesture(&gesture); + + QSignalSpy startedSpy(&gesture, &SwipeGesture::started); + QSignalSpy triggeredSpy(&gesture, &SwipeGesture::triggered); + QSignalSpy cancelledSpy(&gesture, &SwipeGesture::cancelled); + QSignalSpy progressSpy(&gesture, &SwipeGesture::progress); + + recognizer.startSwipeGesture(1); + QCOMPARE(startedSpy.count(), 1); + QCOMPARE(triggeredSpy.count(), 0); + QCOMPARE(cancelledSpy.count(), 0); + QCOMPARE(progressSpy.count(), 0); + + recognizer.updateSwipeGesture(delta); + QCOMPARE(startedSpy.count(), 1); + QCOMPARE(triggeredSpy.count(), 0); + QCOMPARE(cancelledSpy.count(), 0); + QCOMPARE(progressSpy.count(), 1); + QTEST(progressSpy.first().first().value(), "progress"); + + recognizer.endSwipeGesture(); + QCOMPARE(startedSpy.count(), 1); + QCOMPARE(progressSpy.count(), 1); + QCOMPARE(triggeredSpy.isEmpty(), !reached); + QCOMPARE(cancelledSpy.isEmpty(), reached); +} + +void GestureTest::testMinimumScaleDelta() +{ + // pinch gesture + PinchGesture gesture; + gesture.setDirection(PinchGesture::Direction::Contracting); + gesture.setMinimumScaleDelta(.5); + gesture.setMinimumFingerCount(3); + gesture.setMaximumFingerCount(4); + + QCOMPARE(gesture.minimumScaleDeltaReached(1.25), false); + QCOMPARE(gesture.minimumScaleDeltaReached(1.5), true); + + GestureRecognizer recognizer; + recognizer.registerPinchGesture(&gesture); + + QSignalSpy startedSpy(&gesture, &PinchGesture::started); + QSignalSpy triggeredSpy(&gesture, &PinchGesture::triggered); + QSignalSpy cancelledSpy(&gesture, &PinchGesture::cancelled); + QSignalSpy progressSpy(&gesture, &PinchGesture::progress); + + recognizer.startPinchGesture(4); + QCOMPARE(startedSpy.count(), 1); + QCOMPARE(triggeredSpy.count(), 0); + QCOMPARE(cancelledSpy.count(), 0); + QCOMPARE(progressSpy.count(), 0); +} + +void GestureTest::testUnregisterSwipeCancels() +{ + GestureRecognizer recognizer; + std::unique_ptr gesture(new SwipeGesture); + QSignalSpy startedSpy(gesture.get(), &SwipeGesture::started); + QSignalSpy cancelledSpy(gesture.get(), &SwipeGesture::cancelled); + + recognizer.registerSwipeGesture(gesture.get()); + recognizer.startSwipeGesture(1); + QCOMPARE(startedSpy.count(), 1); + QCOMPARE(cancelledSpy.count(), 0); + recognizer.unregisterSwipeGesture(gesture.get()); + QCOMPARE(cancelledSpy.count(), 1); + + // delete the gesture should not trigger cancel + gesture.reset(); + QCOMPARE(cancelledSpy.count(), 1); +} + +void GestureTest::testUnregisterPinchCancels() +{ + GestureRecognizer recognizer; + std::unique_ptr gesture(new PinchGesture); + QSignalSpy startedSpy(gesture.get(), &PinchGesture::started); + QSignalSpy cancelledSpy(gesture.get(), &PinchGesture::cancelled); + + recognizer.registerPinchGesture(gesture.get()); + recognizer.startPinchGesture(1); + QCOMPARE(startedSpy.count(), 1); + QCOMPARE(cancelledSpy.count(), 0); + recognizer.unregisterPinchGesture(gesture.get()); + QCOMPARE(cancelledSpy.count(), 1); + + // delete the gesture should not trigger cancel + gesture.reset(); + QCOMPARE(cancelledSpy.count(), 1); +} + +void GestureTest::testDeleteSwipeCancels() +{ + GestureRecognizer recognizer; + std::unique_ptr gesture(new SwipeGesture); + QSignalSpy startedSpy(gesture.get(), &SwipeGesture::started); + QSignalSpy cancelledSpy(gesture.get(), &SwipeGesture::cancelled); + + recognizer.registerSwipeGesture(gesture.get()); + recognizer.startSwipeGesture(1); + QCOMPARE(startedSpy.count(), 1); + QCOMPARE(cancelledSpy.count(), 0); + gesture.reset(); + QCOMPARE(cancelledSpy.count(), 1); +} + +void GestureTest::testSwipeCancel_data() +{ + QTest::addColumn("direction"); + + QTest::newRow("Up") << KWin::SwipeGesture::Direction::Up; + QTest::newRow("Left") << KWin::SwipeGesture::Direction::Left; + QTest::newRow("Right") << KWin::SwipeGesture::Direction::Right; + QTest::newRow("Down") << KWin::SwipeGesture::Direction::Down; +} + +void GestureTest::testSwipeCancel() +{ + GestureRecognizer recognizer; + std::unique_ptr gesture(new SwipeGesture); + QFETCH(SwipeGesture::Direction, direction); + gesture->setDirection(direction); + QSignalSpy startedSpy(gesture.get(), &SwipeGesture::started); + QSignalSpy cancelledSpy(gesture.get(), &SwipeGesture::cancelled); + QSignalSpy triggeredSpy(gesture.get(), &SwipeGesture::triggered); + + recognizer.registerSwipeGesture(gesture.get()); + recognizer.startSwipeGesture(1); + QCOMPARE(startedSpy.count(), 1); + QCOMPARE(cancelledSpy.count(), 0); + recognizer.cancelSwipeGesture(); + QCOMPARE(cancelledSpy.count(), 1); + QCOMPARE(triggeredSpy.count(), 0); +} + +void GestureTest::testSwipeUpdateTrigger_data() +{ + QTest::addColumn("direction"); + QTest::addColumn("delta"); + + QTest::newRow("Up") << KWin::SwipeGesture::Direction::Up << QPointF(2, -3); + QTest::newRow("Left") << KWin::SwipeGesture::Direction::Left << QPointF(-3, 1); + QTest::newRow("Right") << KWin::SwipeGesture::Direction::Right << QPointF(20, -19); + QTest::newRow("Down") << KWin::SwipeGesture::Direction::Down << QPointF(0, 50); +} + +void GestureTest::testSwipeUpdateTrigger() +{ + GestureRecognizer recognizer; + SwipeGesture gesture; + QFETCH(SwipeGesture::Direction, direction); + gesture.setDirection(direction); + + QSignalSpy triggeredSpy(&gesture, &SwipeGesture::triggered); + QSignalSpy cancelledSpy(&gesture, &SwipeGesture::cancelled); + + recognizer.registerSwipeGesture(&gesture); + + recognizer.startSwipeGesture(1); + QFETCH(QPointF, delta); + recognizer.updateSwipeGesture(delta); + QCOMPARE(cancelledSpy.count(), 0); + QCOMPARE(triggeredSpy.count(), 0); + + recognizer.endSwipeGesture(); + QCOMPARE(cancelledSpy.count(), 0); + QCOMPARE(triggeredSpy.count(), 1); +} + +void GestureTest::testSwipeMinFingerStart_data() +{ + QTest::addColumn("min"); + QTest::addColumn("count"); + QTest::addColumn("started"); + + QTest::newRow("same") << 1u << 1u << true; + QTest::newRow("less") << 2u << 1u << false; + QTest::newRow("more") << 1u << 2u << true; +} + +void GestureTest::testSwipeMinFingerStart() +{ + GestureRecognizer recognizer; + SwipeGesture gesture; + QFETCH(uint, min); + gesture.setMinimumFingerCount(min); + + QSignalSpy startedSpy(&gesture, &SwipeGesture::started); + + recognizer.registerSwipeGesture(&gesture); + QFETCH(uint, count); + recognizer.startSwipeGesture(count); + QTEST(!startedSpy.isEmpty(), "started"); +} + +void GestureTest::testSwipeMaxFingerStart_data() +{ + QTest::addColumn("max"); + QTest::addColumn("count"); + QTest::addColumn("started"); + + QTest::newRow("same") << 1u << 1u << true; + QTest::newRow("less") << 2u << 1u << true; + QTest::newRow("more") << 1u << 2u << false; +} + +void GestureTest::testSwipeMaxFingerStart() +{ + GestureRecognizer recognizer; + SwipeGesture gesture; + QFETCH(uint, max); + gesture.setMaximumFingerCount(max); + + QSignalSpy startedSpy(&gesture, &SwipeGesture::started); + + recognizer.registerSwipeGesture(&gesture); + QFETCH(uint, count); + recognizer.startSwipeGesture(count); + QTEST(!startedSpy.isEmpty(), "started"); +} + +void GestureTest::testNotEmitCallbacksBeforeDirectionDecided() +{ + GestureRecognizer recognizer; + SwipeGesture up; + SwipeGesture down; + SwipeGesture right; + PinchGesture expand; + PinchGesture contract; + up.setDirection(SwipeGesture::Direction::Up); + down.setDirection(SwipeGesture::Direction::Down); + right.setDirection(SwipeGesture::Direction::Right); + expand.setDirection(PinchGesture::Direction::Expanding); + contract.setDirection(PinchGesture::Direction::Contracting); + recognizer.registerSwipeGesture(&up); + recognizer.registerSwipeGesture(&down); + recognizer.registerSwipeGesture(&right); + recognizer.registerPinchGesture(&expand); + recognizer.registerPinchGesture(&contract); + + QSignalSpy upSpy(&up, &SwipeGesture::progress); + QSignalSpy downSpy(&down, &SwipeGesture::progress); + QSignalSpy rightSpy(&right, &SwipeGesture::progress); + QSignalSpy expandSpy(&expand, &PinchGesture::progress); + QSignalSpy contractSpy(&contract, &PinchGesture::progress); + + // don't release callback until we know the direction of swipe gesture + recognizer.startSwipeGesture(4); + QCOMPARE(upSpy.count(), 0); + QCOMPARE(downSpy.count(), 0); + QCOMPARE(rightSpy.count(), 0); + + // up (negative y) + recognizer.updateSwipeGesture(QPointF(0, -1.5)); + QCOMPARE(upSpy.count(), 1); + QCOMPARE(downSpy.count(), 0); + QCOMPARE(rightSpy.count(), 0); + + // down (positive y) + // recognizer.updateSwipeGesture(QPointF(0, 0)); + recognizer.updateSwipeGesture(QPointF(0, 3)); + QCOMPARE(upSpy.count(), 1); + QCOMPARE(downSpy.count(), 1); + QCOMPARE(rightSpy.count(), 0); + + // right + recognizer.cancelSwipeGesture(); + recognizer.startSwipeGesture(4); + recognizer.updateSwipeGesture(QPointF(1, 0)); + QCOMPARE(upSpy.count(), 1); + QCOMPARE(downSpy.count(), 1); + QCOMPARE(rightSpy.count(), 1); + + recognizer.cancelSwipeGesture(); + + // same test for pinch gestures + recognizer.startPinchGesture(4); + QCOMPARE(expandSpy.count(), 0); + QCOMPARE(contractSpy.count(), 0); + + // contracting + recognizer.updatePinchGesture(.5, 0, QPointF(0, 0)); + QCOMPARE(expandSpy.count(), 0); + QCOMPARE(contractSpy.count(), 1); + + // expanding + recognizer.updatePinchGesture(1.5, 0, QPointF(0, 0)); + QCOMPARE(expandSpy.count(), 1); + QCOMPARE(contractSpy.count(), 1); +} + +void GestureTest::testSwipeGeometryStart_data() +{ + QTest::addColumn("geometry"); + QTest::addColumn("startPos"); + QTest::addColumn("started"); + + QTest::newRow("top left") << QRect(0, 0, 10, 20) << QPointF(0, 0) << true; + QTest::newRow("top right") << QRect(0, 0, 10, 20) << QPointF(10, 0) << true; + QTest::newRow("bottom left") << QRect(0, 0, 10, 20) << QPointF(0, 20) << true; + QTest::newRow("bottom right") << QRect(0, 0, 10, 20) << QPointF(10, 20) << true; + QTest::newRow("x too small") << QRect(10, 20, 30, 40) << QPointF(9, 25) << false; + QTest::newRow("y too small") << QRect(10, 20, 30, 40) << QPointF(25, 19) << false; + QTest::newRow("x too large") << QRect(10, 20, 30, 40) << QPointF(41, 25) << false; + QTest::newRow("y too large") << QRect(10, 20, 30, 40) << QPointF(25, 61) << false; + QTest::newRow("inside") << QRect(10, 20, 30, 40) << QPointF(25, 25) << true; +} + +void GestureTest::testSwipeGeometryStart() +{ + GestureRecognizer recognizer; + SwipeGesture gesture; + QFETCH(QRect, geometry); + gesture.setStartGeometry(geometry); + + QSignalSpy startedSpy(&gesture, &SwipeGesture::started); + + recognizer.registerSwipeGesture(&gesture); + QFETCH(QPointF, startPos); + recognizer.startSwipeGesture(startPos); + QTEST(!startedSpy.isEmpty(), "started"); +} + +QTEST_MAIN(GestureTest) +#include "test_gestures.moc" diff --git a/autotests/test_utils.cpp b/autotests/test_utils.cpp new file mode 100644 index 0000000..36e4388 --- /dev/null +++ b/autotests/test_utils.cpp @@ -0,0 +1,71 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2022 MBition GmbH + SPDX-FileContributor: Kai Uwe Broulik + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include + +#include +#include + +#include "utils/ramfile.h" + +#include + +using namespace KWin; + +class TestUtils : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testRamFile(); + void testSealedRamFile(); +}; + +static const QByteArray s_testByteArray = QByteArrayLiteral("Test Data \0\1\2\3"); +static const char s_writeTestArray[] = "test"; + +void TestUtils::testRamFile() +{ + KWin::RamFile file("test", s_testByteArray.constData(), s_testByteArray.size()); + QVERIFY(file.isValid()); + QCOMPARE(file.size(), s_testByteArray.size()); + + QVERIFY(file.fd() != -1); + + char buf[20]; + int num = read(file.fd(), buf, sizeof buf); + QCOMPARE(num, file.size()); + + QCOMPARE(qstrcmp(s_testByteArray.constData(), buf), 0); +} + +void TestUtils::testSealedRamFile() +{ +#if HAVE_MEMFD + KWin::RamFile file("test", s_testByteArray.constData(), s_testByteArray.size(), KWin::RamFile::Flag::SealWrite); + QVERIFY(file.isValid()); + QVERIFY(file.effectiveFlags().testFlag(KWin::RamFile::Flag::SealWrite)); + + // Writing should not work. + auto written = write(file.fd(), s_writeTestArray, strlen(s_writeTestArray)); + QCOMPARE(written, -1); + + // Cannot use MAP_SHARED on sealed file descriptor. + void *data = mmap(nullptr, file.size(), PROT_READ, MAP_SHARED, file.fd(), 0); + QCOMPARE(data, MAP_FAILED); + + data = mmap(nullptr, file.size(), PROT_READ, MAP_PRIVATE, file.fd(), 0); + QVERIFY(data != MAP_FAILED); +#else + QSKIP("Sealing requires memfd suport."); +#endif +} + +QTEST_MAIN(TestUtils) +#include "test_utils.moc" diff --git a/autotests/test_virtual_desktops.cpp b/autotests/test_virtual_desktops.cpp new file mode 100644 index 0000000..d5a22ac --- /dev/null +++ b/autotests/test_virtual_desktops.cpp @@ -0,0 +1,652 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2012 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "input.h" +#include "virtualdesktops.h" +// KDE +#include + +#include +#include + +namespace KWin +{ + +InputRedirection *InputRedirection::s_self = nullptr; + +void InputRedirection::registerShortcut(const QKeySequence &shortcut, QAction *action) +{ + Q_UNUSED(shortcut) + Q_UNUSED(action) +} + +void InputRedirection::registerAxisShortcut(Qt::KeyboardModifiers modifiers, PointerAxisDirection axis, QAction *action) +{ + Q_UNUSED(modifiers) + Q_UNUSED(axis) + Q_UNUSED(action) +} + +void InputRedirection::registerTouchpadSwipeShortcut(SwipeDirection, uint fingerCount, QAction *) +{ + Q_UNUSED(fingerCount) +} + +void InputRedirection::registerRealtimeTouchpadSwipeShortcut(SwipeDirection, uint fingerCount, QAction *, std::function progressCallback) +{ + Q_UNUSED(progressCallback) + Q_UNUSED(fingerCount) +} + +void InputRedirection::registerTouchscreenSwipeShortcut(SwipeDirection, uint, QAction *, std::function) +{ +} + +} + +Q_DECLARE_METATYPE(Qt::Orientation) + +using namespace KWin; + +class TestVirtualDesktops : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void init(); + void cleanup(); + void count_data(); + void count(); + void navigationWrapsAround_data(); + void navigationWrapsAround(); + void current_data(); + void current(); + void currentChangeOnCountChange_data(); + void currentChangeOnCountChange(); + void next_data(); + void next(); + void previous_data(); + void previous(); + void left_data(); + void left(); + void right_data(); + void right(); + void above_data(); + void above(); + void below_data(); + void below(); + void updateGrid_data(); + void updateGrid(); + void updateLayout_data(); + void updateLayout(); + void name_data(); + void name(); + void switchToShortcuts(); + void changeRows(); + void load(); + void save(); + +private: + void addDirectionColumns(); + template + void testDirection(const QString &actionName); +}; + +void TestVirtualDesktops::init() +{ + VirtualDesktopManager::create(); +} + +void TestVirtualDesktops::cleanup() +{ + delete VirtualDesktopManager::self(); +} + +static const uint s_countInitValue = 2; + +void TestVirtualDesktops::count_data() +{ + QTest::addColumn("request"); + QTest::addColumn("result"); + QTest::addColumn("signal"); + QTest::addColumn("removedSignal"); + + QTest::newRow("Minimum") << (uint)1 << (uint)1 << true << true; + QTest::newRow("Below Minimum") << (uint)0 << (uint)1 << true << true; + QTest::newRow("Normal Value") << (uint)10 << (uint)10 << true << false; + QTest::newRow("Maximum") << VirtualDesktopManager::maximum() << VirtualDesktopManager::maximum() << true << false; + QTest::newRow("Above Maximum") << VirtualDesktopManager::maximum() + 1 << VirtualDesktopManager::maximum() << true << false; + QTest::newRow("Unchanged") << s_countInitValue << s_countInitValue << false << false; +} + +void TestVirtualDesktops::count() +{ + VirtualDesktopManager *vds = VirtualDesktopManager::self(); + QCOMPARE(vds->count(), (uint)0); + // start with a useful desktop count + vds->setCount(s_countInitValue); + + QSignalSpy spy(vds, &VirtualDesktopManager::countChanged); + QSignalSpy desktopsRemoved(vds, &VirtualDesktopManager::desktopRemoved); + + auto vdToRemove = vds->desktops().last(); + + QFETCH(uint, request); + QFETCH(uint, result); + QFETCH(bool, signal); + QFETCH(bool, removedSignal); + vds->setCount(request); + QCOMPARE(vds->count(), result); + QCOMPARE(spy.isEmpty(), !signal); + if (!spy.isEmpty()) { + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.count(), 2); + QCOMPARE(arguments.at(0).type(), QVariant::UInt); + QCOMPARE(arguments.at(1).type(), QVariant::UInt); + QCOMPARE(arguments.at(0).toUInt(), s_countInitValue); + QCOMPARE(arguments.at(1).toUInt(), result); + } + QCOMPARE(desktopsRemoved.isEmpty(), !removedSignal); + if (!desktopsRemoved.isEmpty()) { + QList arguments = desktopsRemoved.takeFirst(); + QCOMPARE(arguments.count(), 1); + QCOMPARE(arguments.at(0).value(), vdToRemove); + } +} + +void TestVirtualDesktops::navigationWrapsAround_data() +{ + QTest::addColumn("init"); + QTest::addColumn("request"); + QTest::addColumn("result"); + QTest::addColumn("signal"); + + QTest::newRow("enable") << false << true << true << true; + QTest::newRow("disable") << true << false << false << true; + QTest::newRow("keep enabled") << true << true << true << false; + QTest::newRow("keep disabled") << false << false << false << false; +} + +void TestVirtualDesktops::navigationWrapsAround() +{ + VirtualDesktopManager *vds = VirtualDesktopManager::self(); + QCOMPARE(vds->isNavigationWrappingAround(), false); + QFETCH(bool, init); + QFETCH(bool, request); + QFETCH(bool, result); + QFETCH(bool, signal); + + // set to init value + vds->setNavigationWrappingAround(init); + QCOMPARE(vds->isNavigationWrappingAround(), init); + + QSignalSpy spy(vds, &VirtualDesktopManager::navigationWrappingAroundChanged); + vds->setNavigationWrappingAround(request); + QCOMPARE(vds->isNavigationWrappingAround(), result); + QCOMPARE(spy.isEmpty(), !signal); +} + +void TestVirtualDesktops::current_data() +{ + QTest::addColumn("count"); + QTest::addColumn("init"); + QTest::addColumn("request"); + QTest::addColumn("result"); + QTest::addColumn("signal"); + + QTest::newRow("lower") << (uint)4 << (uint)3 << (uint)2 << (uint)2 << true; + QTest::newRow("higher") << (uint)4 << (uint)1 << (uint)2 << (uint)2 << true; + QTest::newRow("maximum") << (uint)4 << (uint)1 << (uint)4 << (uint)4 << true; + QTest::newRow("above maximum") << (uint)4 << (uint)1 << (uint)5 << (uint)1 << false; + QTest::newRow("minimum") << (uint)4 << (uint)2 << (uint)1 << (uint)1 << true; + QTest::newRow("below minimum") << (uint)4 << (uint)2 << (uint)0 << (uint)2 << false; + QTest::newRow("unchanged") << (uint)4 << (uint)2 << (uint)2 << (uint)2 << false; +} + +void TestVirtualDesktops::current() +{ + VirtualDesktopManager *vds = VirtualDesktopManager::self(); + QCOMPARE(vds->current(), (uint)0); + QFETCH(uint, count); + vds->setCount(count); + QFETCH(uint, init); + QVERIFY(vds->setCurrent(init)); + QCOMPARE(vds->current(), init); + + QSignalSpy spy(vds, &VirtualDesktopManager::currentChanged); + + QFETCH(uint, request); + QFETCH(uint, result); + QFETCH(bool, signal); + QCOMPARE(vds->setCurrent(request), signal); + QCOMPARE(vds->current(), result); + QCOMPARE(spy.isEmpty(), !signal); + if (!spy.isEmpty()) { + QList arguments = spy.takeFirst(); + QCOMPARE(arguments.count(), 2); + QCOMPARE(arguments.at(0).type(), QVariant::UInt); + QCOMPARE(arguments.at(1).type(), QVariant::UInt); + QCOMPARE(arguments.at(0).toUInt(), init); + QCOMPARE(arguments.at(1).toUInt(), result); + } +} + +void TestVirtualDesktops::currentChangeOnCountChange_data() +{ + QTest::addColumn("initCount"); + QTest::addColumn("initCurrent"); + QTest::addColumn("request"); + QTest::addColumn("current"); + QTest::addColumn("signal"); + + QTest::newRow("increment") << (uint)4 << (uint)2 << (uint)5 << (uint)2 << false; + QTest::newRow("increment on last") << (uint)4 << (uint)4 << (uint)5 << (uint)4 << false; + QTest::newRow("decrement") << (uint)4 << (uint)2 << (uint)3 << (uint)2 << false; + QTest::newRow("decrement on second last") << (uint)4 << (uint)3 << (uint)3 << (uint)3 << false; + QTest::newRow("decrement on last") << (uint)4 << (uint)4 << (uint)3 << (uint)3 << true; + QTest::newRow("multiple decrement") << (uint)4 << (uint)2 << (uint)1 << (uint)1 << true; +} + +void TestVirtualDesktops::currentChangeOnCountChange() +{ + VirtualDesktopManager *vds = VirtualDesktopManager::self(); + QFETCH(uint, initCount); + QFETCH(uint, initCurrent); + vds->setCount(initCount); + vds->setCurrent(initCurrent); + + QSignalSpy spy(vds, &VirtualDesktopManager::currentChanged); + + QFETCH(uint, request); + QFETCH(uint, current); + QFETCH(bool, signal); + + vds->setCount(request); + QCOMPARE(vds->current(), current); + QCOMPARE(spy.isEmpty(), !signal); +} + +void TestVirtualDesktops::addDirectionColumns() +{ + QTest::addColumn("initCount"); + QTest::addColumn("initCurrent"); + QTest::addColumn("wrap"); + QTest::addColumn("result"); +} + +template +void TestVirtualDesktops::testDirection(const QString &actionName) +{ + VirtualDesktopManager *vds = VirtualDesktopManager::self(); + QFETCH(uint, initCount); + QFETCH(uint, initCurrent); + vds->setCount(initCount); + vds->setCurrent(initCurrent); + + QFETCH(bool, wrap); + QFETCH(uint, result); + T functor; + QCOMPARE(functor(nullptr, wrap)->x11DesktopNumber(), result); + + vds->setNavigationWrappingAround(wrap); + vds->initShortcuts(); + QAction *action = vds->findChild(actionName); + QVERIFY(action); + action->trigger(); + QCOMPARE(vds->current(), result); + QCOMPARE(functor(initCurrent, wrap), result); +} + +void TestVirtualDesktops::next_data() +{ + addDirectionColumns(); + + QTest::newRow("one desktop, wrap") << (uint)1 << (uint)1 << true << (uint)1; + QTest::newRow("one desktop, no wrap") << (uint)1 << (uint)1 << false << (uint)1; + QTest::newRow("desktops, wrap") << (uint)4 << (uint)1 << true << (uint)2; + QTest::newRow("desktops, no wrap") << (uint)4 << (uint)1 << false << (uint)2; + QTest::newRow("desktops at end, wrap") << (uint)4 << (uint)4 << true << (uint)1; + QTest::newRow("desktops at end, no wrap") << (uint)4 << (uint)4 << false << (uint)4; +} + +void TestVirtualDesktops::next() +{ + testDirection(QStringLiteral("Switch to Next Desktop")); +} + +void TestVirtualDesktops::previous_data() +{ + addDirectionColumns(); + + QTest::newRow("one desktop, wrap") << (uint)1 << (uint)1 << true << (uint)1; + QTest::newRow("one desktop, no wrap") << (uint)1 << (uint)1 << false << (uint)1; + QTest::newRow("desktops, wrap") << (uint)4 << (uint)3 << true << (uint)2; + QTest::newRow("desktops, no wrap") << (uint)4 << (uint)3 << false << (uint)2; + QTest::newRow("desktops at start, wrap") << (uint)4 << (uint)1 << true << (uint)4; + QTest::newRow("desktops at start, no wrap") << (uint)4 << (uint)1 << false << (uint)1; +} + +void TestVirtualDesktops::previous() +{ + testDirection(QStringLiteral("Switch to Previous Desktop")); +} + +void TestVirtualDesktops::left_data() +{ + addDirectionColumns(); + QTest::newRow("one desktop, wrap") << (uint)1 << (uint)1 << true << (uint)1; + QTest::newRow("one desktop, no wrap") << (uint)1 << (uint)1 << false << (uint)1; + QTest::newRow("desktops, wrap, 1st row") << (uint)4 << (uint)2 << true << (uint)1; + QTest::newRow("desktops, no wrap, 1st row") << (uint)4 << (uint)2 << false << (uint)1; + QTest::newRow("desktops, wrap, 2nd row") << (uint)4 << (uint)4 << true << (uint)3; + QTest::newRow("desktops, no wrap, 2nd row") << (uint)4 << (uint)4 << false << (uint)3; + + QTest::newRow("desktops at start, wrap, 1st row") << (uint)4 << (uint)1 << true << (uint)2; + QTest::newRow("desktops at start, no wrap, 1st row") << (uint)4 << (uint)1 << false << (uint)1; + QTest::newRow("desktops at start, wrap, 2nd row") << (uint)4 << (uint)3 << true << (uint)4; + QTest::newRow("desktops at start, no wrap, 2nd row") << (uint)4 << (uint)3 << false << (uint)3; + + QTest::newRow("non symmetric, start") << (uint)5 << (uint)5 << false << (uint)4; + QTest::newRow("non symmetric, end, no wrap") << (uint)5 << (uint)4 << false << (uint)4; + QTest::newRow("non symmetric, end, wrap") << (uint)5 << (uint)4 << true << (uint)5; +} + +void TestVirtualDesktops::left() +{ + testDirection(QStringLiteral("Switch One Desktop to the Left")); +} + +void TestVirtualDesktops::right_data() +{ + addDirectionColumns(); + QTest::newRow("one desktop, wrap") << (uint)1 << (uint)1 << true << (uint)1; + QTest::newRow("one desktop, no wrap") << (uint)1 << (uint)1 << false << (uint)1; + QTest::newRow("desktops, wrap, 1st row") << (uint)4 << (uint)1 << true << (uint)2; + QTest::newRow("desktops, no wrap, 1st row") << (uint)4 << (uint)1 << false << (uint)2; + QTest::newRow("desktops, wrap, 2nd row") << (uint)4 << (uint)3 << true << (uint)4; + QTest::newRow("desktops, no wrap, 2nd row") << (uint)4 << (uint)3 << false << (uint)4; + + QTest::newRow("desktops at start, wrap, 1st row") << (uint)4 << (uint)2 << true << (uint)1; + QTest::newRow("desktops at start, no wrap, 1st row") << (uint)4 << (uint)2 << false << (uint)2; + QTest::newRow("desktops at start, wrap, 2nd row") << (uint)4 << (uint)4 << true << (uint)3; + QTest::newRow("desktops at start, no wrap, 2nd row") << (uint)4 << (uint)4 << false << (uint)4; + + QTest::newRow("non symmetric, start") << (uint)5 << (uint)4 << false << (uint)5; + QTest::newRow("non symmetric, end, no wrap") << (uint)5 << (uint)5 << false << (uint)5; + QTest::newRow("non symmetric, end, wrap") << (uint)5 << (uint)5 << true << (uint)4; +} + +void TestVirtualDesktops::right() +{ + testDirection(QStringLiteral("Switch One Desktop to the Right")); +} + +void TestVirtualDesktops::above_data() +{ + addDirectionColumns(); + QTest::newRow("one desktop, wrap") << (uint)1 << (uint)1 << true << (uint)1; + QTest::newRow("one desktop, no wrap") << (uint)1 << (uint)1 << false << (uint)1; + QTest::newRow("desktops, wrap, 1st column") << (uint)4 << (uint)3 << true << (uint)1; + QTest::newRow("desktops, no wrap, 1st column") << (uint)4 << (uint)3 << false << (uint)1; + QTest::newRow("desktops, wrap, 2nd column") << (uint)4 << (uint)4 << true << (uint)2; + QTest::newRow("desktops, no wrap, 2nd column") << (uint)4 << (uint)4 << false << (uint)2; + + QTest::newRow("desktops at start, wrap, 1st column") << (uint)4 << (uint)1 << true << (uint)3; + QTest::newRow("desktops at start, no wrap, 1st column") << (uint)4 << (uint)1 << false << (uint)1; + QTest::newRow("desktops at start, wrap, 2nd column") << (uint)4 << (uint)2 << true << (uint)4; + QTest::newRow("desktops at start, no wrap, 2nd column") << (uint)4 << (uint)2 << false << (uint)2; +} + +void TestVirtualDesktops::above() +{ + testDirection(QStringLiteral("Switch One Desktop Up")); +} + +void TestVirtualDesktops::below_data() +{ + addDirectionColumns(); + QTest::newRow("one desktop, wrap") << (uint)1 << (uint)1 << true << (uint)1; + QTest::newRow("one desktop, no wrap") << (uint)1 << (uint)1 << false << (uint)1; + QTest::newRow("desktops, wrap, 1st column") << (uint)4 << (uint)1 << true << (uint)3; + QTest::newRow("desktops, no wrap, 1st column") << (uint)4 << (uint)1 << false << (uint)3; + QTest::newRow("desktops, wrap, 2nd column") << (uint)4 << (uint)2 << true << (uint)4; + QTest::newRow("desktops, no wrap, 2nd column") << (uint)4 << (uint)2 << false << (uint)4; + + QTest::newRow("desktops at start, wrap, 1st column") << (uint)4 << (uint)3 << true << (uint)1; + QTest::newRow("desktops at start, no wrap, 1st column") << (uint)4 << (uint)3 << false << (uint)3; + QTest::newRow("desktops at start, wrap, 2nd column") << (uint)4 << (uint)4 << true << (uint)2; + QTest::newRow("desktops at start, no wrap, 2nd column") << (uint)4 << (uint)4 << false << (uint)4; +} + +void TestVirtualDesktops::below() +{ + testDirection(QStringLiteral("Switch One Desktop Down")); +} + +void TestVirtualDesktops::updateGrid_data() +{ + QTest::addColumn("initCount"); + QTest::addColumn("size"); + QTest::addColumn("orientation"); + QTest::addColumn("coords"); + QTest::addColumn("desktop"); + const Qt::Orientation h = Qt::Horizontal; + const Qt::Orientation v = Qt::Vertical; + + QTest::newRow("one desktop, h") << (uint)1 << QSize(1, 1) << h << QPoint(0, 0) << (uint)1; + QTest::newRow("one desktop, v") << (uint)1 << QSize(1, 1) << v << QPoint(0, 0) << (uint)1; + QTest::newRow("one desktop, h, 0") << (uint)1 << QSize(1, 1) << h << QPoint(1, 0) << (uint)0; + QTest::newRow("one desktop, v, 0") << (uint)1 << QSize(1, 1) << v << QPoint(0, 1) << (uint)0; + + QTest::newRow("two desktops, h, 1") << (uint)2 << QSize(2, 1) << h << QPoint(0, 0) << (uint)1; + QTest::newRow("two desktops, h, 2") << (uint)2 << QSize(2, 1) << h << QPoint(1, 0) << (uint)2; + QTest::newRow("two desktops, h, 3") << (uint)2 << QSize(2, 1) << h << QPoint(0, 1) << (uint)0; + QTest::newRow("two desktops, h, 4") << (uint)2 << QSize(2, 1) << h << QPoint(2, 0) << (uint)0; + + QTest::newRow("two desktops, v, 1") << (uint)2 << QSize(2, 1) << v << QPoint(0, 0) << (uint)1; + QTest::newRow("two desktops, v, 2") << (uint)2 << QSize(2, 1) << v << QPoint(1, 0) << (uint)2; + QTest::newRow("two desktops, v, 3") << (uint)2 << QSize(2, 1) << v << QPoint(0, 1) << (uint)0; + QTest::newRow("two desktops, v, 4") << (uint)2 << QSize(2, 1) << v << QPoint(2, 0) << (uint)0; + + QTest::newRow("four desktops, h, one row, 1") << (uint)4 << QSize(4, 1) << h << QPoint(0, 0) << (uint)1; + QTest::newRow("four desktops, h, one row, 2") << (uint)4 << QSize(4, 1) << h << QPoint(1, 0) << (uint)2; + QTest::newRow("four desktops, h, one row, 3") << (uint)4 << QSize(4, 1) << h << QPoint(2, 0) << (uint)3; + QTest::newRow("four desktops, h, one row, 4") << (uint)4 << QSize(4, 1) << h << QPoint(3, 0) << (uint)4; + + QTest::newRow("four desktops, v, one column, 1") << (uint)4 << QSize(1, 4) << v << QPoint(0, 0) << (uint)1; + QTest::newRow("four desktops, v, one column, 2") << (uint)4 << QSize(1, 4) << v << QPoint(0, 1) << (uint)2; + QTest::newRow("four desktops, v, one column, 3") << (uint)4 << QSize(1, 4) << v << QPoint(0, 2) << (uint)3; + QTest::newRow("four desktops, v, one column, 4") << (uint)4 << QSize(1, 4) << v << QPoint(0, 3) << (uint)4; + + QTest::newRow("four desktops, h, grid, 1") << (uint)4 << QSize(2, 2) << h << QPoint(0, 0) << (uint)1; + QTest::newRow("four desktops, h, grid, 2") << (uint)4 << QSize(2, 2) << h << QPoint(1, 0) << (uint)2; + QTest::newRow("four desktops, h, grid, 3") << (uint)4 << QSize(2, 2) << h << QPoint(0, 1) << (uint)3; + QTest::newRow("four desktops, h, grid, 4") << (uint)4 << QSize(2, 2) << h << QPoint(1, 1) << (uint)4; + QTest::newRow("four desktops, h, grid, 0/3") << (uint)4 << QSize(2, 2) << h << QPoint(0, 3) << (uint)0; + + QTest::newRow("three desktops, h, grid, 1") << (uint)3 << QSize(2, 2) << h << QPoint(0, 0) << (uint)1; + QTest::newRow("three desktops, h, grid, 2") << (uint)3 << QSize(2, 2) << h << QPoint(1, 0) << (uint)2; + QTest::newRow("three desktops, h, grid, 3") << (uint)3 << QSize(2, 2) << h << QPoint(0, 1) << (uint)3; + QTest::newRow("three desktops, h, grid, 4") << (uint)3 << QSize(2, 2) << h << QPoint(1, 1) << (uint)0; +} + +void TestVirtualDesktops::updateGrid() +{ + VirtualDesktopManager *vds = VirtualDesktopManager::self(); + QFETCH(uint, initCount); + vds->setCount(initCount); + VirtualDesktopGrid grid; + + QFETCH(QSize, size); + QFETCH(Qt::Orientation, orientation); + QCOMPARE(vds->desktops().count(), int(initCount)); + grid.update(size, orientation, vds->desktops()); + QCOMPARE(grid.size(), size); + QCOMPARE(grid.width(), size.width()); + QCOMPARE(grid.height(), size.height()); + QFETCH(QPoint, coords); + QFETCH(uint, desktop); + QCOMPARE(grid.at(coords), vds->desktopForX11Id(desktop)); + if (desktop != 0) { + QCOMPARE(grid.gridCoords(desktop), coords); + } +} + +void TestVirtualDesktops::updateLayout_data() +{ + QTest::addColumn("desktop"); + QTest::addColumn("result"); + + QTest::newRow("01") << (uint)1 << QSize(1, 1); + QTest::newRow("02") << (uint)2 << QSize(1, 2); + QTest::newRow("03") << (uint)3 << QSize(2, 2); + QTest::newRow("04") << (uint)4 << QSize(2, 2); + QTest::newRow("05") << (uint)5 << QSize(3, 2); + QTest::newRow("06") << (uint)6 << QSize(3, 2); + QTest::newRow("07") << (uint)7 << QSize(4, 2); + QTest::newRow("08") << (uint)8 << QSize(4, 2); + QTest::newRow("09") << (uint)9 << QSize(5, 2); + QTest::newRow("10") << (uint)10 << QSize(5, 2); + QTest::newRow("11") << (uint)11 << QSize(6, 2); + QTest::newRow("12") << (uint)12 << QSize(6, 2); + QTest::newRow("13") << (uint)13 << QSize(7, 2); + QTest::newRow("14") << (uint)14 << QSize(7, 2); + QTest::newRow("15") << (uint)15 << QSize(8, 2); + QTest::newRow("16") << (uint)16 << QSize(8, 2); + QTest::newRow("17") << (uint)17 << QSize(9, 2); + QTest::newRow("18") << (uint)18 << QSize(9, 2); + QTest::newRow("19") << (uint)19 << QSize(10, 2); + QTest::newRow("20") << (uint)20 << QSize(10, 2); +} + +void TestVirtualDesktops::updateLayout() +{ + VirtualDesktopManager *vds = VirtualDesktopManager::self(); + QSignalSpy spy(vds, &VirtualDesktopManager::layoutChanged); + // call update layout - implicitly through setCount + QFETCH(uint, desktop); + QFETCH(QSize, result); + vds->setCount(desktop); + QCOMPARE(vds->grid().size(), result); + QCOMPARE(spy.count(), 1); + const QVariantList &arguments = spy.at(0); + QCOMPARE(arguments.at(0).toInt(), result.width()); + QCOMPARE(arguments.at(1).toInt(), result.height()); + // calling update layout again should not change anything + vds->updateLayout(); + QCOMPARE(vds->grid().size(), result); + QCOMPARE(spy.count(), 2); + const QVariantList &arguments2 = spy.at(1); + QCOMPARE(arguments2.at(0).toInt(), result.width()); + QCOMPARE(arguments2.at(1).toInt(), result.height()); +} + +void TestVirtualDesktops::name_data() +{ + QTest::addColumn("initCount"); + QTest::addColumn("desktop"); + QTest::addColumn("desktopName"); + + QTest::newRow("desktop 1") << (uint)5 << (uint)1 << "Desktop 1"; + QTest::newRow("desktop 2") << (uint)5 << (uint)2 << "Desktop 2"; + QTest::newRow("desktop 3") << (uint)5 << (uint)3 << "Desktop 3"; + QTest::newRow("desktop 4") << (uint)5 << (uint)4 << "Desktop 4"; + QTest::newRow("desktop 5") << (uint)5 << (uint)5 << "Desktop 5"; +} + +void TestVirtualDesktops::name() +{ + VirtualDesktopManager *vds = VirtualDesktopManager::self(); + QFETCH(uint, initCount); + vds->setCount(initCount); + QFETCH(uint, desktop); + + const VirtualDesktop *vd = vds->desktopForX11Id(desktop); + QTEST(vd->name(), "desktopName"); +} + +void TestVirtualDesktops::switchToShortcuts() +{ + VirtualDesktopManager *vds = VirtualDesktopManager::self(); + vds->setCount(vds->maximum()); + vds->setCurrent(vds->maximum()); + QCOMPARE(vds->current(), vds->maximum()); + vds->initShortcuts(); + const QString toDesktop = QStringLiteral("Switch to Desktop %1"); + for (uint i = 1; i <= vds->maximum(); ++i) { + const QString desktop(toDesktop.arg(i)); + QAction *action = vds->findChild(desktop); + QVERIFY2(action, desktop.toUtf8().constData()); + action->trigger(); + QCOMPARE(vds->current(), i); + } + // invoke switchTo not from a QAction + QMetaObject::invokeMethod(vds, "slotSwitchTo"); + // should still be on max + QCOMPARE(vds->current(), vds->maximum()); +} + +void TestVirtualDesktops::changeRows() +{ + VirtualDesktopManager *vds = VirtualDesktopManager::self(); + + vds->setCount(4); + vds->setRows(4); + QCOMPARE(vds->rows(), 4); + + vds->setRows(5); + QCOMPARE(vds->rows(), 4); + + vds->setCount(2); + QCOMPARE(vds->rows(), 2); +} + +void TestVirtualDesktops::load() +{ + VirtualDesktopManager *vds = VirtualDesktopManager::self(); + // no config yet, load should not change anything + vds->load(); + QCOMPARE(vds->count(), (uint)0); + // empty config should create one desktop + KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + vds->setConfig(config); + vds->load(); + QCOMPARE(vds->count(), (uint)1); + // setting a sensible number + config->group("Desktops").writeEntry("Number", 4); + vds->load(); + QCOMPARE(vds->count(), (uint)4); + + // setting the config value and reloading should update + config->group("Desktops").writeEntry("Number", 5); + vds->load(); + QCOMPARE(vds->count(), (uint)5); +} + +void TestVirtualDesktops::save() +{ + VirtualDesktopManager *vds = VirtualDesktopManager::self(); + vds->setCount(4); + // no config yet, just to ensure it actually works + vds->save(); + KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + vds->setConfig(config); + + // now save should create the group "Desktops" + QCOMPARE(config->hasGroup("Desktops"), false); + vds->save(); + QCOMPARE(config->hasGroup("Desktops"), true); + KConfigGroup desktops = config->group("Desktops"); + QCOMPARE(desktops.readEntry("Number", 1), 4); + QCOMPARE(desktops.hasKey("Name_1"), false); + QCOMPARE(desktops.hasKey("Name_2"), false); + QCOMPARE(desktops.hasKey("Name_3"), false); + QCOMPARE(desktops.hasKey("Name_4"), false); +} + +QTEST_MAIN(TestVirtualDesktops) +#include "test_virtual_desktops.moc" diff --git a/autotests/test_window_paint_data.cpp b/autotests/test_window_paint_data.cpp new file mode 100644 index 0000000..3699b4f --- /dev/null +++ b/autotests/test_window_paint_data.cpp @@ -0,0 +1,179 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2012 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include + +#include "virtualdesktops.h" + +#include +#include +#include + +#include + +using namespace KWin; + +class TestWindowPaintData : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testCtor(); + void testCopyCtor(); + void testOperatorMultiplyAssign(); + void testOperatorPlus(); + void testMultiplyOpacity(); + void testMultiplySaturation(); + void testMultiplyBrightness(); +}; + +void TestWindowPaintData::testCtor() +{ + WindowPaintData data; + QCOMPARE(data.xScale(), 1.0); + QCOMPARE(data.yScale(), 1.0); + QCOMPARE(data.zScale(), 1.0); + QCOMPARE(data.xTranslation(), 0.0); + QCOMPARE(data.yTranslation(), 0.0); + QCOMPARE(data.zTranslation(), 0.0); + QCOMPARE(data.translation(), QVector3D()); + QCOMPARE(data.rotationAngle(), 0.0); + QCOMPARE(data.rotationOrigin(), QVector3D()); + QCOMPARE(data.rotationAxis(), QVector3D(0.0, 0.0, 1.0)); + QCOMPARE(data.opacity(), 1.0); + QCOMPARE(data.brightness(), 1.0); + QCOMPARE(data.saturation(), 1.0); +} + +void TestWindowPaintData::testCopyCtor() +{ + WindowPaintData data; + WindowPaintData data2(data); + // no value had been changed + QCOMPARE(data2.xScale(), 1.0); + QCOMPARE(data2.yScale(), 1.0); + QCOMPARE(data2.zScale(), 1.0); + QCOMPARE(data2.xTranslation(), 0.0); + QCOMPARE(data2.yTranslation(), 0.0); + QCOMPARE(data2.zTranslation(), 0.0); + QCOMPARE(data2.translation(), QVector3D()); + QCOMPARE(data2.rotationAngle(), 0.0); + QCOMPARE(data2.rotationOrigin(), QVector3D()); + QCOMPARE(data2.rotationAxis(), QVector3D(0.0, 0.0, 1.0)); + QCOMPARE(data2.opacity(), 1.0); + QCOMPARE(data2.brightness(), 1.0); + QCOMPARE(data2.saturation(), 1.0); + + data2.setScale(QVector3D(0.5, 2.0, 3.0)); + data2.translate(0.5, 2.0, 3.0); + data2.setRotationAngle(45.0); + data2.setRotationOrigin(QVector3D(1.0, 2.0, 3.0)); + data2.setRotationAxis(QVector3D(1.0, 1.0, 0.0)); + data2.setOpacity(0.1); + data2.setBrightness(0.3); + data2.setSaturation(0.4); + + WindowPaintData data3(data2); + QCOMPARE(data3.xScale(), 0.5); + QCOMPARE(data3.yScale(), 2.0); + QCOMPARE(data3.zScale(), 3.0); + QCOMPARE(data3.xTranslation(), 0.5); + QCOMPARE(data3.yTranslation(), 2.0); + QCOMPARE(data3.zTranslation(), 3.0); + QCOMPARE(data3.translation(), QVector3D(0.5, 2.0, 3.0)); + QCOMPARE(data3.rotationAngle(), 45.0); + QCOMPARE(data3.rotationOrigin(), QVector3D(1.0, 2.0, 3.0)); + QCOMPARE(data3.rotationAxis(), QVector3D(1.0, 1.0, 0.0)); + QCOMPARE(data3.opacity(), 0.1); + QCOMPARE(data3.brightness(), 0.3); + QCOMPARE(data3.saturation(), 0.4); +} + +void TestWindowPaintData::testOperatorMultiplyAssign() +{ + WindowPaintData data; + // without anything set, it's 1.0 on all axis + QCOMPARE(data.xScale(), 1.0); + QCOMPARE(data.yScale(), 1.0); + QCOMPARE(data.zScale(), 1.0); + // multiplying by a factor should set all components + data *= 2.0; + QCOMPARE(data.xScale(), 2.0); + QCOMPARE(data.yScale(), 2.0); + QCOMPARE(data.zScale(), 2.0); + // multiplying by a vector2D should set x and y components + data *= QVector2D(2.0, 3.0); + QCOMPARE(data.xScale(), 4.0); + QCOMPARE(data.yScale(), 6.0); + QCOMPARE(data.zScale(), 2.0); + // multiplying by a vector3d should set all components + data *= QVector3D(0.5, 1.5, 2.0); + QCOMPARE(data.xScale(), 2.0); + QCOMPARE(data.yScale(), 9.0); + QCOMPARE(data.zScale(), 4.0); +} + +void TestWindowPaintData::testOperatorPlus() +{ + WindowPaintData data; + QCOMPARE(data.xTranslation(), 0.0); + QCOMPARE(data.yTranslation(), 0.0); + QCOMPARE(data.zTranslation(), 0.0); + QCOMPARE(data.translation(), QVector3D()); + // test with point + data += QPoint(1, 2); + QCOMPARE(data.translation(), QVector3D(1.0, 2.0, 0.0)); + // test with pointf + data += QPointF(0.5, 0.75); + QCOMPARE(data.translation(), QVector3D(1.5, 2.75, 0.0)); + // test with QVector2D + data += QVector2D(0.25, 1.5); + QCOMPARE(data.translation(), QVector3D(1.75, 4.25, 0.0)); + // test with QVector3D + data += QVector3D(1.0, 2.0, 3.5); + QCOMPARE(data.translation(), QVector3D(2.75, 6.25, 3.5)); +} + +void TestWindowPaintData::testMultiplyBrightness() +{ + WindowPaintData data; + QCOMPARE(0.2, data.multiplyBrightness(0.2)); + QCOMPARE(0.2, data.brightness()); + QCOMPARE(0.6, data.multiplyBrightness(3.0)); + QCOMPARE(0.6, data.brightness()); + // just for safety + QCOMPARE(1.0, data.opacity()); + QCOMPARE(1.0, data.saturation()); +} + +void TestWindowPaintData::testMultiplyOpacity() +{ + WindowPaintData data; + QCOMPARE(0.2, data.multiplyOpacity(0.2)); + QCOMPARE(0.2, data.opacity()); + QCOMPARE(0.6, data.multiplyOpacity(3.0)); + QCOMPARE(0.6, data.opacity()); + // just for safety + QCOMPARE(1.0, data.brightness()); + QCOMPARE(1.0, data.saturation()); +} + +void TestWindowPaintData::testMultiplySaturation() +{ + WindowPaintData data; + QCOMPARE(0.2, data.multiplySaturation(0.2)); + QCOMPARE(0.2, data.saturation()); + QCOMPARE(0.6, data.multiplySaturation(3.0)); + QCOMPARE(0.6, data.saturation()); + // just for safety + QCOMPARE(1.0, data.brightness()); + QCOMPARE(1.0, data.opacity()); +} + +QTEST_MAIN(TestWindowPaintData) +#include "test_window_paint_data.moc" diff --git a/autotests/test_x11_timestamp_update.cpp b/autotests/test_x11_timestamp_update.cpp new file mode 100644 index 0000000..fcc8dec --- /dev/null +++ b/autotests/test_x11_timestamp_update.cpp @@ -0,0 +1,115 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2017 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#include +#else +#include +#endif + +#include "main.h" +#include "utils/common.h" + +namespace KWin +{ + +class X11TestApplication : public Application +{ + Q_OBJECT +public: + X11TestApplication(int &argc, char **argv); + ~X11TestApplication() override; + +protected: + void performStartup() override; +}; + +X11TestApplication::X11TestApplication(int &argc, char **argv) + : Application(OperationModeX11, argc, argv) +{ + setX11Connection(QX11Info::connection()); + setX11RootWindow(QX11Info::appRootWindow()); +} + +X11TestApplication::~X11TestApplication() +{ +} + +void X11TestApplication::performStartup() +{ +} + +} + +class X11TimestampUpdateTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testGrabAfterServerTime(); + void testBeforeLastGrabTime(); +}; + +void X11TimestampUpdateTest::testGrabAfterServerTime() +{ + // this test tries to grab the X keyboard with a timestamp in future + // that should fail, but after updating the X11 timestamp, it should + // work again + KWin::updateXTime(); + QCOMPARE(KWin::grabXKeyboard(), true); + KWin::ungrabXKeyboard(); + + // now let's change the timestamp + KWin::kwinApp()->setX11Time(KWin::xTime() + 5 * 60 * 1000); + + // now grab keyboard should fail + QCOMPARE(KWin::grabXKeyboard(), false); + + // let's update timestamp, now it should work again + KWin::updateXTime(); + QCOMPARE(KWin::grabXKeyboard(), true); + KWin::ungrabXKeyboard(); +} + +void X11TimestampUpdateTest::testBeforeLastGrabTime() +{ + // this test tries to grab the X keyboard with a timestamp before the + // last grab time on the server. That should fail, but after updating the X11 + // timestamp it should work again + + // first set the grab timestamp + KWin::updateXTime(); + QCOMPARE(KWin::grabXKeyboard(), true); + KWin::ungrabXKeyboard(); + + // now go to past + const auto timestamp = KWin::xTime(); + KWin::kwinApp()->setX11Time(KWin::xTime() - 5 * 60 * 1000, KWin::Application::TimestampUpdate::Always); + QCOMPARE(KWin::xTime(), timestamp - 5 * 60 * 1000); + + // now grab keyboard should fail + QCOMPARE(KWin::grabXKeyboard(), false); + + // let's update timestamp, now it should work again + KWin::updateXTime(); + QVERIFY(KWin::xTime() >= timestamp); + QCOMPARE(KWin::grabXKeyboard(), true); + KWin::ungrabXKeyboard(); +} + +int main(int argc, char *argv[]) +{ + setenv("QT_QPA_PLATFORM", "xcb", true); + KWin::X11TestApplication app(argc, argv); + app.setAttribute(Qt::AA_Use96Dpi, true); + X11TimestampUpdateTest tc; + return QTest::qExec(&tc, argc, argv); +} + +#include "test_x11_timestamp_update.moc" diff --git a/autotests/test_xcb_size_hints.cpp b/autotests/test_xcb_size_hints.cpp new file mode 100644 index 0000000..e1d4da8 --- /dev/null +++ b/autotests/test_xcb_size_hints.cpp @@ -0,0 +1,369 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2015 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "testutils.h" +// KWin +#include "utils/xcbutils.h" +// Qt +#include +#include +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#include +#else +#include +#endif +#include +// xcb +#include +#include + +using namespace KWin; + +class TestXcbSizeHints : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void testSizeHints_data(); + void testSizeHints(); + void testSizeHintsEmpty(); + void testSizeHintsNotSet(); + void geometryHintsBeforeInit(); + void geometryHintsBeforeRead(); + +private: + Xcb::Window m_testWindow; +}; + +void TestXcbSizeHints::initTestCase() +{ + qApp->setProperty("x11RootWindow", QVariant::fromValue(QX11Info::appRootWindow())); + qApp->setProperty("x11Connection", QVariant::fromValue(QX11Info::connection())); +} + +void TestXcbSizeHints::init() +{ + const uint32_t values[] = {true}; + m_testWindow.create(QRect(0, 0, 10, 10), XCB_WINDOW_CLASS_INPUT_ONLY, XCB_CW_OVERRIDE_REDIRECT, values); + QVERIFY(m_testWindow.isValid()); +} + +void TestXcbSizeHints::cleanup() +{ + m_testWindow.reset(); +} + +void TestXcbSizeHints::testSizeHints_data() +{ + // set + QTest::addColumn("userPos"); + QTest::addColumn("userSize"); + QTest::addColumn("minSize"); + QTest::addColumn("maxSize"); + QTest::addColumn("resizeInc"); + QTest::addColumn("minAspect"); + QTest::addColumn("maxAspect"); + QTest::addColumn("baseSize"); + QTest::addColumn("gravity"); + // read for SizeHints + QTest::addColumn("expectedFlags"); + QTest::addColumn("expectedPad0"); + QTest::addColumn("expectedPad1"); + QTest::addColumn("expectedPad2"); + QTest::addColumn("expectedPad3"); + QTest::addColumn("expectedMinWidth"); + QTest::addColumn("expectedMinHeight"); + QTest::addColumn("expectedMaxWidth"); + QTest::addColumn("expectedMaxHeight"); + QTest::addColumn("expectedWidthInc"); + QTest::addColumn("expectedHeightInc"); + QTest::addColumn("expectedMinAspectNum"); + QTest::addColumn("expectedMinAspectDen"); + QTest::addColumn("expectedMaxAspectNum"); + QTest::addColumn("expectedMaxAspectDen"); + QTest::addColumn("expectedBaseWidth"); + QTest::addColumn("expectedBaseHeight"); + // read for GeometryHints + QTest::addColumn("expectedMinSize"); + QTest::addColumn("expectedMaxSize"); + QTest::addColumn("expectedResizeIncrements"); + QTest::addColumn("expectedMinAspect"); + QTest::addColumn("expectedMaxAspect"); + QTest::addColumn("expectedBaseSize"); + QTest::addColumn("expectedGravity"); + + QTest::newRow("userPos") << QPoint(1, 2) << QSize() << QSize() << QSize() << QSize() << QSize() << QSize() << QSize() << 0 + << 1 << 1 << 2 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 + << QSize(0, 0) << QSize(INT_MAX, INT_MAX) << QSize(1, 1) << QSize(1, INT_MAX) << QSize(INT_MAX, 1) << QSize(0, 0) << qint32(XCB_GRAVITY_NORTH_WEST); + QTest::newRow("userSize") << QPoint() << QSize(1, 2) << QSize() << QSize() << QSize() << QSize() << QSize() << QSize() << 0 + << 2 << 0 << 0 << 1 << 2 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 + << QSize(0, 0) << QSize(INT_MAX, INT_MAX) << QSize(1, 1) << QSize(1, INT_MAX) << QSize(INT_MAX, 1) << QSize(0, 0) << qint32(XCB_GRAVITY_NORTH_WEST); + QTest::newRow("minSize") << QPoint() << QSize() << QSize(1, 2) << QSize() << QSize() << QSize() << QSize() << QSize() << 0 + << 16 << 0 << 0 << 0 << 0 << 1 << 2 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 + << QSize(1, 2) << QSize(INT_MAX, INT_MAX) << QSize(1, 1) << QSize(1, INT_MAX) << QSize(INT_MAX, 1) << QSize(0, 0) << qint32(XCB_GRAVITY_NORTH_WEST); + QTest::newRow("maxSize") << QPoint() << QSize() << QSize() << QSize(1, 2) << QSize() << QSize() << QSize() << QSize() << 0 + << 32 << 0 << 0 << 0 << 0 << 0 << 0 << 1 << 2 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 + << QSize(0, 0) << QSize(1, 2) << QSize(1, 1) << QSize(1, INT_MAX) << QSize(INT_MAX, 1) << QSize(0, 0) << qint32(XCB_GRAVITY_NORTH_WEST); + QTest::newRow("maxSize0") << QPoint() << QSize() << QSize() << QSize(0, 0) << QSize() << QSize() << QSize() << QSize() << 0 + << 32 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 + << QSize(0, 0) << QSize(1, 1) << QSize(1, 1) << QSize(1, INT_MAX) << QSize(INT_MAX, 1) << QSize(0, 0) << qint32(XCB_GRAVITY_NORTH_WEST); + QTest::newRow("min/maxSize") << QPoint() << QSize() << QSize(1, 2) << QSize(3, 4) << QSize() << QSize() << QSize() << QSize() << 0 + << 48 << 0 << 0 << 0 << 0 << 1 << 2 << 3 << 4 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 + << QSize(1, 2) << QSize(3, 4) << QSize(1, 1) << QSize(1, INT_MAX) << QSize(INT_MAX, 1) << QSize(0, 0) << qint32(XCB_GRAVITY_NORTH_WEST); + QTest::newRow("resizeInc") << QPoint() << QSize() << QSize() << QSize() << QSize(1, 2) << QSize() << QSize() << QSize() << 0 + << 64 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 1 << 2 << 0 << 0 << 0 << 0 << 0 << 0 + << QSize(0, 0) << QSize(INT_MAX, INT_MAX) << QSize(1, 2) << QSize(1, INT_MAX) << QSize(INT_MAX, 1) << QSize(0, 0) << qint32(XCB_GRAVITY_NORTH_WEST); + QTest::newRow("resizeInc0") << QPoint() << QSize() << QSize() << QSize() << QSize(0, 0) << QSize() << QSize() << QSize() << 0 + << 64 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 + << QSize(0, 0) << QSize(INT_MAX, INT_MAX) << QSize(1, 1) << QSize(1, INT_MAX) << QSize(INT_MAX, 1) << QSize(0, 0) << qint32(XCB_GRAVITY_NORTH_WEST); + QTest::newRow("aspect") << QPoint() << QSize() << QSize() << QSize() << QSize() << QSize(1, 2) << QSize(3, 4) << QSize() << 0 + << 128 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 1 << 2 << 3 << 4 << 0 << 0 + << QSize(0, 0) << QSize(INT_MAX, INT_MAX) << QSize(1, 1) << QSize(1, 2) << QSize(3, 4) << QSize(0, 0) << qint32(XCB_GRAVITY_NORTH_WEST); + QTest::newRow("aspectDivision0") << QPoint() << QSize() << QSize() << QSize() << QSize() << QSize(1, 0) << QSize(3, 0) << QSize() << 0 + << 128 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 1 << 0 << 3 << 0 << 0 << 0 + << QSize(0, 0) << QSize(INT_MAX, INT_MAX) << QSize(1, 1) << QSize(1, 1) << QSize(3, 1) << QSize(0, 0) << qint32(XCB_GRAVITY_NORTH_WEST); + QTest::newRow("baseSize") << QPoint() << QSize() << QSize() << QSize() << QSize() << QSize() << QSize() << QSize(1, 2) << 0 + << 256 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 1 << 2 + << QSize(1, 2) << QSize(INT_MAX, INT_MAX) << QSize(1, 1) << QSize(1, INT_MAX) << QSize(INT_MAX, 1) << QSize(1, 2) << qint32(XCB_GRAVITY_NORTH_WEST); + QTest::newRow("gravity") << QPoint() << QSize() << QSize() << QSize() << QSize() << QSize() << QSize() << QSize() << qint32(XCB_GRAVITY_STATIC) + << 512 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0 + << QSize(0, 0) << QSize(INT_MAX, INT_MAX) << QSize(1, 1) << QSize(1, INT_MAX) << QSize(INT_MAX, 1) << QSize(0, 0) << qint32(XCB_GRAVITY_STATIC); + QTest::newRow("all") << QPoint(1, 2) << QSize(3, 4) << QSize(5, 6) << QSize(7, 8) << QSize(9, 10) << QSize(11, 12) << QSize(13, 14) << QSize(15, 16) << 1 + << 1011 << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10 << 11 << 12 << 13 << 14 << 15 << 16 + << QSize(5, 6) << QSize(7, 8) << QSize(9, 10) << QSize(11, 12) << QSize(13, 14) << QSize(15, 16) << qint32(XCB_GRAVITY_NORTH_WEST); +} + +void TestXcbSizeHints::testSizeHints() +{ + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + QFETCH(QPoint, userPos); + if (!userPos.isNull()) { + xcb_icccm_size_hints_set_position(&hints, 1, userPos.x(), userPos.y()); + } + QFETCH(QSize, userSize); + if (userSize.isValid()) { + xcb_icccm_size_hints_set_size(&hints, 1, userSize.width(), userSize.height()); + } + QFETCH(QSize, minSize); + if (minSize.isValid()) { + xcb_icccm_size_hints_set_min_size(&hints, minSize.width(), minSize.height()); + } + QFETCH(QSize, maxSize); + if (maxSize.isValid()) { + xcb_icccm_size_hints_set_max_size(&hints, maxSize.width(), maxSize.height()); + } + QFETCH(QSize, resizeInc); + if (resizeInc.isValid()) { + xcb_icccm_size_hints_set_resize_inc(&hints, resizeInc.width(), resizeInc.height()); + } + QFETCH(QSize, minAspect); + QFETCH(QSize, maxAspect); + if (minAspect.isValid() && maxAspect.isValid()) { + xcb_icccm_size_hints_set_aspect(&hints, minAspect.width(), minAspect.height(), maxAspect.width(), maxAspect.height()); + } + QFETCH(QSize, baseSize); + if (baseSize.isValid()) { + xcb_icccm_size_hints_set_base_size(&hints, baseSize.width(), baseSize.height()); + } + QFETCH(qint32, gravity); + if (gravity != 0) { + xcb_icccm_size_hints_set_win_gravity(&hints, (xcb_gravity_t)gravity); + } + xcb_icccm_set_wm_normal_hints(QX11Info::connection(), m_testWindow, &hints); + xcb_flush(QX11Info::connection()); + + Xcb::GeometryHints geoHints; + geoHints.init(m_testWindow); + geoHints.read(); + QCOMPARE(geoHints.hasAspect(), minAspect.isValid() && maxAspect.isValid()); + QCOMPARE(geoHints.hasBaseSize(), baseSize.isValid()); + QCOMPARE(geoHints.hasMaxSize(), maxSize.isValid()); + QCOMPARE(geoHints.hasMinSize(), minSize.isValid()); + QCOMPARE(geoHints.hasPosition(), !userPos.isNull()); + QCOMPARE(geoHints.hasResizeIncrements(), resizeInc.isValid()); + QCOMPARE(geoHints.hasSize(), userSize.isValid()); + QCOMPARE(geoHints.hasWindowGravity(), gravity != 0); + QTEST(geoHints.baseSize().toSize(), "expectedBaseSize"); + QTEST(geoHints.maxAspect(), "expectedMaxAspect"); + QTEST(geoHints.maxSize().toSize(), "expectedMaxSize"); + QTEST(geoHints.minAspect(), "expectedMinAspect"); + QTEST(geoHints.minSize().toSize(), "expectedMinSize"); + QTEST(geoHints.resizeIncrements().toSize(), "expectedResizeIncrements"); + QTEST(qint32(geoHints.windowGravity()), "expectedGravity"); + + auto sizeHints = geoHints.m_sizeHints; + QVERIFY(sizeHints); + QTEST(sizeHints->flags, "expectedFlags"); + QTEST(sizeHints->pad[0], "expectedPad0"); + QTEST(sizeHints->pad[1], "expectedPad1"); + QTEST(sizeHints->pad[2], "expectedPad2"); + QTEST(sizeHints->pad[3], "expectedPad3"); + QTEST(sizeHints->minWidth, "expectedMinWidth"); + QTEST(sizeHints->minHeight, "expectedMinHeight"); + QTEST(sizeHints->maxWidth, "expectedMaxWidth"); + QTEST(sizeHints->maxHeight, "expectedMaxHeight"); + QTEST(sizeHints->widthInc, "expectedWidthInc"); + QTEST(sizeHints->heightInc, "expectedHeightInc"); + QTEST(sizeHints->minAspect[0], "expectedMinAspectNum"); + QTEST(sizeHints->minAspect[1], "expectedMinAspectDen"); + QTEST(sizeHints->maxAspect[0], "expectedMaxAspectNum"); + QTEST(sizeHints->maxAspect[1], "expectedMaxAspectDen"); + QTEST(sizeHints->baseWidth, "expectedBaseWidth"); + QTEST(sizeHints->baseHeight, "expectedBaseHeight"); + QCOMPARE(sizeHints->winGravity, gravity); + + // copy + Xcb::GeometryHints::NormalHints::SizeHints sizeHints2 = *sizeHints; + QTEST(sizeHints2.flags, "expectedFlags"); + QTEST(sizeHints2.pad[0], "expectedPad0"); + QTEST(sizeHints2.pad[1], "expectedPad1"); + QTEST(sizeHints2.pad[2], "expectedPad2"); + QTEST(sizeHints2.pad[3], "expectedPad3"); + QTEST(sizeHints2.minWidth, "expectedMinWidth"); + QTEST(sizeHints2.minHeight, "expectedMinHeight"); + QTEST(sizeHints2.maxWidth, "expectedMaxWidth"); + QTEST(sizeHints2.maxHeight, "expectedMaxHeight"); + QTEST(sizeHints2.widthInc, "expectedWidthInc"); + QTEST(sizeHints2.heightInc, "expectedHeightInc"); + QTEST(sizeHints2.minAspect[0], "expectedMinAspectNum"); + QTEST(sizeHints2.minAspect[1], "expectedMinAspectDen"); + QTEST(sizeHints2.maxAspect[0], "expectedMaxAspectNum"); + QTEST(sizeHints2.maxAspect[1], "expectedMaxAspectDen"); + QTEST(sizeHints2.baseWidth, "expectedBaseWidth"); + QTEST(sizeHints2.baseHeight, "expectedBaseHeight"); + QCOMPARE(sizeHints2.winGravity, gravity); +} + +void TestXcbSizeHints::testSizeHintsEmpty() +{ + xcb_size_hints_t xcbHints; + memset(&xcbHints, 0, sizeof(xcbHints)); + xcb_icccm_set_wm_normal_hints(QX11Info::connection(), m_testWindow, &xcbHints); + xcb_flush(QX11Info::connection()); + + Xcb::GeometryHints hints; + hints.init(m_testWindow); + hints.read(); + QVERIFY(!hints.hasAspect()); + QVERIFY(!hints.hasBaseSize()); + QVERIFY(!hints.hasMaxSize()); + QVERIFY(!hints.hasMinSize()); + QVERIFY(!hints.hasPosition()); + QVERIFY(!hints.hasResizeIncrements()); + QVERIFY(!hints.hasSize()); + QVERIFY(!hints.hasWindowGravity()); + + QCOMPARE(hints.baseSize(), QSize(0, 0)); + QCOMPARE(hints.maxAspect(), QSize(INT_MAX, 1)); + QCOMPARE(hints.maxSize(), QSize(INT_MAX, INT_MAX)); + QCOMPARE(hints.minAspect(), QSize(1, INT_MAX)); + QCOMPARE(hints.minSize(), QSize(0, 0)); + QCOMPARE(hints.resizeIncrements(), QSize(1, 1)); + QCOMPARE(hints.windowGravity(), XCB_GRAVITY_NORTH_WEST); + + auto sizeHints = hints.m_sizeHints; + QVERIFY(sizeHints); + QCOMPARE(sizeHints->flags, 0); + QCOMPARE(sizeHints->pad[0], 0); + QCOMPARE(sizeHints->pad[1], 0); + QCOMPARE(sizeHints->pad[2], 0); + QCOMPARE(sizeHints->pad[3], 0); + QCOMPARE(sizeHints->minWidth, 0); + QCOMPARE(sizeHints->minHeight, 0); + QCOMPARE(sizeHints->maxWidth, 0); + QCOMPARE(sizeHints->maxHeight, 0); + QCOMPARE(sizeHints->widthInc, 0); + QCOMPARE(sizeHints->heightInc, 0); + QCOMPARE(sizeHints->minAspect[0], 0); + QCOMPARE(sizeHints->minAspect[1], 0); + QCOMPARE(sizeHints->maxAspect[0], 0); + QCOMPARE(sizeHints->maxAspect[1], 0); + QCOMPARE(sizeHints->baseWidth, 0); + QCOMPARE(sizeHints->baseHeight, 0); + QCOMPARE(sizeHints->winGravity, 0); +} + +void TestXcbSizeHints::testSizeHintsNotSet() +{ + Xcb::GeometryHints hints; + hints.init(m_testWindow); + hints.read(); + QVERIFY(!hints.m_sizeHints); + QVERIFY(!hints.hasAspect()); + QVERIFY(!hints.hasBaseSize()); + QVERIFY(!hints.hasMaxSize()); + QVERIFY(!hints.hasMinSize()); + QVERIFY(!hints.hasPosition()); + QVERIFY(!hints.hasResizeIncrements()); + QVERIFY(!hints.hasSize()); + QVERIFY(!hints.hasWindowGravity()); + + QCOMPARE(hints.baseSize(), QSize(0, 0)); + QCOMPARE(hints.maxAspect(), QSize(INT_MAX, 1)); + QCOMPARE(hints.maxSize(), QSize(INT_MAX, INT_MAX)); + QCOMPARE(hints.minAspect(), QSize(1, INT_MAX)); + QCOMPARE(hints.minSize(), QSize(0, 0)); + QCOMPARE(hints.resizeIncrements(), QSize(1, 1)); + QCOMPARE(hints.windowGravity(), XCB_GRAVITY_NORTH_WEST); +} + +void TestXcbSizeHints::geometryHintsBeforeInit() +{ + Xcb::GeometryHints hints; + QVERIFY(!hints.hasAspect()); + QVERIFY(!hints.hasBaseSize()); + QVERIFY(!hints.hasMaxSize()); + QVERIFY(!hints.hasMinSize()); + QVERIFY(!hints.hasPosition()); + QVERIFY(!hints.hasResizeIncrements()); + QVERIFY(!hints.hasSize()); + QVERIFY(!hints.hasWindowGravity()); + + QCOMPARE(hints.baseSize(), QSize(0, 0)); + QCOMPARE(hints.maxAspect(), QSize(INT_MAX, 1)); + QCOMPARE(hints.maxSize(), QSize(INT_MAX, INT_MAX)); + QCOMPARE(hints.minAspect(), QSize(1, INT_MAX)); + QCOMPARE(hints.minSize(), QSize(0, 0)); + QCOMPARE(hints.resizeIncrements(), QSize(1, 1)); + QCOMPARE(hints.windowGravity(), XCB_GRAVITY_NORTH_WEST); +} + +void TestXcbSizeHints::geometryHintsBeforeRead() +{ + xcb_size_hints_t xcbHints; + memset(&xcbHints, 0, sizeof(xcbHints)); + xcb_icccm_size_hints_set_position(&xcbHints, 1, 1, 2); + xcb_icccm_set_wm_normal_hints(QX11Info::connection(), m_testWindow, &xcbHints); + xcb_flush(QX11Info::connection()); + + Xcb::GeometryHints hints; + hints.init(m_testWindow); + QVERIFY(!hints.hasAspect()); + QVERIFY(!hints.hasBaseSize()); + QVERIFY(!hints.hasMaxSize()); + QVERIFY(!hints.hasMinSize()); + QVERIFY(!hints.hasPosition()); + QVERIFY(!hints.hasResizeIncrements()); + QVERIFY(!hints.hasSize()); + QVERIFY(!hints.hasWindowGravity()); + + QCOMPARE(hints.baseSize(), QSize(0, 0)); + QCOMPARE(hints.maxAspect(), QSize(INT_MAX, 1)); + QCOMPARE(hints.maxSize(), QSize(INT_MAX, INT_MAX)); + QCOMPARE(hints.minAspect(), QSize(1, INT_MAX)); + QCOMPARE(hints.minSize(), QSize(0, 0)); + QCOMPARE(hints.resizeIncrements(), QSize(1, 1)); + QCOMPARE(hints.windowGravity(), XCB_GRAVITY_NORTH_WEST); +} + +Q_CONSTRUCTOR_FUNCTION(forceXcb) +QTEST_MAIN(TestXcbSizeHints) +#include "test_xcb_size_hints.moc" diff --git a/autotests/test_xcb_window.cpp b/autotests/test_xcb_window.cpp new file mode 100644 index 0000000..173f15b --- /dev/null +++ b/autotests/test_xcb_window.cpp @@ -0,0 +1,206 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2013 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "testutils.h" +// KWin +#include "utils/xcbutils.h" +// Qt +#include +#include +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#include +#else +#include +#endif +// xcb +#include + +using namespace KWin; + +class TestXcbWindow : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void defaultCtor(); + void ctor(); + void classCtor(); + void create(); + void mapUnmap(); + void geometry(); + void destroy(); + void destroyNotManaged(); +}; + +void TestXcbWindow::initTestCase() +{ + qApp->setProperty("x11RootWindow", QVariant::fromValue(QX11Info::appRootWindow())); + qApp->setProperty("x11Connection", QVariant::fromValue(QX11Info::connection())); +} + +void TestXcbWindow::defaultCtor() +{ + Xcb::Window window; + QCOMPARE(window.isValid(), false); + xcb_window_t wId = window; + QCOMPARE(wId, noneWindow()); + + xcb_window_t nativeWindow = createWindow(); + Xcb::Window window2(nativeWindow); + QCOMPARE(window2.isValid(), true); + wId = window2; + QCOMPARE(wId, nativeWindow); +} + +void TestXcbWindow::ctor() +{ + const QRect geometry(0, 0, 10, 10); + const uint32_t values[] = {true}; + Xcb::Window window(geometry, XCB_CW_OVERRIDE_REDIRECT, values); + QCOMPARE(window.isValid(), true); + QVERIFY(window != XCB_WINDOW_NONE); + Xcb::WindowGeometry windowGeometry(window); + QCOMPARE(windowGeometry.isNull(), false); + QCOMPARE(windowGeometry.rect(), geometry); +} + +void TestXcbWindow::classCtor() +{ + const QRect geometry(0, 0, 10, 10); + const uint32_t values[] = {true}; + Xcb::Window window(geometry, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_CW_OVERRIDE_REDIRECT, values); + QCOMPARE(window.isValid(), true); + QVERIFY(window != XCB_WINDOW_NONE); + Xcb::WindowGeometry windowGeometry(window); + QCOMPARE(windowGeometry.isNull(), false); + QCOMPARE(windowGeometry.rect(), geometry); + + Xcb::WindowAttributes attribs(window); + QCOMPARE(attribs.isNull(), false); + QVERIFY(attribs->_class == XCB_WINDOW_CLASS_INPUT_ONLY); +} + +void TestXcbWindow::create() +{ + Xcb::Window window; + QCOMPARE(window.isValid(), false); + xcb_window_t wId = window; + QCOMPARE(wId, noneWindow()); + + const QRect geometry(0, 0, 10, 10); + const uint32_t values[] = {true}; + window.create(geometry, XCB_CW_OVERRIDE_REDIRECT, values); + QCOMPARE(window.isValid(), true); + QVERIFY(window != XCB_WINDOW_NONE); + // and reset again + window.reset(); + QCOMPARE(window.isValid(), false); + QVERIFY(window == XCB_WINDOW_NONE); +} + +void TestXcbWindow::mapUnmap() +{ + const QRect geometry(0, 0, 10, 10); + const uint32_t values[] = {true}; + Xcb::Window window(geometry, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_CW_OVERRIDE_REDIRECT, values); + Xcb::WindowAttributes attribs(window); + QCOMPARE(attribs.isNull(), false); + QVERIFY(attribs->map_state == XCB_MAP_STATE_UNMAPPED); + + window.map(); + Xcb::WindowAttributes attribs2(window); + QCOMPARE(attribs2.isNull(), false); + QVERIFY(attribs2->map_state != XCB_MAP_STATE_UNMAPPED); + + window.unmap(); + Xcb::WindowAttributes attribs3(window); + QCOMPARE(attribs3.isNull(), false); + QVERIFY(attribs3->map_state == XCB_MAP_STATE_UNMAPPED); + + // map, unmap shouldn't fail for an invalid window, it's just ignored + window.reset(); + window.map(); + window.unmap(); +} + +void TestXcbWindow::geometry() +{ + const QRect geometry(0, 0, 10, 10); + const uint32_t values[] = {true}; + Xcb::Window window(geometry, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_CW_OVERRIDE_REDIRECT, values); + Xcb::WindowGeometry windowGeometry(window); + QCOMPARE(windowGeometry.isNull(), false); + QCOMPARE(windowGeometry.rect(), geometry); + + const QRect geometry2(10, 20, 100, 200); + window.setGeometry(geometry2); + Xcb::WindowGeometry windowGeometry2(window); + QCOMPARE(windowGeometry2.isNull(), false); + QCOMPARE(windowGeometry2.rect(), geometry2); + + // setting a geometry on an invalid window should be ignored + window.reset(); + window.setGeometry(geometry2); + Xcb::WindowGeometry windowGeometry3(window); + QCOMPARE(windowGeometry3.isNull(), true); +} + +void TestXcbWindow::destroy() +{ + const QRect geometry(0, 0, 10, 10); + const uint32_t values[] = {true}; + Xcb::Window window(geometry, XCB_CW_OVERRIDE_REDIRECT, values); + QCOMPARE(window.isValid(), true); + xcb_window_t wId = window; + + window.create(geometry, XCB_CW_OVERRIDE_REDIRECT, values); + // wId should now be invalid + xcb_generic_error_t *error = nullptr; + UniqueCPtr attribs(xcb_get_window_attributes_reply( + connection(), + xcb_get_window_attributes(connection(), wId), + &error)); + QVERIFY(!attribs); + QCOMPARE(error->error_code, uint8_t(3)); + QCOMPARE(error->resource_id, wId); + free(error); + + // test the same for the dtor + { + Xcb::Window scopedWindow(geometry, XCB_CW_OVERRIDE_REDIRECT, values); + QVERIFY(scopedWindow.isValid()); + wId = scopedWindow; + } + error = nullptr; + UniqueCPtr attribs2(xcb_get_window_attributes_reply( + connection(), + xcb_get_window_attributes(connection(), wId), + &error)); + QVERIFY(!attribs2); + QCOMPARE(error->error_code, uint8_t(3)); + QCOMPARE(error->resource_id, wId); + free(error); +} + +void TestXcbWindow::destroyNotManaged() +{ + Xcb::Window window; + // just destroy the non-existing window + window.reset(); + + // now let's add a window + window.reset(createWindow(), false); + xcb_window_t w = window; + window.reset(); + Xcb::WindowAttributes attribs(w); + QVERIFY(attribs); +} + +Q_CONSTRUCTOR_FUNCTION(forceXcb) +QTEST_MAIN(TestXcbWindow) +#include "test_xcb_window.moc" diff --git a/autotests/test_xcb_wrapper.cpp b/autotests/test_xcb_wrapper.cpp new file mode 100644 index 0000000..6eaa410 --- /dev/null +++ b/autotests/test_xcb_wrapper.cpp @@ -0,0 +1,534 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2013 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "testutils.h" +// KWin +#include "utils/xcbutils.h" +// Qt +#include +#include +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#include +#else +#include +#endif +#include +// xcb +#include + +using namespace KWin; + +class TestXcbWrapper : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void initTestCase(); + void init(); + void cleanup(); + void defaultCtor(); + void normalCtor(); + void copyCtorEmpty(); + void copyCtorBeforeRetrieve(); + void copyCtorAfterRetrieve(); + void assignementEmpty(); + void assignmentBeforeRetrieve(); + void assignmentAfterRetrieve(); + void discard(); + void testQueryTree(); + void testCurrentInput(); + void testTransientFor(); + void testPropertyByteArray(); + void testPropertyBool(); + void testAtom(); + void testMotifEmpty(); + void testMotif_data(); + void testMotif(); + +private: + void testEmpty(Xcb::WindowGeometry &geometry); + void testGeometry(Xcb::WindowGeometry &geometry, const QRect &rect); + Xcb::Window m_testWindow; +}; + +void TestXcbWrapper::initTestCase() +{ + qApp->setProperty("x11RootWindow", QVariant::fromValue(QX11Info::appRootWindow())); + qApp->setProperty("x11Connection", QVariant::fromValue(QX11Info::connection())); +} + +void TestXcbWrapper::init() +{ + const uint32_t values[] = {true}; + m_testWindow.create(QRect(0, 0, 10, 10), XCB_WINDOW_CLASS_INPUT_ONLY, XCB_CW_OVERRIDE_REDIRECT, values); + QVERIFY(m_testWindow.isValid()); +} + +void TestXcbWrapper::cleanup() +{ + m_testWindow.reset(); +} + +void TestXcbWrapper::testEmpty(Xcb::WindowGeometry &geometry) +{ + QCOMPARE(geometry.window(), KWin::noneWindow()); + QVERIFY(!geometry.data()); + QCOMPARE(geometry.isNull(), true); + QCOMPARE(geometry.rect(), QRect()); + QVERIFY(!geometry); +} + +void TestXcbWrapper::testGeometry(Xcb::WindowGeometry &geometry, const QRect &rect) +{ + QCOMPARE(geometry.window(), (xcb_window_t)m_testWindow); + // now lets retrieve some data + QCOMPARE(geometry.rect(), rect); + QVERIFY(geometry.isRetrieved()); + QCOMPARE(geometry.isNull(), false); + QVERIFY(geometry); + QVERIFY(geometry.data()); + QCOMPARE(geometry.data()->x, int16_t(rect.x())); + QCOMPARE(geometry.data()->y, int16_t(rect.y())); + QCOMPARE(geometry.data()->width, uint16_t(rect.width())); + QCOMPARE(geometry.data()->height, uint16_t(rect.height())); +} + +void TestXcbWrapper::defaultCtor() +{ + Xcb::WindowGeometry geometry; + testEmpty(geometry); + QVERIFY(!geometry.isRetrieved()); +} + +void TestXcbWrapper::normalCtor() +{ + Xcb::WindowGeometry geometry(m_testWindow); + QVERIFY(!geometry.isRetrieved()); + testGeometry(geometry, QRect(0, 0, 10, 10)); +} + +void TestXcbWrapper::copyCtorEmpty() +{ + Xcb::WindowGeometry geometry; + Xcb::WindowGeometry other(geometry); + testEmpty(geometry); + QVERIFY(geometry.isRetrieved()); + testEmpty(other); + QVERIFY(!other.isRetrieved()); +} + +void TestXcbWrapper::copyCtorBeforeRetrieve() +{ + Xcb::WindowGeometry geometry(m_testWindow); + QVERIFY(!geometry.isRetrieved()); + Xcb::WindowGeometry other(geometry); + testEmpty(geometry); + QVERIFY(geometry.isRetrieved()); + + QVERIFY(!other.isRetrieved()); + testGeometry(other, QRect(0, 0, 10, 10)); +} + +void TestXcbWrapper::copyCtorAfterRetrieve() +{ + Xcb::WindowGeometry geometry(m_testWindow); + QVERIFY(geometry); + QVERIFY(geometry.isRetrieved()); + QCOMPARE(geometry.rect(), QRect(0, 0, 10, 10)); + Xcb::WindowGeometry other(geometry); + testEmpty(geometry); + QVERIFY(geometry.isRetrieved()); + + QVERIFY(other.isRetrieved()); + testGeometry(other, QRect(0, 0, 10, 10)); +} + +void TestXcbWrapper::assignementEmpty() +{ + Xcb::WindowGeometry geometry; + Xcb::WindowGeometry other; + testEmpty(geometry); + testEmpty(other); + + other = geometry; + QVERIFY(geometry.isRetrieved()); + testEmpty(geometry); + testEmpty(other); + QVERIFY(!other.isRetrieved()); + + QT_WARNING_PUSH + QT_WARNING_DISABLE_CLANG("-Wself-assign-overloaded") + // test assignment to self + geometry = geometry; + other = other; + testEmpty(geometry); + testEmpty(other); + QT_WARNING_POP +} + +void TestXcbWrapper::assignmentBeforeRetrieve() +{ + Xcb::WindowGeometry geometry(m_testWindow); + Xcb::WindowGeometry other = geometry; + QVERIFY(geometry.isRetrieved()); + testEmpty(geometry); + + QVERIFY(!other.isRetrieved()); + testGeometry(other, QRect(0, 0, 10, 10)); + + other = Xcb::WindowGeometry(m_testWindow); + QVERIFY(!other.isRetrieved()); + QCOMPARE(other.window(), (xcb_window_t)m_testWindow); + other = Xcb::WindowGeometry(); + testEmpty(geometry); + + QT_WARNING_PUSH + QT_WARNING_DISABLE_CLANG("-Wself-assign-overloaded") + // test assignment to self + geometry = geometry; + other = other; + testEmpty(geometry); + QT_WARNING_POP +} + +void TestXcbWrapper::assignmentAfterRetrieve() +{ + Xcb::WindowGeometry geometry(m_testWindow); + QVERIFY(geometry); + QVERIFY(geometry.isRetrieved()); + Xcb::WindowGeometry other = geometry; + testEmpty(geometry); + + QVERIFY(other.isRetrieved()); + testGeometry(other, QRect(0, 0, 10, 10)); + + QT_WARNING_PUSH + QT_WARNING_DISABLE_CLANG("-Wself-assign-overloaded") + // test assignment to self + geometry = geometry; + other = other; + testEmpty(geometry); + testGeometry(other, QRect(0, 0, 10, 10)); + QT_WARNING_POP + + // set to empty again + other = Xcb::WindowGeometry(); + testEmpty(other); +} + +void TestXcbWrapper::discard() +{ + // discard of reply cannot be tested properly as we cannot check whether the reply has been discarded + // therefore it's more or less just a test to ensure that it doesn't crash and the code paths + // are taken. + Xcb::WindowGeometry *geometry = new Xcb::WindowGeometry(); + delete geometry; + + geometry = new Xcb::WindowGeometry(m_testWindow); + delete geometry; + + geometry = new Xcb::WindowGeometry(m_testWindow); + QVERIFY(geometry->data()); + delete geometry; +} + +void TestXcbWrapper::testQueryTree() +{ + Xcb::Tree tree(m_testWindow); + // should have root as parent + QCOMPARE(tree.parent(), static_cast(QX11Info::appRootWindow())); + // shouldn't have any children + QCOMPARE(tree->children_len, uint16_t(0)); + QVERIFY(!tree.children()); + + // query for root + Xcb::Tree root(QX11Info::appRootWindow()); + // shouldn't have a parent + QCOMPARE(root.parent(), xcb_window_t(XCB_WINDOW_NONE)); + QVERIFY(root->children_len > 0); + xcb_window_t *children = root.children(); + bool found = false; + for (int i = 0; i < xcb_query_tree_children_length(root.data()); ++i) { + if (children[i] == tree.window()) { + found = true; + break; + } + } + QVERIFY(found); + + // query for not existing window + Xcb::Tree doesntExist(XCB_WINDOW_NONE); + QCOMPARE(doesntExist.parent(), xcb_window_t(XCB_WINDOW_NONE)); + QVERIFY(doesntExist.isNull()); + QVERIFY(doesntExist.isRetrieved()); +} + +void TestXcbWrapper::testCurrentInput() +{ + xcb_connection_t *c = QX11Info::connection(); + m_testWindow.map(); + QX11Info::setAppTime(QX11Info::getTimestamp()); + + // let's set the input focus + m_testWindow.focus(XCB_INPUT_FOCUS_PARENT, QX11Info::appTime()); + xcb_flush(c); + + Xcb::CurrentInput input; + QCOMPARE(input.window(), (xcb_window_t)m_testWindow); + + // creating a copy should make the input object have no window any more + Xcb::CurrentInput input2(input); + QCOMPARE(input2.window(), (xcb_window_t)m_testWindow); + QCOMPARE(input.window(), xcb_window_t(XCB_WINDOW_NONE)); +} + +void TestXcbWrapper::testTransientFor() +{ + Xcb::TransientFor transient(m_testWindow); + QCOMPARE(transient.window(), (xcb_window_t)m_testWindow); + // our m_testWindow doesn't have a transient for hint + xcb_window_t compareWindow = XCB_WINDOW_NONE; + QVERIFY(!transient.getTransientFor(&compareWindow)); + QCOMPARE(compareWindow, xcb_window_t(XCB_WINDOW_NONE)); + bool ok = true; + QCOMPARE(transient.value(32, XCB_ATOM_WINDOW, XCB_WINDOW_NONE, &ok), xcb_window_t(XCB_WINDOW_NONE)); + QVERIFY(!ok); + ok = true; + QCOMPARE(transient.value(XCB_WINDOW_NONE, &ok), xcb_window_t(XCB_WINDOW_NONE)); + QVERIFY(!ok); + + // Create a Window with a transient for hint + Xcb::Window transientWindow(KWin::createWindow()); + xcb_window_t testWindowId = m_testWindow; + transientWindow.changeProperty(XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 32, 1, &testWindowId); + + // let's get another transient object + Xcb::TransientFor realTransient(transientWindow); + QVERIFY(realTransient.getTransientFor(&compareWindow)); + QCOMPARE(compareWindow, (xcb_window_t)m_testWindow); + ok = false; + QCOMPARE(realTransient.value(32, XCB_ATOM_WINDOW, XCB_WINDOW_NONE, &ok), (xcb_window_t)m_testWindow); + QVERIFY(ok); + ok = false; + QCOMPARE(realTransient.value(XCB_WINDOW_NONE, &ok), (xcb_window_t)m_testWindow); + QVERIFY(ok); + ok = false; + QCOMPARE(realTransient.value(), (xcb_window_t)m_testWindow); + QCOMPARE(realTransient.value(nullptr, &ok)[0], (xcb_window_t)m_testWindow); + QVERIFY(ok); + QCOMPARE(realTransient.value()[0], (xcb_window_t)m_testWindow); + + // test for a not existing window + Xcb::TransientFor doesntExist(XCB_WINDOW_NONE); + QVERIFY(!doesntExist.getTransientFor(&compareWindow)); +} + +void TestXcbWrapper::testPropertyByteArray() +{ + Xcb::Window testWindow(KWin::createWindow()); + Xcb::Property prop(false, testWindow, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 100000); + QCOMPARE(prop.toByteArray(), QByteArray()); + bool ok = true; + QCOMPARE(prop.toByteArray(&ok), QByteArray()); + QVERIFY(!ok); + ok = true; + QVERIFY(!prop.value()); + QCOMPARE(prop.value("bar", &ok), "bar"); + QVERIFY(!ok); + QCOMPARE(QByteArray(Xcb::StringProperty(testWindow, XCB_ATOM_WM_NAME)), QByteArray()); + + testWindow.changeProperty(XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, 3, "foo"); + prop = Xcb::Property(false, testWindow, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 100000); + QCOMPARE(prop.toByteArray(), QByteArrayLiteral("foo")); + QCOMPARE(prop.toByteArray(&ok), QByteArrayLiteral("foo")); + QVERIFY(ok); + QCOMPARE(prop.value(nullptr, &ok), "foo"); + QVERIFY(ok); + QCOMPARE(QByteArray(Xcb::StringProperty(testWindow, XCB_ATOM_WM_NAME)), QByteArrayLiteral("foo")); + + // verify incorrect format and type + QCOMPARE(prop.toByteArray(32), QByteArray()); + QCOMPARE(prop.toByteArray(8, XCB_ATOM_CARDINAL), QByteArray()); + + // verify empty property + testWindow.changeProperty(XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, 0, nullptr); + prop = Xcb::Property(false, testWindow, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 100000); + QCOMPARE(prop.toByteArray(), QByteArray()); + QCOMPARE(prop.toByteArray(&ok), QByteArray()); + // valid bytearray + QVERIFY(ok); + // The bytearray should be empty + QVERIFY(prop.toByteArray().isEmpty()); + // The bytearray should be not null + QVERIFY(!prop.toByteArray().isNull()); + QVERIFY(!prop.value()); + QCOMPARE(QByteArray(Xcb::StringProperty(testWindow, XCB_ATOM_WM_NAME)), QByteArray()); + + // verify non existing property + Xcb::Atom invalid(QByteArrayLiteral("INVALID_ATOM")); + prop = Xcb::Property(false, testWindow, invalid, XCB_ATOM_STRING, 0, 100000); + QCOMPARE(prop.toByteArray(), QByteArray()); + QCOMPARE(prop.toByteArray(&ok), QByteArray()); + // invalid bytearray + QVERIFY(!ok); + // The bytearray should be empty + QVERIFY(prop.toByteArray().isEmpty()); + // The bytearray should be not null + QVERIFY(prop.toByteArray().isNull()); + QVERIFY(!prop.value()); + QCOMPARE(QByteArray(Xcb::StringProperty(testWindow, XCB_ATOM_WM_NAME)), QByteArray()); +} + +void TestXcbWrapper::testPropertyBool() +{ + Xcb::Window testWindow(KWin::createWindow()); + Xcb::Atom blockCompositing(QByteArrayLiteral("_KDE_NET_WM_BLOCK_COMPOSITING")); + QVERIFY(blockCompositing != XCB_ATOM_NONE); + NETWinInfo info(QX11Info::connection(), testWindow, QX11Info::appRootWindow(), NET::Properties(), NET::WM2BlockCompositing); + + Xcb::Property prop(false, testWindow, blockCompositing, XCB_ATOM_CARDINAL, 0, 100000); + bool ok = true; + QVERIFY(!prop.toBool()); + QVERIFY(!prop.toBool(&ok)); + QVERIFY(!ok); + + info.setBlockingCompositing(true); + xcb_flush(QX11Info::connection()); + prop = Xcb::Property(false, testWindow, blockCompositing, XCB_ATOM_CARDINAL, 0, 100000); + QVERIFY(prop.toBool()); + QVERIFY(prop.toBool(&ok)); + QVERIFY(ok); + + // incorrect type and format + QVERIFY(!prop.toBool(8)); + QVERIFY(!prop.toBool(32, blockCompositing)); + QVERIFY(!prop.toBool(32, blockCompositing, &ok)); + QVERIFY(!ok); + + // incorrect value: + uint32_t d[] = {1, 0}; + testWindow.changeProperty(blockCompositing, XCB_ATOM_CARDINAL, 32, 2, d); + prop = Xcb::Property(false, testWindow, blockCompositing, XCB_ATOM_CARDINAL, 0, 100000); + QVERIFY(!prop.toBool()); + ok = true; + QVERIFY(!prop.toBool(&ok)); + QVERIFY(!ok); +} + +void TestXcbWrapper::testAtom() +{ + Xcb::Atom atom(QByteArrayLiteral("WM_CLIENT_MACHINE")); + QCOMPARE(atom.name(), QByteArrayLiteral("WM_CLIENT_MACHINE")); + QVERIFY(atom == XCB_ATOM_WM_CLIENT_MACHINE); + QVERIFY(atom.isValid()); + + // test the const paths + const Xcb::Atom &atom2(atom); + QVERIFY(atom2.isValid()); + QVERIFY(atom2 == XCB_ATOM_WM_CLIENT_MACHINE); + QCOMPARE(atom2.name(), QByteArrayLiteral("WM_CLIENT_MACHINE")); + + // destroy before retrieved + Xcb::Atom atom3(QByteArrayLiteral("WM_CLIENT_MACHINE")); + QCOMPARE(atom3.name(), QByteArrayLiteral("WM_CLIENT_MACHINE")); +} + +void TestXcbWrapper::testMotifEmpty() +{ + Xcb::Atom atom(QByteArrayLiteral("_MOTIF_WM_HINTS")); + Xcb::MotifHints hints(atom); + // pre init + QCOMPARE(hints.hasDecoration(), false); + QCOMPARE(hints.noBorder(), false); + QCOMPARE(hints.resize(), true); + QCOMPARE(hints.move(), true); + QCOMPARE(hints.minimize(), true); + QCOMPARE(hints.maximize(), true); + QCOMPARE(hints.close(), true); + // post init, pre read + hints.init(m_testWindow); + QCOMPARE(hints.hasDecoration(), false); + QCOMPARE(hints.noBorder(), false); + QCOMPARE(hints.resize(), true); + QCOMPARE(hints.move(), true); + QCOMPARE(hints.minimize(), true); + QCOMPARE(hints.maximize(), true); + QCOMPARE(hints.close(), true); + // post read + hints.read(); + QCOMPARE(hints.hasDecoration(), false); + QCOMPARE(hints.noBorder(), false); + QCOMPARE(hints.resize(), true); + QCOMPARE(hints.move(), true); + QCOMPARE(hints.minimize(), true); + QCOMPARE(hints.maximize(), true); + QCOMPARE(hints.close(), true); +} + +void TestXcbWrapper::testMotif_data() +{ + QTest::addColumn("flags"); + QTest::addColumn("functions"); + QTest::addColumn("decorations"); + + QTest::addColumn("expectedHasDecoration"); + QTest::addColumn("expectedNoBorder"); + QTest::addColumn("expectedResize"); + QTest::addColumn("expectedMove"); + QTest::addColumn("expectedMinimize"); + QTest::addColumn("expectedMaximize"); + QTest::addColumn("expectedClose"); + + QTest::newRow("none") << 0u << 0u << 0u << false << false << true << true << true << true << true; + QTest::newRow("noborder") << 2u << 5u << 0u << true << true << true << true << true << true << true; + QTest::newRow("border") << 2u << 5u << 1u << true << false << true << true << true << true << true; + QTest::newRow("resize") << 1u << 2u << 1u << false << false << true << false << false << false << false; + QTest::newRow("move") << 1u << 4u << 1u << false << false << false << true << false << false << false; + QTest::newRow("minimize") << 1u << 8u << 1u << false << false << false << false << true << false << false; + QTest::newRow("maximize") << 1u << 16u << 1u << false << false << false << false << false << true << false; + QTest::newRow("close") << 1u << 32u << 1u << false << false << false << false << false << false << true; + + QTest::newRow("resize/all") << 1u << 3u << 1u << false << false << false << true << true << true << true; + QTest::newRow("move/all") << 1u << 5u << 1u << false << false << true << false << true << true << true; + QTest::newRow("minimize/all") << 1u << 9u << 1u << false << false << true << true << false << true << true; + QTest::newRow("maximize/all") << 1u << 17u << 1u << false << false << true << true << true << false << true; + QTest::newRow("close/all") << 1u << 33u << 1u << false << false << true << true << true << true << false; + + QTest::newRow("all") << 1u << 62u << 1u << false << false << true << true << true << true << true; + QTest::newRow("all/all") << 1u << 63u << 1u << false << false << false << false << false << false << false; + QTest::newRow("all/all/deco") << 3u << 63u << 1u << true << false << false << false << false << false << false; +} + +void TestXcbWrapper::testMotif() +{ + Xcb::Atom atom(QByteArrayLiteral("_MOTIF_WM_HINTS")); + QFETCH(quint32, flags); + QFETCH(quint32, functions); + QFETCH(quint32, decorations); + quint32 data[] = { + flags, + functions, + decorations, + 0, + 0}; + xcb_change_property(QX11Info::connection(), XCB_PROP_MODE_REPLACE, m_testWindow, atom, atom, 32, 5, data); + xcb_flush(QX11Info::connection()); + Xcb::MotifHints hints(atom); + hints.init(m_testWindow); + hints.read(); + QTEST(hints.hasDecoration(), "expectedHasDecoration"); + QTEST(hints.noBorder(), "expectedNoBorder"); + QTEST(hints.resize(), "expectedResize"); + QTEST(hints.move(), "expectedMove"); + QTEST(hints.minimize(), "expectedMinimize"); + QTEST(hints.maximize(), "expectedMaximize"); + QTEST(hints.close(), "expectedClose"); +} + +Q_CONSTRUCTOR_FUNCTION(forceXcb) +QTEST_MAIN(TestXcbWrapper) +#include "test_xcb_wrapper.moc" diff --git a/autotests/test_xkb.cpp b/autotests/test_xkb.cpp new file mode 100644 index 0000000..043788b --- /dev/null +++ b/autotests/test_xkb.cpp @@ -0,0 +1,482 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2017 Martin Flöser + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#include "xkb.h" + +#include +#include + +using namespace KWin; + +class XkbTest : public QObject +{ + Q_OBJECT +private Q_SLOTS: + void testToQtKey_data(); + void testToQtKey(); +}; + +// from kwindowsystem/src/platforms/xcb/kkeyserver.cpp +// adjusted to xkb +struct TransKey +{ + Qt::Key keySymQt; + xkb_keysym_t keySymX; + Qt::KeyboardModifiers modifiers; +}; + +static const TransKey g_rgQtToSymX[] = { + {Qt::Key_Escape, XKB_KEY_Escape, Qt::KeyboardModifiers()}, + {Qt::Key_Tab, XKB_KEY_Tab, Qt::KeyboardModifiers()}, + {Qt::Key_Backtab, XKB_KEY_ISO_Left_Tab, Qt::KeyboardModifiers()}, + {Qt::Key_Backspace, XKB_KEY_BackSpace, Qt::KeyboardModifiers()}, + {Qt::Key_Return, XKB_KEY_Return, Qt::KeyboardModifiers()}, + {Qt::Key_Insert, XKB_KEY_Insert, Qt::KeyboardModifiers()}, + {Qt::Key_Delete, XKB_KEY_Delete, Qt::KeyboardModifiers()}, + {Qt::Key_Pause, XKB_KEY_Pause, Qt::KeyboardModifiers()}, + {Qt::Key_Print, XKB_KEY_Print, Qt::KeyboardModifiers()}, + {Qt::Key_SysReq, XKB_KEY_Sys_Req, Qt::KeyboardModifiers()}, + {Qt::Key_Home, XKB_KEY_Home, Qt::KeyboardModifiers()}, + {Qt::Key_End, XKB_KEY_End, Qt::KeyboardModifiers()}, + {Qt::Key_Left, XKB_KEY_Left, Qt::KeyboardModifiers()}, + {Qt::Key_Up, XKB_KEY_Up, Qt::KeyboardModifiers()}, + {Qt::Key_Right, XKB_KEY_Right, Qt::KeyboardModifiers()}, + {Qt::Key_Down, XKB_KEY_Down, Qt::KeyboardModifiers()}, + {Qt::Key_PageUp, XKB_KEY_Prior, Qt::KeyboardModifiers()}, + {Qt::Key_PageDown, XKB_KEY_Next, Qt::KeyboardModifiers()}, + {Qt::Key_CapsLock, XKB_KEY_Caps_Lock, Qt::KeyboardModifiers()}, + {Qt::Key_NumLock, XKB_KEY_Num_Lock, Qt::KeyboardModifiers()}, + {Qt::Key_ScrollLock, XKB_KEY_Scroll_Lock, Qt::KeyboardModifiers()}, + {Qt::Key_F1, XKB_KEY_F1, Qt::KeyboardModifiers()}, + {Qt::Key_F2, XKB_KEY_F2, Qt::KeyboardModifiers()}, + {Qt::Key_F3, XKB_KEY_F3, Qt::KeyboardModifiers()}, + {Qt::Key_F4, XKB_KEY_F4, Qt::KeyboardModifiers()}, + {Qt::Key_F5, XKB_KEY_F5, Qt::KeyboardModifiers()}, + {Qt::Key_F6, XKB_KEY_F6, Qt::KeyboardModifiers()}, + {Qt::Key_F7, XKB_KEY_F7, Qt::KeyboardModifiers()}, + {Qt::Key_F8, XKB_KEY_F8, Qt::KeyboardModifiers()}, + {Qt::Key_F9, XKB_KEY_F9, Qt::KeyboardModifiers()}, + {Qt::Key_F10, XKB_KEY_F10, Qt::KeyboardModifiers()}, + {Qt::Key_F11, XKB_KEY_F11, Qt::KeyboardModifiers()}, + {Qt::Key_F12, XKB_KEY_F12, Qt::KeyboardModifiers()}, + {Qt::Key_F13, XKB_KEY_F13, Qt::KeyboardModifiers()}, + {Qt::Key_F14, XKB_KEY_F14, Qt::KeyboardModifiers()}, + {Qt::Key_F15, XKB_KEY_F15, Qt::KeyboardModifiers()}, + {Qt::Key_F16, XKB_KEY_F16, Qt::KeyboardModifiers()}, + {Qt::Key_F17, XKB_KEY_F17, Qt::KeyboardModifiers()}, + {Qt::Key_F18, XKB_KEY_F18, Qt::KeyboardModifiers()}, + {Qt::Key_F19, XKB_KEY_F19, Qt::KeyboardModifiers()}, + {Qt::Key_F20, XKB_KEY_F20, Qt::KeyboardModifiers()}, + {Qt::Key_F21, XKB_KEY_F21, Qt::KeyboardModifiers()}, + {Qt::Key_F22, XKB_KEY_F22, Qt::KeyboardModifiers()}, + {Qt::Key_F23, XKB_KEY_F23, Qt::KeyboardModifiers()}, + {Qt::Key_F24, XKB_KEY_F24, Qt::KeyboardModifiers()}, + {Qt::Key_F25, XKB_KEY_F25, Qt::KeyboardModifiers()}, + {Qt::Key_F26, XKB_KEY_F26, Qt::KeyboardModifiers()}, + {Qt::Key_F27, XKB_KEY_F27, Qt::KeyboardModifiers()}, + {Qt::Key_F28, XKB_KEY_F28, Qt::KeyboardModifiers()}, + {Qt::Key_F29, XKB_KEY_F29, Qt::KeyboardModifiers()}, + {Qt::Key_F30, XKB_KEY_F30, Qt::KeyboardModifiers()}, + {Qt::Key_F31, XKB_KEY_F31, Qt::KeyboardModifiers()}, + {Qt::Key_F32, XKB_KEY_F32, Qt::KeyboardModifiers()}, + {Qt::Key_F33, XKB_KEY_F33, Qt::KeyboardModifiers()}, + {Qt::Key_F34, XKB_KEY_F34, Qt::KeyboardModifiers()}, + {Qt::Key_F35, XKB_KEY_F35, Qt::KeyboardModifiers()}, + {Qt::Key_Super_L, XKB_KEY_Super_L, Qt::KeyboardModifiers()}, + {Qt::Key_Super_R, XKB_KEY_Super_R, Qt::KeyboardModifiers()}, + {Qt::Key_Menu, XKB_KEY_Menu, Qt::KeyboardModifiers()}, + {Qt::Key_Hyper_L, XKB_KEY_Hyper_L, Qt::KeyboardModifiers()}, + {Qt::Key_Hyper_R, XKB_KEY_Hyper_R, Qt::KeyboardModifiers()}, + {Qt::Key_Help, XKB_KEY_Help, Qt::KeyboardModifiers()}, + {Qt::Key_Space, XKB_KEY_KP_Space, Qt::KeypadModifier}, + {Qt::Key_Tab, XKB_KEY_KP_Tab, Qt::KeypadModifier}, + {Qt::Key_Enter, XKB_KEY_KP_Enter, Qt::KeypadModifier}, + {Qt::Key_Home, XKB_KEY_KP_Home, Qt::KeypadModifier}, + {Qt::Key_Left, XKB_KEY_KP_Left, Qt::KeypadModifier}, + {Qt::Key_Up, XKB_KEY_KP_Up, Qt::KeypadModifier}, + {Qt::Key_Right, XKB_KEY_KP_Right, Qt::KeypadModifier}, + {Qt::Key_Down, XKB_KEY_KP_Down, Qt::KeypadModifier}, + {Qt::Key_PageUp, XKB_KEY_KP_Prior, Qt::KeypadModifier}, + {Qt::Key_PageDown, XKB_KEY_KP_Next, Qt::KeypadModifier}, + {Qt::Key_End, XKB_KEY_KP_End, Qt::KeypadModifier}, + {Qt::Key_Clear, XKB_KEY_KP_Begin, Qt::KeypadModifier}, + {Qt::Key_Insert, XKB_KEY_KP_Insert, Qt::KeypadModifier}, + {Qt::Key_Delete, XKB_KEY_KP_Delete, Qt::KeypadModifier}, + {Qt::Key_Equal, XKB_KEY_KP_Equal, Qt::KeypadModifier}, + {Qt::Key_Asterisk, XKB_KEY_KP_Multiply, Qt::KeypadModifier}, + {Qt::Key_Plus, XKB_KEY_KP_Add, Qt::KeypadModifier}, + {Qt::Key_Comma, XKB_KEY_KP_Separator, Qt::KeypadModifier}, + {Qt::Key_Minus, XKB_KEY_KP_Subtract, Qt::KeypadModifier}, + {Qt::Key_Period, XKB_KEY_KP_Decimal, Qt::KeypadModifier}, + {Qt::Key_Slash, XKB_KEY_KP_Divide, Qt::KeypadModifier}, + {Qt::Key_Back, XKB_KEY_XF86Back, Qt::KeyboardModifiers()}, + {Qt::Key_Forward, XKB_KEY_XF86Forward, Qt::KeyboardModifiers()}, + {Qt::Key_Stop, XKB_KEY_XF86Stop, Qt::KeyboardModifiers()}, + {Qt::Key_Refresh, XKB_KEY_XF86Refresh, Qt::KeyboardModifiers()}, + {Qt::Key_Favorites, XKB_KEY_XF86Favorites, Qt::KeyboardModifiers()}, + {Qt::Key_LaunchMedia, XKB_KEY_XF86AudioMedia, Qt::KeyboardModifiers()}, + {Qt::Key_OpenUrl, XKB_KEY_XF86OpenURL, Qt::KeyboardModifiers()}, + {Qt::Key_HomePage, XKB_KEY_XF86HomePage, Qt::KeyboardModifiers()}, + {Qt::Key_Search, XKB_KEY_XF86Search, Qt::KeyboardModifiers()}, + {Qt::Key_VolumeDown, XKB_KEY_XF86AudioLowerVolume, Qt::KeyboardModifiers()}, + {Qt::Key_VolumeMute, XKB_KEY_XF86AudioMute, Qt::KeyboardModifiers()}, + {Qt::Key_VolumeUp, XKB_KEY_XF86AudioRaiseVolume, Qt::KeyboardModifiers()}, + {Qt::Key_MediaPlay, XKB_KEY_XF86AudioPlay, Qt::KeyboardModifiers()}, + {Qt::Key_MediaPause, XKB_KEY_XF86AudioPause, Qt::KeyboardModifiers()}, + {Qt::Key_MediaStop, XKB_KEY_XF86AudioStop, Qt::KeyboardModifiers()}, + {Qt::Key_MediaPrevious, XKB_KEY_XF86AudioPrev, Qt::KeyboardModifiers()}, + {Qt::Key_MediaNext, XKB_KEY_XF86AudioNext, Qt::KeyboardModifiers()}, + {Qt::Key_MediaRecord, XKB_KEY_XF86AudioRecord, Qt::KeyboardModifiers()}, + {Qt::Key_LaunchMail, XKB_KEY_XF86Mail, Qt::KeyboardModifiers()}, + {Qt::Key_Launch0, XKB_KEY_XF86MyComputer, Qt::KeyboardModifiers()}, + {Qt::Key_Launch1, XKB_KEY_XF86Calculator, Qt::KeyboardModifiers()}, + {Qt::Key_Memo, XKB_KEY_XF86Memo, Qt::KeyboardModifiers()}, + {Qt::Key_ToDoList, XKB_KEY_XF86ToDoList, Qt::KeyboardModifiers()}, + {Qt::Key_Calendar, XKB_KEY_XF86Calendar, Qt::KeyboardModifiers()}, + {Qt::Key_PowerDown, XKB_KEY_XF86PowerDown, Qt::KeyboardModifiers()}, + {Qt::Key_ContrastAdjust, XKB_KEY_XF86ContrastAdjust, Qt::KeyboardModifiers()}, + {Qt::Key_Standby, XKB_KEY_XF86Standby, Qt::KeyboardModifiers()}, + {Qt::Key_MonBrightnessUp, XKB_KEY_XF86MonBrightnessUp, Qt::KeyboardModifiers()}, + {Qt::Key_MonBrightnessDown, XKB_KEY_XF86MonBrightnessDown, Qt::KeyboardModifiers()}, + {Qt::Key_KeyboardLightOnOff, XKB_KEY_XF86KbdLightOnOff, Qt::KeyboardModifiers()}, + {Qt::Key_KeyboardBrightnessUp, XKB_KEY_XF86KbdBrightnessUp, Qt::KeyboardModifiers()}, + {Qt::Key_KeyboardBrightnessDown, XKB_KEY_XF86KbdBrightnessDown, Qt::KeyboardModifiers()}, + {Qt::Key_PowerOff, XKB_KEY_XF86PowerOff, Qt::KeyboardModifiers()}, + {Qt::Key_WakeUp, XKB_KEY_XF86WakeUp, Qt::KeyboardModifiers()}, + {Qt::Key_Eject, XKB_KEY_XF86Eject, Qt::KeyboardModifiers()}, + {Qt::Key_ScreenSaver, XKB_KEY_XF86ScreenSaver, Qt::KeyboardModifiers()}, + {Qt::Key_WWW, XKB_KEY_XF86WWW, Qt::KeyboardModifiers()}, + {Qt::Key_Sleep, XKB_KEY_XF86Sleep, Qt::KeyboardModifiers()}, + {Qt::Key_LightBulb, XKB_KEY_XF86LightBulb, Qt::KeyboardModifiers()}, + {Qt::Key_Shop, XKB_KEY_XF86Shop, Qt::KeyboardModifiers()}, + {Qt::Key_History, XKB_KEY_XF86History, Qt::KeyboardModifiers()}, + {Qt::Key_AddFavorite, XKB_KEY_XF86AddFavorite, Qt::KeyboardModifiers()}, + {Qt::Key_HotLinks, XKB_KEY_XF86HotLinks, Qt::KeyboardModifiers()}, + {Qt::Key_BrightnessAdjust, XKB_KEY_XF86BrightnessAdjust, Qt::KeyboardModifiers()}, + {Qt::Key_Finance, XKB_KEY_XF86Finance, Qt::KeyboardModifiers()}, + {Qt::Key_Community, XKB_KEY_XF86Community, Qt::KeyboardModifiers()}, + {Qt::Key_AudioRewind, XKB_KEY_XF86AudioRewind, Qt::KeyboardModifiers()}, + {Qt::Key_BackForward, XKB_KEY_XF86BackForward, Qt::KeyboardModifiers()}, + {Qt::Key_ApplicationLeft, XKB_KEY_XF86ApplicationLeft, Qt::KeyboardModifiers()}, + {Qt::Key_ApplicationRight, XKB_KEY_XF86ApplicationRight, Qt::KeyboardModifiers()}, + {Qt::Key_Book, XKB_KEY_XF86Book, Qt::KeyboardModifiers()}, + {Qt::Key_CD, XKB_KEY_XF86CD, Qt::KeyboardModifiers()}, + {Qt::Key_Calculator, XKB_KEY_XF86Calculater, Qt::KeyboardModifiers()}, + {Qt::Key_Clear, XKB_KEY_XF86Clear, Qt::KeyboardModifiers()}, + {Qt::Key_ClearGrab, XKB_KEY_XF86ClearGrab, Qt::KeyboardModifiers()}, + {Qt::Key_Close, XKB_KEY_XF86Close, Qt::KeyboardModifiers()}, + {Qt::Key_Copy, XKB_KEY_XF86Copy, Qt::KeyboardModifiers()}, + {Qt::Key_Cut, XKB_KEY_XF86Cut, Qt::KeyboardModifiers()}, + {Qt::Key_Display, XKB_KEY_XF86Display, Qt::KeyboardModifiers()}, + {Qt::Key_DOS, XKB_KEY_XF86DOS, Qt::KeyboardModifiers()}, + {Qt::Key_Documents, XKB_KEY_XF86Documents, Qt::KeyboardModifiers()}, + {Qt::Key_Excel, XKB_KEY_XF86Excel, Qt::KeyboardModifiers()}, + {Qt::Key_Explorer, XKB_KEY_XF86Explorer, Qt::KeyboardModifiers()}, + {Qt::Key_Game, XKB_KEY_XF86Game, Qt::KeyboardModifiers()}, + {Qt::Key_Go, XKB_KEY_XF86Go, Qt::KeyboardModifiers()}, + {Qt::Key_iTouch, XKB_KEY_XF86iTouch, Qt::KeyboardModifiers()}, + {Qt::Key_LogOff, XKB_KEY_XF86LogOff, Qt::KeyboardModifiers()}, + {Qt::Key_Market, XKB_KEY_XF86Market, Qt::KeyboardModifiers()}, + {Qt::Key_Meeting, XKB_KEY_XF86Meeting, Qt::KeyboardModifiers()}, + {Qt::Key_MenuKB, XKB_KEY_XF86MenuKB, Qt::KeyboardModifiers()}, + {Qt::Key_MenuPB, XKB_KEY_XF86MenuPB, Qt::KeyboardModifiers()}, + {Qt::Key_MySites, XKB_KEY_XF86MySites, Qt::KeyboardModifiers()}, + {Qt::Key_News, XKB_KEY_XF86News, Qt::KeyboardModifiers()}, + {Qt::Key_OfficeHome, XKB_KEY_XF86OfficeHome, Qt::KeyboardModifiers()}, + {Qt::Key_Option, XKB_KEY_XF86Option, Qt::KeyboardModifiers()}, + {Qt::Key_Paste, XKB_KEY_XF86Paste, Qt::KeyboardModifiers()}, + {Qt::Key_Phone, XKB_KEY_XF86Phone, Qt::KeyboardModifiers()}, + {Qt::Key_Reply, XKB_KEY_XF86Reply, Qt::KeyboardModifiers()}, + {Qt::Key_Reload, XKB_KEY_XF86Reload, Qt::KeyboardModifiers()}, + {Qt::Key_RotateWindows, XKB_KEY_XF86RotateWindows, Qt::KeyboardModifiers()}, + {Qt::Key_RotationPB, XKB_KEY_XF86RotationPB, Qt::KeyboardModifiers()}, + {Qt::Key_RotationKB, XKB_KEY_XF86RotationKB, Qt::KeyboardModifiers()}, + {Qt::Key_Save, XKB_KEY_XF86Save, Qt::KeyboardModifiers()}, + {Qt::Key_Send, XKB_KEY_XF86Send, Qt::KeyboardModifiers()}, + {Qt::Key_Spell, XKB_KEY_XF86Spell, Qt::KeyboardModifiers()}, + {Qt::Key_SplitScreen, XKB_KEY_XF86SplitScreen, Qt::KeyboardModifiers()}, + {Qt::Key_Support, XKB_KEY_XF86Support, Qt::KeyboardModifiers()}, + {Qt::Key_TaskPane, XKB_KEY_XF86TaskPane, Qt::KeyboardModifiers()}, + {Qt::Key_Terminal, XKB_KEY_XF86Terminal, Qt::KeyboardModifiers()}, + {Qt::Key_Tools, XKB_KEY_XF86Tools, Qt::KeyboardModifiers()}, + {Qt::Key_Travel, XKB_KEY_XF86Travel, Qt::KeyboardModifiers()}, + {Qt::Key_Video, XKB_KEY_XF86Video, Qt::KeyboardModifiers()}, + {Qt::Key_Word, XKB_KEY_XF86Word, Qt::KeyboardModifiers()}, + {Qt::Key_Xfer, XKB_KEY_XF86Xfer, Qt::KeyboardModifiers()}, + {Qt::Key_ZoomIn, XKB_KEY_XF86ZoomIn, Qt::KeyboardModifiers()}, + {Qt::Key_ZoomOut, XKB_KEY_XF86ZoomOut, Qt::KeyboardModifiers()}, + {Qt::Key_Away, XKB_KEY_XF86Away, Qt::KeyboardModifiers()}, + {Qt::Key_Messenger, XKB_KEY_XF86Messenger, Qt::KeyboardModifiers()}, + {Qt::Key_WebCam, XKB_KEY_XF86WebCam, Qt::KeyboardModifiers()}, + {Qt::Key_MailForward, XKB_KEY_XF86MailForward, Qt::KeyboardModifiers()}, + {Qt::Key_Pictures, XKB_KEY_XF86Pictures, Qt::KeyboardModifiers()}, + {Qt::Key_Music, XKB_KEY_XF86Music, Qt::KeyboardModifiers()}, + {Qt::Key_Battery, XKB_KEY_XF86Battery, Qt::KeyboardModifiers()}, + {Qt::Key_Bluetooth, XKB_KEY_XF86Bluetooth, Qt::KeyboardModifiers()}, + {Qt::Key_WLAN, XKB_KEY_XF86WLAN, Qt::KeyboardModifiers()}, + {Qt::Key_UWB, XKB_KEY_XF86UWB, Qt::KeyboardModifiers()}, + {Qt::Key_AudioForward, XKB_KEY_XF86AudioForward, Qt::KeyboardModifiers()}, + {Qt::Key_AudioRepeat, XKB_KEY_XF86AudioRepeat, Qt::KeyboardModifiers()}, + {Qt::Key_AudioRandomPlay, XKB_KEY_XF86AudioRandomPlay, Qt::KeyboardModifiers()}, + {Qt::Key_Subtitle, XKB_KEY_XF86Subtitle, Qt::KeyboardModifiers()}, + {Qt::Key_AudioCycleTrack, XKB_KEY_XF86AudioCycleTrack, Qt::KeyboardModifiers()}, + {Qt::Key_Time, XKB_KEY_XF86Time, Qt::KeyboardModifiers()}, + {Qt::Key_Select, XKB_KEY_XF86Select, Qt::KeyboardModifiers()}, + {Qt::Key_View, XKB_KEY_XF86View, Qt::KeyboardModifiers()}, + {Qt::Key_TopMenu, XKB_KEY_XF86TopMenu, Qt::KeyboardModifiers()}, + {Qt::Key_Bluetooth, XKB_KEY_XF86Bluetooth, Qt::KeyboardModifiers()}, + {Qt::Key_Suspend, XKB_KEY_XF86Suspend, Qt::KeyboardModifiers()}, + {Qt::Key_Hibernate, XKB_KEY_XF86Hibernate, Qt::KeyboardModifiers()}, + {Qt::Key_TouchpadToggle, XKB_KEY_XF86TouchpadToggle, Qt::KeyboardModifiers()}, + {Qt::Key_TouchpadOn, XKB_KEY_XF86TouchpadOn, Qt::KeyboardModifiers()}, + {Qt::Key_TouchpadOff, XKB_KEY_XF86TouchpadOff, Qt::KeyboardModifiers()}, + {Qt::Key_MicMute, XKB_KEY_XF86AudioMicMute, Qt::KeyboardModifiers()}, + {Qt::Key_Launch2, XKB_KEY_XF86Launch0, Qt::KeyboardModifiers()}, + {Qt::Key_Launch3, XKB_KEY_XF86Launch1, Qt::KeyboardModifiers()}, + {Qt::Key_Launch4, XKB_KEY_XF86Launch2, Qt::KeyboardModifiers()}, + {Qt::Key_Launch5, XKB_KEY_XF86Launch3, Qt::KeyboardModifiers()}, + {Qt::Key_Launch6, XKB_KEY_XF86Launch4, Qt::KeyboardModifiers()}, + {Qt::Key_Launch7, XKB_KEY_XF86Launch5, Qt::KeyboardModifiers()}, + {Qt::Key_Launch8, XKB_KEY_XF86Launch6, Qt::KeyboardModifiers()}, + {Qt::Key_Launch9, XKB_KEY_XF86Launch7, Qt::KeyboardModifiers()}, + {Qt::Key_LaunchA, XKB_KEY_XF86Launch8, Qt::KeyboardModifiers()}, + {Qt::Key_LaunchB, XKB_KEY_XF86Launch9, Qt::KeyboardModifiers()}, + {Qt::Key_LaunchC, XKB_KEY_XF86LaunchA, Qt::KeyboardModifiers()}, + {Qt::Key_LaunchD, XKB_KEY_XF86LaunchB, Qt::KeyboardModifiers()}, + {Qt::Key_LaunchE, XKB_KEY_XF86LaunchC, Qt::KeyboardModifiers()}, + {Qt::Key_LaunchF, XKB_KEY_XF86LaunchD, Qt::KeyboardModifiers()}, + + /* + * Latin 1 + * (ISO/IEC 8859-1 = Unicode U+0020..U+00FF) + * Byte 3 = 0 + */ + {Qt::Key_Exclam, XKB_KEY_exclam, Qt::KeyboardModifiers()}, + {Qt::Key_QuoteDbl, XKB_KEY_quotedbl, Qt::KeyboardModifiers()}, + {Qt::Key_NumberSign, XKB_KEY_numbersign, Qt::KeyboardModifiers()}, + {Qt::Key_Dollar, XKB_KEY_dollar, Qt::KeyboardModifiers()}, + {Qt::Key_Percent, XKB_KEY_percent, Qt::KeyboardModifiers()}, + {Qt::Key_Ampersand, XKB_KEY_ampersand, Qt::KeyboardModifiers()}, + {Qt::Key_Apostrophe, XKB_KEY_apostrophe, Qt::KeyboardModifiers()}, + {Qt::Key_ParenLeft, XKB_KEY_parenleft, Qt::KeyboardModifiers()}, + {Qt::Key_ParenRight, XKB_KEY_parenright, Qt::KeyboardModifiers()}, + {Qt::Key_Asterisk, XKB_KEY_asterisk, Qt::KeyboardModifiers()}, + {Qt::Key_Plus, XKB_KEY_plus, Qt::KeyboardModifiers()}, + {Qt::Key_Comma, XKB_KEY_comma, Qt::KeyboardModifiers()}, + {Qt::Key_Minus, XKB_KEY_minus, Qt::KeyboardModifiers()}, + {Qt::Key_Period, XKB_KEY_period, Qt::KeyboardModifiers()}, + {Qt::Key_Slash, XKB_KEY_slash, Qt::KeyboardModifiers()}, + {Qt::Key_0, XKB_KEY_0, Qt::KeyboardModifiers()}, + {Qt::Key_1, XKB_KEY_1, Qt::KeyboardModifiers()}, + {Qt::Key_2, XKB_KEY_2, Qt::KeyboardModifiers()}, + {Qt::Key_3, XKB_KEY_3, Qt::KeyboardModifiers()}, + {Qt::Key_4, XKB_KEY_4, Qt::KeyboardModifiers()}, + {Qt::Key_5, XKB_KEY_5, Qt::KeyboardModifiers()}, + {Qt::Key_6, XKB_KEY_6, Qt::KeyboardModifiers()}, + {Qt::Key_7, XKB_KEY_7, Qt::KeyboardModifiers()}, + {Qt::Key_8, XKB_KEY_8, Qt::KeyboardModifiers()}, + {Qt::Key_9, XKB_KEY_9, Qt::KeyboardModifiers()}, + {Qt::Key_Colon, XKB_KEY_colon, Qt::KeyboardModifiers()}, + {Qt::Key_Semicolon, XKB_KEY_semicolon, Qt::KeyboardModifiers()}, + {Qt::Key_Less, XKB_KEY_less, Qt::KeyboardModifiers()}, + {Qt::Key_Equal, XKB_KEY_equal, Qt::KeyboardModifiers()}, + {Qt::Key_Greater, XKB_KEY_greater, Qt::KeyboardModifiers()}, + {Qt::Key_Question, XKB_KEY_question, Qt::KeyboardModifiers()}, + {Qt::Key_At, XKB_KEY_at, Qt::KeyboardModifiers()}, + {Qt::Key_A, XKB_KEY_A, Qt::ShiftModifier}, + {Qt::Key_B, XKB_KEY_B, Qt::ShiftModifier}, + {Qt::Key_C, XKB_KEY_C, Qt::ShiftModifier}, + {Qt::Key_D, XKB_KEY_D, Qt::ShiftModifier}, + {Qt::Key_E, XKB_KEY_E, Qt::ShiftModifier}, + {Qt::Key_F, XKB_KEY_F, Qt::ShiftModifier}, + {Qt::Key_G, XKB_KEY_G, Qt::ShiftModifier}, + {Qt::Key_H, XKB_KEY_H, Qt::ShiftModifier}, + {Qt::Key_I, XKB_KEY_I, Qt::ShiftModifier}, + {Qt::Key_J, XKB_KEY_J, Qt::ShiftModifier}, + {Qt::Key_K, XKB_KEY_K, Qt::ShiftModifier}, + {Qt::Key_L, XKB_KEY_L, Qt::ShiftModifier}, + {Qt::Key_M, XKB_KEY_M, Qt::ShiftModifier}, + {Qt::Key_N, XKB_KEY_N, Qt::ShiftModifier}, + {Qt::Key_O, XKB_KEY_O, Qt::ShiftModifier}, + {Qt::Key_P, XKB_KEY_P, Qt::ShiftModifier}, + {Qt::Key_Q, XKB_KEY_Q, Qt::ShiftModifier}, + {Qt::Key_R, XKB_KEY_R, Qt::ShiftModifier}, + {Qt::Key_S, XKB_KEY_S, Qt::ShiftModifier}, + {Qt::Key_T, XKB_KEY_T, Qt::ShiftModifier}, + {Qt::Key_U, XKB_KEY_U, Qt::ShiftModifier}, + {Qt::Key_V, XKB_KEY_V, Qt::ShiftModifier}, + {Qt::Key_W, XKB_KEY_W, Qt::ShiftModifier}, + {Qt::Key_X, XKB_KEY_X, Qt::ShiftModifier}, + {Qt::Key_Y, XKB_KEY_Y, Qt::ShiftModifier}, + {Qt::Key_Z, XKB_KEY_Z, Qt::ShiftModifier}, + {Qt::Key_BracketLeft, XKB_KEY_bracketleft, Qt::KeyboardModifiers()}, + {Qt::Key_Backslash, XKB_KEY_backslash, Qt::KeyboardModifiers()}, + {Qt::Key_BracketRight, XKB_KEY_bracketright, Qt::KeyboardModifiers()}, + {Qt::Key_AsciiCircum, XKB_KEY_asciicircum, Qt::KeyboardModifiers()}, + {Qt::Key_Underscore, XKB_KEY_underscore, Qt::KeyboardModifiers()}, + {Qt::Key_QuoteLeft, XKB_KEY_quoteleft, Qt::KeyboardModifiers()}, + {Qt::Key_A, XKB_KEY_a, Qt::KeyboardModifiers()}, + {Qt::Key_B, XKB_KEY_b, Qt::KeyboardModifiers()}, + {Qt::Key_C, XKB_KEY_c, Qt::KeyboardModifiers()}, + {Qt::Key_D, XKB_KEY_d, Qt::KeyboardModifiers()}, + {Qt::Key_E, XKB_KEY_e, Qt::KeyboardModifiers()}, + {Qt::Key_F, XKB_KEY_f, Qt::KeyboardModifiers()}, + {Qt::Key_G, XKB_KEY_g, Qt::KeyboardModifiers()}, + {Qt::Key_H, XKB_KEY_h, Qt::KeyboardModifiers()}, + {Qt::Key_I, XKB_KEY_i, Qt::KeyboardModifiers()}, + {Qt::Key_J, XKB_KEY_j, Qt::KeyboardModifiers()}, + {Qt::Key_K, XKB_KEY_k, Qt::KeyboardModifiers()}, + {Qt::Key_L, XKB_KEY_l, Qt::KeyboardModifiers()}, + {Qt::Key_M, XKB_KEY_m, Qt::KeyboardModifiers()}, + {Qt::Key_N, XKB_KEY_n, Qt::KeyboardModifiers()}, + {Qt::Key_O, XKB_KEY_o, Qt::KeyboardModifiers()}, + {Qt::Key_P, XKB_KEY_p, Qt::KeyboardModifiers()}, + {Qt::Key_Q, XKB_KEY_q, Qt::KeyboardModifiers()}, + {Qt::Key_R, XKB_KEY_r, Qt::KeyboardModifiers()}, + {Qt::Key_S, XKB_KEY_s, Qt::KeyboardModifiers()}, + {Qt::Key_T, XKB_KEY_t, Qt::KeyboardModifiers()}, + {Qt::Key_U, XKB_KEY_u, Qt::KeyboardModifiers()}, + {Qt::Key_V, XKB_KEY_v, Qt::KeyboardModifiers()}, + {Qt::Key_W, XKB_KEY_w, Qt::KeyboardModifiers()}, + {Qt::Key_X, XKB_KEY_x, Qt::KeyboardModifiers()}, + {Qt::Key_Y, XKB_KEY_y, Qt::KeyboardModifiers()}, + {Qt::Key_Z, XKB_KEY_z, Qt::KeyboardModifiers()}, + {Qt::Key_BraceLeft, XKB_KEY_braceleft, Qt::KeyboardModifiers()}, + {Qt::Key_Bar, XKB_KEY_bar, Qt::KeyboardModifiers()}, + {Qt::Key_BraceRight, XKB_KEY_braceright, Qt::KeyboardModifiers()}, + {Qt::Key_AsciiTilde, XKB_KEY_asciitilde, Qt::KeyboardModifiers()}, + + {Qt::Key_nobreakspace, XKB_KEY_nobreakspace, Qt::KeyboardModifiers()}, + {Qt::Key_exclamdown, XKB_KEY_exclamdown, Qt::KeyboardModifiers()}, + {Qt::Key_cent, XKB_KEY_cent, Qt::KeyboardModifiers()}, + {Qt::Key_sterling, XKB_KEY_sterling, Qt::KeyboardModifiers()}, + {Qt::Key_currency, XKB_KEY_currency, Qt::KeyboardModifiers()}, + {Qt::Key_yen, XKB_KEY_yen, Qt::KeyboardModifiers()}, + {Qt::Key_brokenbar, XKB_KEY_brokenbar, Qt::KeyboardModifiers()}, + {Qt::Key_section, XKB_KEY_section, Qt::KeyboardModifiers()}, + {Qt::Key_diaeresis, XKB_KEY_diaeresis, Qt::KeyboardModifiers()}, + {Qt::Key_copyright, XKB_KEY_copyright, Qt::KeyboardModifiers()}, + {Qt::Key_ordfeminine, XKB_KEY_ordfeminine, Qt::KeyboardModifiers()}, + {Qt::Key_guillemotleft, XKB_KEY_guillemotleft, Qt::KeyboardModifiers()}, + {Qt::Key_notsign, XKB_KEY_notsign, Qt::KeyboardModifiers()}, + {Qt::Key_hyphen, XKB_KEY_hyphen, Qt::KeyboardModifiers()}, + {Qt::Key_registered, XKB_KEY_registered, Qt::KeyboardModifiers()}, + {Qt::Key_macron, XKB_KEY_macron, Qt::KeyboardModifiers()}, + {Qt::Key_degree, XKB_KEY_degree, Qt::KeyboardModifiers()}, + {Qt::Key_plusminus, XKB_KEY_plusminus, Qt::KeyboardModifiers()}, + {Qt::Key_twosuperior, XKB_KEY_twosuperior, Qt::KeyboardModifiers()}, + {Qt::Key_threesuperior, XKB_KEY_threesuperior, Qt::KeyboardModifiers()}, + {Qt::Key_acute, XKB_KEY_acute, Qt::KeyboardModifiers()}, + {Qt::Key_mu, XKB_KEY_mu, Qt::KeyboardModifiers()}, + {Qt::Key_paragraph, XKB_KEY_paragraph, Qt::KeyboardModifiers()}, + {Qt::Key_periodcentered, XKB_KEY_periodcentered, Qt::KeyboardModifiers()}, + {Qt::Key_cedilla, XKB_KEY_cedilla, Qt::KeyboardModifiers()}, + {Qt::Key_onesuperior, XKB_KEY_onesuperior, Qt::KeyboardModifiers()}, + {Qt::Key_masculine, XKB_KEY_masculine, Qt::KeyboardModifiers()}, + {Qt::Key_guillemotright, XKB_KEY_guillemotright, Qt::KeyboardModifiers()}, + {Qt::Key_onequarter, XKB_KEY_onequarter, Qt::KeyboardModifiers()}, + {Qt::Key_onehalf, XKB_KEY_onehalf, Qt::KeyboardModifiers()}, + {Qt::Key_threequarters, XKB_KEY_threequarters, Qt::KeyboardModifiers()}, + {Qt::Key_questiondown, XKB_KEY_questiondown, Qt::KeyboardModifiers()}, + {Qt::Key_Agrave, XKB_KEY_Agrave, Qt::ShiftModifier}, + {Qt::Key_Aacute, XKB_KEY_Aacute, Qt::ShiftModifier}, + {Qt::Key_Acircumflex, XKB_KEY_Acircumflex, Qt::ShiftModifier}, + {Qt::Key_Atilde, XKB_KEY_Atilde, Qt::ShiftModifier}, + {Qt::Key_Adiaeresis, XKB_KEY_Adiaeresis, Qt::ShiftModifier}, + {Qt::Key_Aring, XKB_KEY_Aring, Qt::ShiftModifier}, + {Qt::Key_AE, XKB_KEY_AE, Qt::ShiftModifier}, + {Qt::Key_Ccedilla, XKB_KEY_Ccedilla, Qt::ShiftModifier}, + {Qt::Key_Egrave, XKB_KEY_Egrave, Qt::ShiftModifier}, + {Qt::Key_Eacute, XKB_KEY_Eacute, Qt::ShiftModifier}, + {Qt::Key_Ecircumflex, XKB_KEY_Ecircumflex, Qt::ShiftModifier}, + {Qt::Key_Ediaeresis, XKB_KEY_Ediaeresis, Qt::ShiftModifier}, + {Qt::Key_Igrave, XKB_KEY_Igrave, Qt::ShiftModifier}, + {Qt::Key_Iacute, XKB_KEY_Iacute, Qt::ShiftModifier}, + {Qt::Key_Icircumflex, XKB_KEY_Icircumflex, Qt::ShiftModifier}, + {Qt::Key_Idiaeresis, XKB_KEY_Idiaeresis, Qt::ShiftModifier}, + {Qt::Key_ETH, XKB_KEY_ETH, Qt::ShiftModifier}, + {Qt::Key_Ntilde, XKB_KEY_Ntilde, Qt::ShiftModifier}, + {Qt::Key_Ograve, XKB_KEY_Ograve, Qt::ShiftModifier}, + {Qt::Key_Oacute, XKB_KEY_Oacute, Qt::ShiftModifier}, + {Qt::Key_Ocircumflex, XKB_KEY_Ocircumflex, Qt::ShiftModifier}, + {Qt::Key_Otilde, XKB_KEY_Otilde, Qt::ShiftModifier}, + {Qt::Key_Odiaeresis, XKB_KEY_Odiaeresis, Qt::ShiftModifier}, + {Qt::Key_multiply, XKB_KEY_multiply, Qt::ShiftModifier}, + {Qt::Key_Ooblique, XKB_KEY_Ooblique, Qt::ShiftModifier}, + {Qt::Key_Ugrave, XKB_KEY_Ugrave, Qt::ShiftModifier}, + {Qt::Key_Uacute, XKB_KEY_Uacute, Qt::ShiftModifier}, + {Qt::Key_Ucircumflex, XKB_KEY_Ucircumflex, Qt::ShiftModifier}, + {Qt::Key_Udiaeresis, XKB_KEY_Udiaeresis, Qt::ShiftModifier}, + {Qt::Key_Yacute, XKB_KEY_Yacute, Qt::ShiftModifier}, + {Qt::Key_THORN, XKB_KEY_THORN, Qt::ShiftModifier}, + {Qt::Key_ssharp, XKB_KEY_ssharp, Qt::KeyboardModifiers()}, + {Qt::Key_Agrave, XKB_KEY_agrave, Qt::KeyboardModifiers()}, + {Qt::Key_Aacute, XKB_KEY_aacute, Qt::KeyboardModifiers()}, + {Qt::Key_Acircumflex, XKB_KEY_acircumflex, Qt::KeyboardModifiers()}, + {Qt::Key_Atilde, XKB_KEY_atilde, Qt::KeyboardModifiers()}, + {Qt::Key_Adiaeresis, XKB_KEY_adiaeresis, Qt::KeyboardModifiers()}, + {Qt::Key_Aring, XKB_KEY_aring, Qt::KeyboardModifiers()}, + {Qt::Key_AE, XKB_KEY_ae, Qt::KeyboardModifiers()}, + {Qt::Key_Ccedilla, XKB_KEY_ccedilla, Qt::KeyboardModifiers()}, + {Qt::Key_Egrave, XKB_KEY_egrave, Qt::KeyboardModifiers()}, + {Qt::Key_Eacute, XKB_KEY_eacute, Qt::KeyboardModifiers()}, + {Qt::Key_Ecircumflex, XKB_KEY_ecircumflex, Qt::KeyboardModifiers()}, + {Qt::Key_Ediaeresis, XKB_KEY_ediaeresis, Qt::KeyboardModifiers()}, + {Qt::Key_Igrave, XKB_KEY_igrave, Qt::KeyboardModifiers()}, + {Qt::Key_Iacute, XKB_KEY_iacute, Qt::KeyboardModifiers()}, + {Qt::Key_Icircumflex, XKB_KEY_icircumflex, Qt::KeyboardModifiers()}, + {Qt::Key_Idiaeresis, XKB_KEY_idiaeresis, Qt::KeyboardModifiers()}, + {Qt::Key_ETH, XKB_KEY_eth, Qt::KeyboardModifiers()}, + {Qt::Key_Ntilde, XKB_KEY_ntilde, Qt::KeyboardModifiers()}, + {Qt::Key_Ograve, XKB_KEY_ograve, Qt::KeyboardModifiers()}, + {Qt::Key_Oacute, XKB_KEY_oacute, Qt::KeyboardModifiers()}, + {Qt::Key_Ocircumflex, XKB_KEY_ocircumflex, Qt::KeyboardModifiers()}, + {Qt::Key_Otilde, XKB_KEY_otilde, Qt::KeyboardModifiers()}, + {Qt::Key_Odiaeresis, XKB_KEY_odiaeresis, Qt::KeyboardModifiers()}, + {Qt::Key_division, XKB_KEY_division, Qt::KeyboardModifiers()}, + {Qt::Key_Ooblique, XKB_KEY_ooblique, Qt::KeyboardModifiers()}, + {Qt::Key_Ugrave, XKB_KEY_ugrave, Qt::KeyboardModifiers()}, + {Qt::Key_Uacute, XKB_KEY_uacute, Qt::KeyboardModifiers()}, + {Qt::Key_Ucircumflex, XKB_KEY_ucircumflex, Qt::KeyboardModifiers()}, + {Qt::Key_Udiaeresis, XKB_KEY_udiaeresis, Qt::KeyboardModifiers()}, + {Qt::Key_Yacute, XKB_KEY_yacute, Qt::KeyboardModifiers()}, + {Qt::Key_THORN, XKB_KEY_thorn, Qt::KeyboardModifiers()}, + {Qt::Key_ydiaeresis, XKB_KEY_ydiaeresis, Qt::KeyboardModifiers()}, + /* + * Numpad + */ + {Qt::Key_0, XKB_KEY_KP_0, Qt::KeypadModifier}, + {Qt::Key_1, XKB_KEY_KP_1, Qt::KeypadModifier}, + {Qt::Key_2, XKB_KEY_KP_2, Qt::KeypadModifier}, + {Qt::Key_3, XKB_KEY_KP_3, Qt::KeypadModifier}, + {Qt::Key_4, XKB_KEY_KP_4, Qt::KeypadModifier}, + {Qt::Key_5, XKB_KEY_KP_5, Qt::KeypadModifier}, + {Qt::Key_6, XKB_KEY_KP_6, Qt::KeypadModifier}, + {Qt::Key_7, XKB_KEY_KP_7, Qt::KeypadModifier}, + {Qt::Key_8, XKB_KEY_KP_8, Qt::KeypadModifier}, + {Qt::Key_9, XKB_KEY_KP_9, Qt::KeypadModifier}}; + +void XkbTest::testToQtKey_data() +{ + QTest::addColumn("qt"); + QTest::addColumn("keySym"); + for (std::size_t i = 0; i < sizeof(g_rgQtToSymX) / sizeof(TransKey); i++) { + const QByteArray row = QByteArray::number(g_rgQtToSymX[i].keySymX, 16); + QTest::newRow(row.constData()) << g_rgQtToSymX[i].keySymQt << g_rgQtToSymX[i].keySymX; + } +} + +void XkbTest::testToQtKey() +{ + Xkb xkb; + QFETCH(xkb_keysym_t, keySym); + QTEST(xkb.toQtKey(keySym), "qt"); +} + +QTEST_MAIN(XkbTest) +#include "test_xkb.moc" diff --git a/autotests/testutils.h b/autotests/testutils.h new file mode 100644 index 0000000..e9b5e76 --- /dev/null +++ b/autotests/testutils.h @@ -0,0 +1,55 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2013 Martin Gräßlin + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#ifndef TESTUTILS_H +#define TESTUTILS_H +// KWin +#include +// XCB +#include + +namespace +{ +static void forceXcb() +{ + qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("xcb")); +} +} + +namespace KWin +{ + +/** + * Wrapper to create an 0,0x10,10 input only window for testing purposes + */ +#ifndef NO_NONE_WINDOW +static xcb_window_t createWindow() +{ + xcb_window_t w = xcb_generate_id(connection()); + const uint32_t values[] = {true}; + xcb_create_window(connection(), 0, w, rootWindow(), + 0, 0, 10, 10, + 0, XCB_WINDOW_CLASS_INPUT_ONLY, XCB_COPY_FROM_PARENT, + XCB_CW_OVERRIDE_REDIRECT, values); + return w; +} +#endif + +/** + * casts XCB_WINDOW_NONE to uint32_t. Needed to make QCOMPARE working. + */ +#ifndef NO_NONE_WINDOW +static uint32_t noneWindow() +{ + return XCB_WINDOW_NONE; +} +#endif + +} // namespace + +#endif diff --git a/autotests/xcb_scaling_mock.cpp b/autotests/xcb_scaling_mock.cpp new file mode 100644 index 0000000..9493cf2 --- /dev/null +++ b/autotests/xcb_scaling_mock.cpp @@ -0,0 +1,34 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2022 Aleix Pol Gonzalez + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include + +namespace KWin +{ + +uint32_t Xcb::toXNative(qreal value) +{ + return value; +} + +qreal Xcb::fromXNative(int value) +{ + return value; +} + +QSizeF Xcb::fromXNative(const QSize &value) +{ + return value; +} + +QRectF Xcb::nativeFloor(const QRectF &value) +{ + return value; +} +} diff --git a/cmake/modules/FindLibcap.cmake b/cmake/modules/FindLibcap.cmake new file mode 100644 index 0000000..f0efa3e --- /dev/null +++ b/cmake/modules/FindLibcap.cmake @@ -0,0 +1,38 @@ +# Try to find the setcap binary and cap libraries +# +# This will define: +# +# Libcap_FOUND - system has the cap library and setcap binary +# Libcap_LIBRARIES - cap libraries to link against +# SETCAP_EXECUTABLE - path of the setcap binary +# In addition, the following targets are defined: +# +# Libcap::SetCapabilities +# + + +# SPDX-FileCopyrightText: 2014 Hrvoje Senjan +# +# SPDX-License-Identifier: BSD-3-Clause + +find_program(SETCAP_EXECUTABLE NAMES setcap DOC "The setcap executable") + +find_library(Libcap_LIBRARIES NAMES cap DOC "The cap (capabilities) library") + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Libcap FOUND_VAR Libcap_FOUND + REQUIRED_VARS SETCAP_EXECUTABLE Libcap_LIBRARIES) + +if(Libcap_FOUND AND NOT TARGET Libcap::SetCapabilities) + add_executable(Libcap::SetCapabilities IMPORTED) + set_target_properties(Libcap::SetCapabilities PROPERTIES + IMPORTED_LOCATION "${SETCAP_EXECUTABLE}" + ) +endif() + +mark_as_advanced(SETCAP_EXECUTABLE Libcap_LIBRARIES) + +include(FeatureSummary) +set_package_properties(Libcap PROPERTIES + URL https://sites.google.com/site/fullycapable/ + DESCRIPTION "Capabilities are a measure to limit the omnipotence of the superuser.") diff --git a/cmake/modules/FindLibdrm.cmake b/cmake/modules/FindLibdrm.cmake new file mode 100644 index 0000000..cf6e061 --- /dev/null +++ b/cmake/modules/FindLibdrm.cmake @@ -0,0 +1,105 @@ +#.rst: +# FindLibdrm +# ------- +# +# Try to find libdrm on a Unix system. +# +# This will define the following variables: +# +# ``Libdrm_FOUND`` +# True if (the requested version of) libdrm is available +# ``Libdrm_VERSION`` +# The version of libdrm +# ``Libdrm_LIBRARIES`` +# This can be passed to target_link_libraries() instead of the ``Libdrm::Libdrm`` +# target +# ``Libdrm_INCLUDE_DIRS`` +# This should be passed to target_include_directories() if the target is not +# used for linking +# ``Libdrm_DEFINITIONS`` +# This should be passed to target_compile_options() if the target is not +# used for linking +# +# If ``Libdrm_FOUND`` is TRUE, it will also define the following imported target: +# +# ``Libdrm::Libdrm`` +# The libdrm library +# +# In general we recommend using the imported target, as it is easier to use. +# Bear in mind, however, that if the target is in the link interface of an +# exported library, it must be made available by the package config file. + +#============================================================================= +# SPDX-FileCopyrightText: 2014 Alex Merry +# SPDX-FileCopyrightText: 2014 Martin Gräßlin +# +# SPDX-License-Identifier: BSD-3-Clause +#============================================================================= + +if(CMAKE_VERSION VERSION_LESS 2.8.12) + message(FATAL_ERROR "CMake 2.8.12 is required by FindLibdrm.cmake") +endif() +if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.12) + message(AUTHOR_WARNING "Your project should require at least CMake 2.8.12 to use FindLibdrm.cmake") +endif() + +if(NOT WIN32) + # Use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + find_package(PkgConfig) + pkg_check_modules(PKG_Libdrm QUIET libdrm) + + set(Libdrm_DEFINITIONS ${PKG_Libdrm_CFLAGS_OTHER}) + set(Libdrm_VERSION ${PKG_Libdrm_VERSION}) + + find_path(Libdrm_INCLUDE_DIR + NAMES + xf86drm.h + HINTS + ${PKG_Libdrm_INCLUDE_DIRS} + ) + find_library(Libdrm_LIBRARY + NAMES + drm + HINTS + ${PKG_Libdrm_LIBRARY_DIRS} + ) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Libdrm + FOUND_VAR + Libdrm_FOUND + REQUIRED_VARS + Libdrm_LIBRARY + Libdrm_INCLUDE_DIR + VERSION_VAR + Libdrm_VERSION + ) + + if(Libdrm_FOUND AND NOT TARGET Libdrm::Libdrm) + add_library(Libdrm::Libdrm UNKNOWN IMPORTED) + set_target_properties(Libdrm::Libdrm PROPERTIES + IMPORTED_LOCATION "${Libdrm_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${Libdrm_DEFINITIONS}" + INTERFACE_INCLUDE_DIRECTORIES "${Libdrm_INCLUDE_DIR}" + INTERFACE_INCLUDE_DIRECTORIES "${Libdrm_INCLUDE_DIR}/libdrm" + ) + endif() + + mark_as_advanced(Libdrm_LIBRARY Libdrm_INCLUDE_DIR) + + # compatibility variables + set(Libdrm_LIBRARIES ${Libdrm_LIBRARY}) + set(Libdrm_INCLUDE_DIRS ${Libdrm_INCLUDE_DIR} "${Libdrm_INCLUDE_DIR}/libdrm") + set(Libdrm_VERSION_STRING ${Libdrm_VERSION}) + +else() + message(STATUS "FindLibdrm.cmake cannot find libdrm on Windows systems.") + set(Libdrm_FOUND FALSE) +endif() + +include(FeatureSummary) +set_package_properties(Libdrm PROPERTIES + URL "https://wiki.freedesktop.org/dri/" + DESCRIPTION "Userspace interface to kernel DRM services" +) diff --git a/cmake/modules/FindXKB.cmake b/cmake/modules/FindXKB.cmake new file mode 100644 index 0000000..2a2e451 --- /dev/null +++ b/cmake/modules/FindXKB.cmake @@ -0,0 +1,89 @@ +#.rst: +# FindXKB +# ------- +# +# Try to find xkbcommon on a Unix system +# If found, this will define the following variables: +# +# ``XKB_FOUND`` +# True if XKB is available +# ``XKB_LIBRARIES`` +# Link these to use XKB +# ``XKB_INCLUDE_DIRS`` +# Include directory for XKB +# ``XKB_DEFINITIONS`` +# Compiler flags for using XKB +# +# Additionally, the following imported targets will be defined: +# +# ``XKB::XKB`` +# The XKB library + +#============================================================================= +# SPDX-FileCopyrightText: 2014 Martin Gräßlin +# +# SPDX-License-Identifier: BSD-3-Clause +#============================================================================= + +if(CMAKE_VERSION VERSION_LESS 2.8.12) + message(FATAL_ERROR "CMake 2.8.12 is required by FindXKB.cmake") +endif() +if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.12) + message(AUTHOR_WARNING "Your project should require at least CMake 2.8.12 to use FindXKB.cmake") +endif() + +if(NOT WIN32) + # Use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + find_package(PkgConfig) + pkg_check_modules(PKG_XKB QUIET xkbcommon) + + set(XKB_DEFINITIONS ${PKG_XKB_CFLAGS_OTHER}) + + find_path(XKB_INCLUDE_DIR + NAMES + xkbcommon/xkbcommon.h + HINTS + ${PKG_XKB_INCLUDE_DIRS} + ) + find_library(XKB_LIBRARY + NAMES + xkbcommon + HINTS + ${PKG_XKB_LIBRARY_DIRS} + ) + + set(XKB_LIBRARIES ${XKB_LIBRARY}) + set(XKB_INCLUDE_DIRS ${XKB_INCLUDE_DIR}) + set(XKB_VERSION ${PKG_XKB_VERSION}) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(XKB + FOUND_VAR + XKB_FOUND + REQUIRED_VARS + XKB_LIBRARY + XKB_INCLUDE_DIR + VERSION_VAR + XKB_VERSION + ) + + if(XKB_FOUND AND NOT TARGET XKB::XKB) + add_library(XKB::XKB UNKNOWN IMPORTED) + set_target_properties(XKB::XKB PROPERTIES + IMPORTED_LOCATION "${XKB_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${XKB_DEFINITIONS}" + INTERFACE_INCLUDE_DIRECTORIES "${XKB_INCLUDE_DIR}" + ) + endif() + +else() + message(STATUS "FindXKB.cmake cannot find XKB on Windows systems.") + set(XKB_FOUND FALSE) +endif() + +include(FeatureSummary) +set_package_properties(XKB PROPERTIES + URL "https://xkbcommon.org" + DESCRIPTION "XKB API common to servers and clients" +) diff --git a/cmake/modules/FindXwayland.cmake b/cmake/modules/FindXwayland.cmake new file mode 100644 index 0000000..1c96518 --- /dev/null +++ b/cmake/modules/FindXwayland.cmake @@ -0,0 +1,39 @@ +#.rst: +# FindXwayland +# ------- +# +# Try to find Xwayland on a Unix system. +# +# This will define the following variables: +# +# ``Xwayland_FOUND`` +# True if (the requested version of) Xwayland is available +# ``Xwayland_VERSION`` +# The version of Xwayland +# ``Xwayland_HAVE_LISTENFD`` +# True if (the requested version of) Xwayland has -listenfd option + +#============================================================================= +# SPDX-FileCopyrightText: 2016 Martin Gräßlin +# SPDX-FileCopyrightText: 2021 Vlad Zahorodnii +# +# SPDX-License-Identifier: BSD-3-Clause +#============================================================================= + +find_package(PkgConfig) +pkg_check_modules(PKG_xwayland QUIET xwayland) + +set(Xwayland_VERSION ${PKG_xwayland_VERSION}) +pkg_get_variable(Xwayland_HAVE_LISTENFD xwayland have_listenfd) + +find_program(Xwayland_EXECUTABLE NAMES Xwayland) +find_package_handle_standard_args(Xwayland + FOUND_VAR Xwayland_FOUND + REQUIRED_VARS Xwayland_EXECUTABLE + VERSION_VAR Xwayland_VERSION +) +mark_as_advanced( + Xwayland_EXECUTABLE + Xwayland_HAVE_LISTENFD + Xwayland_VERSION +) diff --git a/cmake/modules/Findgbm.cmake b/cmake/modules/Findgbm.cmake new file mode 100644 index 0000000..0f4ef37 --- /dev/null +++ b/cmake/modules/Findgbm.cmake @@ -0,0 +1,104 @@ +#.rst: +# Findgbm +# ------- +# +# Try to find gbm on a Unix system. +# +# This will define the following variables: +# +# ``gbm_FOUND`` +# True if (the requested version of) gbm is available +# ``gbm_VERSION`` +# The version of gbm +# ``gbm_LIBRARIES`` +# This can be passed to target_link_libraries() instead of the ``gbm::gbm`` +# target +# ``gbm_INCLUDE_DIRS`` +# This should be passed to target_include_directories() if the target is not +# used for linking +# ``gbm_DEFINITIONS`` +# This should be passed to target_compile_options() if the target is not +# used for linking +# +# If ``gbm_FOUND`` is TRUE, it will also define the following imported target: +# +# ``gbm::gbm`` +# The gbm library +# +# In general we recommend using the imported target, as it is easier to use. +# Bear in mind, however, that if the target is in the link interface of an +# exported library, it must be made available by the package config file. + +#============================================================================= +# SPDX-FileCopyrightText: 2014 Alex Merry +# SPDX-FileCopyrightText: 2014 Martin Gräßlin +# +# SPDX-License-Identifier: BSD-3-Clause +#============================================================================= + +if(CMAKE_VERSION VERSION_LESS 2.8.12) + message(FATAL_ERROR "CMake 2.8.12 is required by Findgbm.cmake") +endif() +if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.12) + message(AUTHOR_WARNING "Your project should require at least CMake 2.8.12 to use Findgbm.cmake") +endif() + +if(NOT WIN32) + # Use pkg-config to get the directories and then use these values + # in the FIND_PATH() and FIND_LIBRARY() calls + find_package(PkgConfig) + pkg_check_modules(PKG_gbm QUIET gbm) + + set(gbm_DEFINITIONS ${PKG_gbm_CFLAGS_OTHER}) + set(gbm_VERSION ${PKG_gbm_VERSION}) + + find_path(gbm_INCLUDE_DIR + NAMES + gbm.h + HINTS + ${PKG_gbm_INCLUDE_DIRS} + ) + find_library(gbm_LIBRARY + NAMES + gbm + HINTS + ${PKG_gbm_LIBRARY_DIRS} + ) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(gbm + FOUND_VAR + gbm_FOUND + REQUIRED_VARS + gbm_LIBRARY + gbm_INCLUDE_DIR + VERSION_VAR + gbm_VERSION + ) + + if(gbm_FOUND AND NOT TARGET gbm::gbm) + add_library(gbm::gbm UNKNOWN IMPORTED) + set_target_properties(gbm::gbm PROPERTIES + IMPORTED_LOCATION "${gbm_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${gbm_DEFINITIONS}" + INTERFACE_INCLUDE_DIRECTORIES "${gbm_INCLUDE_DIR}" + ) + endif() + + mark_as_advanced(gbm_LIBRARY gbm_INCLUDE_DIR) + + # compatibility variables + set(gbm_LIBRARIES ${gbm_LIBRARY}) + set(gbm_INCLUDE_DIRS ${gbm_INCLUDE_DIR}) + set(gbm_VERSION_STRING ${gbm_VERSION}) + +else() + message(STATUS "Findgbm.cmake cannot find gbm on Windows systems.") + set(gbm_FOUND FALSE) +endif() + +include(FeatureSummary) +set_package_properties(gbm PROPERTIES + URL "https://www.mesa3d.org" + DESCRIPTION "Mesa gbm library" +) diff --git a/cmake/modules/Findhwdata.cmake b/cmake/modules/Findhwdata.cmake new file mode 100644 index 0000000..3525173 --- /dev/null +++ b/cmake/modules/Findhwdata.cmake @@ -0,0 +1,25 @@ +# - Try to find hwdata +# Once done this will define +# +# hwdata_DIR - The hwdata directory +# hwdata_PNPIDS_FILE - File with mapping of hw vendor IDs to names +# hwdata_FOUND - The hwdata directory exists and contains pnp.ids file + +# SPDX-FileCopyrightText: 2020 Daniel Vrátil +# +# SPDX-License-Identifier: BSD-3-Clause + +if (UNIX AND NOT APPLE) + find_path(hwdata_DIR NAMES hwdata/pnp.ids HINTS /usr/share ENV XDG_DATA_DIRS) + find_file(hwdata_PNPIDS_FILE NAMES hwdata/pnp.ids HINTS /usr/share) + if (NOT hwdata_DIR OR NOT hwdata_PNPIDS_FILE) + set(hwdata_FOUND FALSE) + else() + set(hwdata_FOUND TRUE) + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(hwdata DEFAULT_MSG hwdata_FOUND hwdata_DIR hwdata_PNPIDS_FILE) + + mark_as_advanced(hwdata_FOUND hwdata_DIR hwdata_PNPIDS_FILE) +endif() diff --git a/cmake/modules/Findlcms2.cmake b/cmake/modules/Findlcms2.cmake new file mode 100644 index 0000000..c488a0d --- /dev/null +++ b/cmake/modules/Findlcms2.cmake @@ -0,0 +1,75 @@ +#.rst: +# Findlcms2 +# ------- +# +# Try to find lcms2 on a Unix system. +# +# This will define the following variables: +# +# ``lcms2_FOUND`` +# True if (the requested version of) lcms2 is available +# ``lcms2_VERSION`` +# The version of lcms2 +# ``lcms2_LIBRARIES`` +# This should be passed to target_link_libraries() if the target is not +# used for linking +# ``lcms2_INCLUDE_DIRS`` +# This should be passed to target_include_directories() if the target is not +# used for linking +# ``lcms2_DEFINITIONS`` +# This should be passed to target_compile_options() if the target is not +# used for linking +# +# If ``lcms2_FOUND`` is TRUE, it will also define the following imported target: +# +# ``lcms2::lcms2`` +# The lcms2 library +# +# In general we recommend using the imported target, as it is easier to use. +# Bear in mind, however, that if the target is in the link interface of an +# exported library, it must be made available by the package config file. + +# SPDX-FileCopyrightText: 2020 Vlad Zahorodnii +# SPDX-License-Identifier: BSD-3-Clause + +find_package(PkgConfig) +pkg_check_modules(PKG_lcms2 QUIET lcms2) + +set(lcms2_VERSION ${PKG_lcms2_VERSION}) +set(lcms2_DEFINITIONS ${PKG_lcms2_CFLAGS_OTHER}) + +find_path(lcms2_INCLUDE_DIR + NAMES lcms2.h + HINTS ${PKG_lcms2_INCLUDE_DIRS} +) + +find_library(lcms2_LIBRARY + NAMES lcms2 + HINTS ${PKG_lcms2_LIBRARY_DIRS} +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(lcms2 + FOUND_VAR lcms2_FOUND + REQUIRED_VARS lcms2_LIBRARY + lcms2_INCLUDE_DIR + VERSION_VAR lcms2_VERSION +) + +if (lcms2_FOUND AND NOT TARGET lcms2::lcms2) + add_library(lcms2::lcms2 UNKNOWN IMPORTED) + set_target_properties(lcms2::lcms2 PROPERTIES + IMPORTED_LOCATION "${lcms2_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${lcms2_DEFINITIONS}" + # Don't use the register keyword to allow compiling in C++17 mode. + # See https://github.com/mm2/Little-CMS/issues/243 + INTERFACE_COMPILE_DEFINITIONS "CMS_NO_REGISTER_KEYWORD=1" + INTERFACE_INCLUDE_DIRECTORIES "${lcms2_INCLUDE_DIR}" + ) +endif() + +set(lcms2_INCLUDE_DIRS ${lcms2_INCLUDE_DIR}) +set(lcms2_LIBRARIES ${lcms2_LIBRARY}) + +mark_as_advanced(lcms2_INCLUDE_DIR) +mark_as_advanced(lcms2_LIBRARY) diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt new file mode 100644 index 0000000..690a113 --- /dev/null +++ b/data/CMakeLists.txt @@ -0,0 +1,14 @@ +add_subdirectory(icons) + +########### next target ############### +add_executable(kwin5_update_default_rules update_default_rules.cpp) +target_link_libraries(kwin5_update_default_rules + KF5::ConfigCore + Qt::Core + Qt::DBus +) +install(TARGETS kwin5_update_default_rules DESTINATION ${KDE_INSTALL_LIBDIR}/kconf_update_bin/) + +########### install files ############### + +install(FILES org_kde_kwin.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) diff --git a/data/icons/16-apps-kwin.png b/data/icons/16-apps-kwin.png new file mode 100644 index 0000000000000000000000000000000000000000..2ee63b13030fd0404186e336296107f61d3f15d8 GIT binary patch literal 380 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!60wlNoGJgf6n2Mb|LpV4>-?)JUISV`@iy0W0 zH-a$Z*XFmLKtah8*NBqf{Irtt#G+J&^73-M%)IR4& zz}19<>0!VJwuf!OMMXc_?s2QXKRG@9_lIb?-Uq8J->=Y(vvjz<#L{ijnxHo4UuKQE z60_3xXm9l>=3l5QF;g;YUzbhQ0-gg??$2eb`DnY1C6Uw4@_Cr%dCqMU9`xDhdx`c* z%o4Ks=n$QDBD?tP3FcS^t1lKyPm407+OH_|nIcgyx52e*6^r}}4xuM2sv9<_)*aKE z5xC%>vA36XLhuRJ0_PU*l1FR4H*;p|y)Xkdg00002b3#c}2nbc| zMg#x=010qNS#tmY2VwvK2Vwy@dYRh*000?uMObuGZ)S9NVRB^vcXxL#X>MzCV_|S* zE^l&Yo9;Xs0005vNklKS%rPu4 z0<;T>E0Lxt0_=8PI&0$&sTT~<6#A9kx$!I}ViE?^L4B z2zuf`xtro!@EiQL06;E4vs!#q=1OkBXDJuGl2Yl53hTYe4ftlO;Fy#$dlZ`_H-M{3 zy_FIKH&_%}&}X}Lq-=9Sp1x&)rnC}4P!$1A3xXF&sWk6^RffcYJWuD0 z81^y(3_546$2e&pzT0JkI1sGXtW&qaOaLyc!zWXiH)dQM_*YdF_~oFW!E0YVveP$fCqlyqTm*0JSR5*c&N%V(ZzwyR6AlUQT>8ZI)bH54k!>3 xR^tyxGrtNmv!)U?4R?enTckl-qFU`0gunM^Jq00&uW0}P002ovPDHLkV1iTW^>hFL literal 0 HcmV?d00001 diff --git a/data/icons/48-apps-kwin.png b/data/icons/48-apps-kwin.png new file mode 100644 index 0000000000000000000000000000000000000000..ea3606ec80419f8fce8e7e527f600ddfb9913986 GIT binary patch literal 877 zcmV-z1CsoSP)MzCV_|S* zE^l&Yo9;Xs0008)NklZ8?fs!!S~3E$p8lD7Z?_qkQ;7VYA272Xt{m1bAnYPU@`2p3_;O5h}e!k{!; zbic666T&F8=LPl)d!^ZR)$UU7cHvAia8RhUOIwO|YV(K?K1l`!MTxBvMhgRdCQ^67 z?&83pQnu&+28Q*S%H3q^)R+L2ZP)Y^1b*_C)2SPSCEba@>oTIGIxI40ZC+qbjf@xd zrs%Mvx~&r?VP9TgT-YYvS~UmUSL#clWs?Z2K~Y@IGGoCTc~%{^%OnlkZoFp zy~)6rq71#142%km)~HI|ZNgw-V2@HZSUT`G0A|V$d?e!p_}6|Fz9|>*g+D=W?ukWR zLWRll0Iyo*9v}Et4sb-sc%SkBr$n~i955hcyhpi!dG`son8*Tal3t%4pi_;na1+Q@ z>GPt%aZU++iUYr>_B1)*QI88%&J_U;Fd?kdqxWXR3BVy+)d(ZYzM@$V3$-Q-0(IsV zbckH-2|%;RHnt4licLa;9}57X+Y)0=XZtJx=(Saaa2E6nPlDex2`vSI(Bm4#&)^5u z4uf$m&HyfX!1G0b(5O>iw&7%e1FC#y`DT3#MS$hCCj(d9E8J-Uz^GOkC&~jX*(b79 zX>;6BZ7M~!fpP$Uc}Te1I|ke#gtcDqsi%Yo&6N)DyMBLwcZJXe81_ABfaLuwgx3Hl zH$ayTcC00000NkvXXu0mjf DC=P*` literal 0 HcmV?d00001 diff --git a/data/icons/CMakeLists.txt b/data/icons/CMakeLists.txt new file mode 100644 index 0000000..6690f28 --- /dev/null +++ b/data/icons/CMakeLists.txt @@ -0,0 +1,11 @@ +ecm_install_icons( + ICONS + 16-apps-kwin.png + 32-apps-kwin.png + 48-apps-kwin.png + sc-apps-kwin.svgz + DESTINATION + ${KDE_INSTALL_ICONDIR} + THEME + hicolor +) diff --git a/data/icons/sc-apps-kwin.svgz b/data/icons/sc-apps-kwin.svgz new file mode 100644 index 0000000000000000000000000000000000000000..5248264d2a60620e92918c0e81ac0175856abb08 GIT binary patch literal 3106 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 literal 0 HcmV?d00001 diff --git a/data/org_kde_kwin.categories b/data/org_kde_kwin.categories new file mode 100644 index 0000000..dfc9120 --- /dev/null +++ b/data/org_kde_kwin.categories @@ -0,0 +1,23 @@ +kwin_core KWin Core DEFAULT_SEVERITY [WARNING] IDENTIFIER [KWIN_CORE] +kwin_utils KWin utils DEFAULT_SEVERITY [WARNING] IDENTIFIER [KWIN_UTILS] +kwin_virtualkeyboard KWin Virtual Keyboard Integration DEFAULT_SEVERITY [WARNING] IDENTIFIER [KWIN_VIRTUALKEYBOARD] +kwineffects KWin Effects DEFAULT_SEVERITY [WARNING] IDENTIFIER [KWINEFFECTS] +libkwineffects KWin Effects Library DEFAULT_SEVERITY [WARNING] IDENTIFIER [LIBKWINEFFECTS] +libkwinglutils KWin OpenGL utility Library DEFAULT_SEVERITY [WARNING] IDENTIFIER [LIBKWINGLUTILS] +libkwinxrenderutils KWin XRender utility Library DEFAULT_SEVERITY [WARNING] IDENTIFIER [LIBKWINXRENDERUTILS] +kwin_wayland_drm KWin Wayland (DRM backend) DEFAULT_SEVERITY [WARNING] IDENTIFIER [KWIN_DRM] +kwin_wayland_framebuffer KWin Wayland (Framebuffer backend) DEFAULT_SEVERITY [WARNING] IDENTIFIER [KWIN_FB] +kwin_wayland_backend KWin Wayland (Wayland backend) DEFAULT_SEVERITY [WARNING] IDENTIFIER [KWIN_WAYLAND_BACKEND] +kwin_wayland_x11windowed KWin Wayland (X11 backend) DEFAULT_SEVERITY [WARNING] IDENTIFIER [KWIN_X11WINDOWED] +kwin_platform_x11_standalone KWin X11 Standalone Platform DEFAULT_SEVERITY [WARNING] IDENTIFIER [KWIN_X11STANDALONE] +kwin_libinput KWin Libinput Integration DEFAULT_SEVERITY [WARNING] IDENTIFIER [KWIN_LIBINPUT] +kwin_tabbox KWin Window Switcher DEFAULT_SEVERITY [WARNING] IDENTIFIER [KWIN_TABBOX] +kwin_decorations KWin Decorations DEFAULT_SEVERITY [WARNING] IDENTIFIER [KWIN_DECORATIONS] +kwin_scripting KWin Scripting DEFAULT_SEVERITY [WARNING] IDENTIFIER [KWIN_SCRIPTING] +aurorae KWin Aurorae Window Decoration Engine DEFAULT_SEVERITY [WARNING] IDENTIFIER [AURORAE] +kwin_xkbcommon KWin xkbcommon integration DEFAULT_SEVERITY [WARNING] IDENTIFIER [KWIN_XKB] +kwin_qpa_plugin KWin QtPlatformAbstraction plugin DEFAULT_SEVERITY [WARNING] IDENTIFIER [KWIN_QPA] +kwin_scene_qpainter KWin QPainter based compositor scene plugin DEFAULT_SEVERITY [WARNING] IDENTIFIER [KWIN_QPAINTER] +kwin_scene_opengl KWin OpenGL based compositor scene plugins DEFAULT_SEVERITY [WARNING] IDENTIFIER [KWIN_OPENGL] +kwin_screencast KWin Screen Cast Service DEFAULT_SEVERITY [WARNING] IDENTIFIER [KWIN_SCREENCAST] +kwin_xwl KWin Xwayland Server DEFAULT_SEVERITY [WARNING] IDENTIFIER [KWIN_XWL] diff --git a/data/update_default_rules.cpp b/data/update_default_rules.cpp new file mode 100644 index 0000000..6399d5d --- /dev/null +++ b/data/update_default_rules.cpp @@ -0,0 +1,57 @@ +/* + KWin - the KDE window manager + This file is part of the KDE project. + + SPDX-FileCopyrightText: 2005 Lubos Lunak + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +// read additional window rules and add them to kwinrulesrc + +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + if (argc != 2) { + return 1; + } + + QCoreApplication::setApplicationName("kwin_update_default_rules"); + + QString file = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString("kwin/default_rules/%1").arg(argv[1])); + if (file.isEmpty()) { + qWarning() << "File " << argv[1] << " not found!"; + return 1; + } + KConfig src_cfg(file); + KConfig dest_cfg("kwinrulesrc", KConfig::NoGlobals); + KConfigGroup scg(&src_cfg, "General"); + KConfigGroup dcg(&dest_cfg, "General"); + int count = scg.readEntry("count", 0); + int pos = dcg.readEntry("count", 0); + for (int group = 1; + group <= count; + ++group) { + QMap entries = src_cfg.entryMap(QString::number(group)); + ++pos; + dest_cfg.deleteGroup(QString::number(pos)); + KConfigGroup dcg2(&dest_cfg, QString::number(pos)); + for (QMap::ConstIterator it = entries.constBegin(); + it != entries.constEnd(); + ++it) { + dcg2.writeEntry(it.key(), *it); + } + } + dcg.writeEntry("count", pos); + scg.sync(); + dcg.sync(); + // Send signal to all kwin instances + QDBusMessage message = + QDBusMessage::createSignal("/KWin", "org.kde.KWin", "reloadConfig"); + QDBusConnection::sessionBus().send(message); +} diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt new file mode 100644 index 0000000..c305cdd --- /dev/null +++ b/doc/CMakeLists.txt @@ -0,0 +1,9 @@ +ecm_optional_add_subdirectory(desktop) +ecm_optional_add_subdirectory(kwindecoration) +ecm_optional_add_subdirectory(kwinscreenedges) +ecm_optional_add_subdirectory(kwintabbox) +ecm_optional_add_subdirectory(kwintouchscreen) +ecm_optional_add_subdirectory(kwinvirtualkeyboard) +ecm_optional_add_subdirectory(windowbehaviour) +ecm_optional_add_subdirectory(windowspecific) +ecm_optional_add_subdirectory(kwineffects) diff --git a/doc/TESTING.md b/doc/TESTING.md new file mode 100644 index 0000000..075d217 --- /dev/null +++ b/doc/TESTING.md @@ -0,0 +1,33 @@ +# Testing in KWin +KWin provides a unit and integration test suite for X11 and Wayland. The source code for the tests can be found in the subdirectory autotests. The test suite should be run prior to any merge to KWin. + +# Dependencies +The following additional software needs to be installed for running the test suite: + +* Xvfb +* Xephyr +* glxgears +* DMZ-white cursor theme +* breeze window decoration + +# Preparing OpenGL +Some of the tests require OpenGL. The test suite is implemented against Mesa and uses the Mesa specific EGL extension +EGL_MESA_platform_surfaceless. This extension supports rendering without any real GPU using llvmpipe as software +emulation. This gives the tests a stable base removing variance introduced by different hardware and drivers. + +Users of non-Mesa drivers (e.g. proprietary NVIDIA driver) need to ensure that Mesa is also installed. If your system +uses libglvnd this should work out of the box, if not you might need to tune LD_LIBRARY_PATH. + +# Running the test suite +The test suite can be run from the build directory. Best is to do: + + cd path/to/build/directory + xvfb-run ctest + +# Running individual tests +All tests executables are created in the directory "bin" in the build directory. Each test can be executed by just starting it from within the test directory. To prevent side effects with the running session it is recommended to start a dedicated dbus session: + + cd path/to/build/directory/bin + dbus-run-session ./testFoo + +For tests relying on X11 one should also either start a dedicated Xvfb and export DISPLAY or use xvfb-run as described above. diff --git a/doc/coding-conventions.md b/doc/coding-conventions.md new file mode 100644 index 0000000..42bb4bf --- /dev/null +++ b/doc/coding-conventions.md @@ -0,0 +1,86 @@ +# Coding Conventions + +This document describes some of the recommended coding conventions that should be followed in KWin. + +For KWin, it is recommended to follow the KDE Frameworks Coding Style. + + +## `auto` Keyword + +Optionally, you can use the `auto` keyword in the following cases. If in doubt, for example if using +`auto` could make the code less readable, do not use `auto`. Keep in mind that code is read much more +often than written. + +* When it avoids repetition of a type in the same statement. + + ``` + auto something = new MyCustomType; + auto keyEvent = static_cast(event); + auto myList = QStringList({ "FooThing", "BarThing" }); + ``` + +* When assigning iterator types. + + ``` + auto it = myList.const_iterator(); + ``` + + +## `QRect::right()` and `QRect::bottom()` + +For historical reasons, the `QRect::right()` and `QRect::bottom()` functions deviate from the true +bottom-right corner of the rectangle. Note that this is not the case for the `QRectF` class. + +As a general rule, avoid using `QRect::right()` and `QRect::bottom()` as well methods that operate +on them. There are exceptions, though. + +Exception 1: you can use `QRect::moveRight()` and `QRect::moveBottom()` to snap a `QRect` to +another `QRect` as long as the corresponding borders match, for example + +``` +// Ok +rect.moveRight(anotherRect.right()); +rect.moveBottom(anotherRect.bottom()); +rect.moveBottomRight(anotherRect.bottomRight()); + +// Bad +rect.moveRight(anotherRect.left() - 1); // must be rect.moveLeft(anotherRect.left() - rect.width()); +rect.moveBottom(anotherRect.top() - 1); // must be rect.moveTop(anotherRect.top() - rect.height()); +rect.moveBottomRight(anotherRect.topLeft() - QPoint(1, 1)); +``` + +Exception 2: you can use `QRect::setRight()` and `QRect::setBottom()` to clip a `QRect` by another +`QRect` as long as the corresponding borders match, for example + +``` +// Ok +rect.setRight(anotherRect.right()); +rect.setBottom(anotherRect.bottom()); +rect.setBottomRight(anotherRect.bottomRight()); + +// Bad +rect.setRight(anotherRect.left()); +rect.setBottom(anotherRect.top()); +rect.setBottomRight(anotherRect.topLeft()); +``` + +Exception 3: you can use `QRect::right()` and `QRect::bottom()` in conditional statements as long +as the compared borders are the same, for example + +``` +// Ok +if (rect.right() > anotherRect.right()) { + return; +} +if (rect.bottom() > anotherRect.bottom()) { + return; +} + +// Bad +if (rect.right() > anotherRect.left()) { + return; +} +if (rect.bottom() > anotherRect.top()) { + return; +} +``` diff --git a/doc/desktop/CMakeLists.txt b/doc/desktop/CMakeLists.txt new file mode 100644 index 0000000..3a90981 --- /dev/null +++ b/doc/desktop/CMakeLists.txt @@ -0,0 +1,2 @@ +########### install files ############### +kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kcontrol/desktop) diff --git a/doc/desktop/index.docbook b/doc/desktop/index.docbook new file mode 100644 index 0000000..72bbe35 --- /dev/null +++ b/doc/desktop/index.docbook @@ -0,0 +1,84 @@ + + + +]> + +
+Virtual Desktops + + + +&Mike.McBride; &Mike.McBride.mail; +&Jost.Schenck; &Jost.Schenck.mail; + + + +2021-04-09 +Plasma 5.20 + + +KDE +Systemsettings +virtual +desktop + + + + + &kde; offers you the possibility to have several virtual desktops. + + + You can change the names to the desktops by clicking on the Rename button (appears when you hover the desktop item in the list) and entering text into the text field. + + + If you want to remove a desktop, use the trash overlay icon at the right of the desktop item. + + + Press the Add button to add a virtual desktop (default name is New Desktop) to the list. + + + You can configure the number of rows in the pager item on the panel. Just use the rows input box below the list to adjust the number of desktops. The desktops will be rearranged by the rows automatically. + + + + +Navigation wraps around +Enable this option if you want a keyboard or active desktop border navigation +beyond the edge of a desktop to take you to the opposite edge of the new desktop. + + + + +Show animation when switching +Select Slide, +Desktop Cube Animation, or Fade Desktop +from the drop-down box or switch animations off by unchecking the item. If the selected animation has settings options, click on the +tools icon on the right of the drop-down box to launch a configuration dialog. + + + + +Show on-screen display when switching +Enable this option if you want to have an on-screen display for desktop switching. + + + + +Show desktop layout indicators +Enabling this option will show a small preview of the desktop layout +indicating the selected desktop. + + + + + +Scrolling the mouse wheel over an empty space on the +desktop or on the Pager icon in the panel will change to the next +virtual desktop numerically, in the direction you scrolled (either up or down). +You can change this default behavior on the page Mouse Actions in +the Desktop Settings (&Alt;D, +&Alt;S). + +
diff --git a/doc/kwindecoration/CMakeLists.txt b/doc/kwindecoration/CMakeLists.txt new file mode 100644 index 0000000..932e9b3 --- /dev/null +++ b/doc/kwindecoration/CMakeLists.txt @@ -0,0 +1,2 @@ +########### install files ############### +kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kcontrol/kwindecoration) diff --git a/doc/kwindecoration/button.png b/doc/kwindecoration/button.png new file mode 100644 index 0000000000000000000000000000000000000000..4880b8efa35733e0ebdb7253bb893efe9e70e291 GIT binary patch literal 26082 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

qrdy5-(Q*i?k|#QbE9_&(8McV>OugUuV{G( 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

  • (|3d1&!3syN`lByvp!0~mbYqyMRX1oIic$QW; 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 literal 0 HcmV?d00001 diff --git a/doc/kwindecoration/configure.png b/doc/kwindecoration/configure.png new file mode 100644 index 0000000000000000000000000000000000000000..4d1f26332bfffd504cd2fbe85bbea159aba639d9 GIT binary patch literal 1080 zcmeAS@N?(olHy`uVBq!ia0vp^vLMXC3?vhMr`s|xFf#=Bgt!8^j9|bF2CS^CY;0`o zV896mTwGkdyu5sTd;$UjLPA0xKYb>IK7IHIL?7Q{L+?I(grfH!-(f)?-n|2&_ith7 z&AWFf=Pn>TM?gV38-xX|l2uYl+kR`mMiD+qe^@)ZcZc=hVpix*Fy zKY#r6>4Qg)Zrr+c?Z(ZEmoJ~YaPiF9bEi(9IeF^z@sp>H9zSvT=&=I_5AEHzf7hP9 zTet53qD@=2Leb_e8zE@Z=8aIaX~U*X8#aQ_`i&c*Xv4;J5VU^7Iv`qyiPo=O3qotw ztXsWi-Ky1VSFKvJa@CsUD^>x~(q$_aFI~21@zVJV7tNi&VAhe~A9%BuXrqTKv~jO?7$^vvYc^n~P;xWuH`_=K3a_~_WUC?JZ7jRd0T7$AxO zqNr#f3J0P{Ac_i$hzt#n2nr4J4-E45^>g#^baZjGb#Sz{wX?9cHnX%cwXg&u3sVaO zGO@4#A#*4)Ha7<$GY~R@A~PdXC^9rPgCY}C17j0?BV%1dLoGdhO&wizEp1f|4Hb0_ zWi@qdNKIKyO<7e<34~M?Ra6y~RTPw!dL@dAP*#q-{ zKuM5aFoS@CfkQw-LBoUv8;)Fl@>yk}Xa!L2x~Gd{NX4zCCmGY*9Au7vtQO^$;5oo> zZf{$%e&T@~)p>f^!9fZW8JNWqnavI)@bDUn8S*q%bN|1-VX=F;_u>Rig zv`xk8fvbw$;qLD*E#rJ9iTawE?A>5hXZ33dw{71A%eFLbmS$t`zw^aH1Qfx zdmLQ;;AU=HL$oB9f@zj(a8%r*snStvGhA0`@2UOuAU3&Dap&IpBV76a7&#AooHlvx S``17(GI+ZBxvX-aJR!iqz#xLEDd}NgV1e&H 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 literal 0 HcmV?d00001 diff --git a/doc/kwindecoration/index.docbook b/doc/kwindecoration/index.docbook new file mode 100644 index 0000000..08c1cd4 --- /dev/null +++ b/doc/kwindecoration/index.docbook @@ -0,0 +1,146 @@ + + + +]> + +
    + + +&Rik.Hemsley; &Rik.Hemsley.mail; +&Anne-Marie.Mahfouf; &Anne-Marie.Mahfouf.mail; + + + +2021-04-09 +Plasma 5.21 + + +KDE +Systemsettings +kwin +window +border +theme +style + + + +Window Decorations + +This module allows you to select a style for the buttons and borders around +the windows. + + +Window Decorations + + + +Window Decoration Configuration Module + + + + + + Window Decoration Configuration Module + + + + + +Choose a window decoration style from the preview list, using the +search field at the top of the screen or download a new style using the +Get New Window Decorations button. + +The default window decoration is called Breeze. + +Each style has a different look, but also a different +feel. Some have (sometimes invisible) +resize borders all around the edge, which make resizing +easier but moving more difficult. Some have no borders on certain +edges. + +You are encouraged to experiment with the different styles until +you find one which best suits your pattern of work. + +In the preview of each style you find a + +configure button to open configuration dialogs for the decoration. + +The options in this configuration dialog are applied to all windows. +Some window decorations (⪚ Breeze) +provide a Window-Specific Overrides tab. +On this tab you can change the border size and the visibility +of the window titlebar for particular windows. + +Different options for particular windows you find in the &systemsettings; +module Window Rules. + + + +For accessibility purposes, some window decorations support +extra wide borders. If this is available, you can also choose a +Window border size here. The large borders are easier to see for low +vision users, and easier to grab for people with limited mobility or +difficulty using a mouse. + + + + + +Decorations + +In this dialog you can change the decoration of the window. + +The available options depend on the selected style. + + + +Breeze Decoration Options + + + + + + Breeze Decoration Options + + + + + + + +Titlebar Buttons + +This tab allows you to customize the button location on the titlebar. +You can drag buttons ⪚ the Application menu into the +titlebar, remove them or drag around the +buttons until you have the order that makes you comfortable. + + + +Button Options + + + + + + Button Options + + + + +Enable Close windows by double clicking the menu button +to have an additional option to the Close button or if you have removed the Close button from the titlebar. + + +Check the Show titlebar buttons tooltips item if you +want to see the default tooltips when you hover the titlebar buttons with the mouse pointer. + + + + + + +
    diff --git a/doc/kwindecoration/main.png b/doc/kwindecoration/main.png new file mode 100644 index 0000000000000000000000000000000000000000..b86982a50408ab74b4e1d9dac74aa637bf528023 GIT binary patch literal 35633 zcmcG#byOSQ6F7>sNP!XvPLW{2OL4aZx8P8$K!GAfinKVCLJ00&AhIo&?FL2r*wPuGraj*pKIkB^VgyZ;)8NB^4p z$0tWehx_RMqwRykWS1&EE{P=zT zKm4`0Sif|!xU~Fr>7w-4`P{HMi<)Z|Ru)cM53 z#NhZO3WXXS`-j7$BaK6g!^6WpgI{_Fh6V-(!uk*U`ue*22GHBXKDDmy?(dyHI=+32 z{kHVAy`$~x*FRtV>svaSn_C=Pb{d)*>*^cOm|y>0y6#ZAc3-Y`SFLutwzdw9QdPVE z!|zoQWmR=mRn?LeTZR=|lI0s^!T zzP=3~pzpjssJZ94ySX|zyS#bx=7nuB^0lOum9>GTy^fiUu9>Z#xg8o|rZzgJHXbjv zv`wtEj9+ORIcpkPzBA-e(>GH$Fw-zFkI@%Y)qAO+=b@r&VytVbsBQQk8Z>L|DyZve zsl#|xBji-Hq#&Boa!@I0`TqyCrDY|hBsQot?RXrlOPxKQ}uwJuMYE>C?yf1jIO)xR@9im3^nD`y{4PMKNvvsBv29vBr~t13=W?uC?N#^pkVpDbLJoUgyC>||L7NVAmq8w zpey?9EEpREGGWNy%Te=ClLjGPHUObl3Xn;ClQiUq#{d9`M+JpN;IH)@Wb!?~2VC~+ zd-!(qbDj_wMfkSmV*tt{dKDP$iuVbA1h$7!#M@WM%S3=zdVTbW$n)*FAzgpJlEQuv zl?zhj<3Ug9=0#*7xq@2x*T4@X)WpVk_l(GbdfDAs`xL0LGBTEsi3G*3e$x-@M`9xU z;0+2&vT%5T;^cF8jtnLgOIQ^o$v#o^gd3r=F3UoNmD{d3)fN8{76l3hVd+ zvQyLw{sh@%(eN9|-C^eFcxUwWJn>=uw*4gAFG&IM&Iz6IKa`Du$o^%>xmgL54q@I{ z0H{v#1%KI_BX3wJhzG`;yQ3W|Ye5zT_fdZ#KzY4*G8FzQHPi#OtWhv6Astx;)qF7#A2RAibCCi)TGmN zUW}xKd82Fj7gU3pn>ueh21@5*#<29b==8%Z)!vrtHn zgvgkNR-G24A^)?@n8u+x`441djdHgADMzc&9BE_U+|J+k%t-9Z=A3!vk*4h#V4-r3 zoer{&`@jX_adZGRR*l>ve64a4Uf3Dnzr9Z@o4VyZB<~u#?6~CQ(LmXJGnIqtjQBL?fK7gAeI_{i z0zY*b7PZaF%EH1pNyQ`}kmlzmd&2o`1a^bxTi6XBlj~liXV)S@26rk=pf*GIj$rjap zLhMr2J&p%`%P}y%n;V8zEG)s-HCB))poV5-RT*dwA`fLI<8O{INRjM(O?EB|ebT3I zy3?9(dDsdzT3T)C2*)tB_V)Lmb!``u{WD1Q#~x^Gvch}waUH1)t&7~$i_ZccCp11s z4eA&kovEj7>n}sv4Hre8{w`cttSYCgjlDJ~-c;?>edNtPsRulNOXO*Xtg7!?+Y$M) zXMy-BF|WAeJ~ilBZ27i4lbk!2j~=U;=IzdRH?ytWb+78EOIg@KeP9;u<(y14myrp8 zceT(_j3>hoS{DJ)IqOV|NJA{qmTvEHpM5;Kj-=!ZIQv6fVh`IBYF)g@es;jD6r-*W z!(=Ki5L@KPQsqojee%VrFN^BOJiy&#(*+h^n9=_>Hw_UaR>Nt&%yw(wZR>P9A!Mj+ zr}b*jvpGTWxbQL!(QA4ZtQ=`WX$FNRdWi%u>Is5@wX^1_@ZWoTIS*7vU#4d2>z5v3 zQplcSGDX|AD(neK%Q3eG#EpuDzHjyVQfWEiY|~h#R$(UZkk0!?AL){emtUu#tTblh z$jj7R@!BEO$b0%3#E5U&<4LN>i)k_|PK0|Vu;P_A;R2%>TIu^Nn^Z+ zZ2N8>^BFjFz>XhOem`C>Uv`X+yyt|$LE{OK$5jLb~7Cp0-W8PLS$ z4Ug9K1Pe@Au(+W{%N!tw4#N9NU{;x^QCZ#>6%1nzDZFfsOrr=zP$gc7jaDMJi{*GT zm32^oqhoxJvqZG3aXC#0n|9+9?!T{it#FE)I+9sulB~siBv6O;vJ21qLtM)g+^Hd) zmHgy$re?Qz`+o{Ds~K%bl3utv|4E`NGq{v+0_nXnDS5a2-?ZW#t!xscHHE_icdkiR zrW`}1y7A3#DZQK~M%{v6t2-6)i>ZInF4T6f(3|R8yTgA4e=pea9*=AKC=P+I!{e8m zzFreji_e(>82jm7_?TUyCOX>_F5B?a3rOz7g-z*3=GMeYZsvvJOzagZQ`SWE8jk%l zmXhB+*Vlo*)kXMH2|8D~uj zVk4xl>A3p!%~Wd@_g_96EHUIbe`|bpR4s^AUE3>ut6dW&#~&KD9n{xYWL&!q@n9{k zm&+!K6~5#cyy$zg^)s*Ywz6^iRm3N9$Qx7v-Ip{@>E5pYO=k_(Y7J}eeLJOY@)l-k z;F=d&LRcRys)5%(k}+X7c6yN{O+)^ioS!nf8aeO=^@}qj0a)ou&cj}kYf%#*)oZ1P z12owdn6HX!(2y{NDphDGK%hyb_?KgM+Rk&Y%fs}w3;;Se&6VLv^~+Y_QnumZ629jW zl8{)?6L>CFh;T!hC(Xb|8ez8`o0yT0z}ro!%*~wfgV7vM-&|zfkJ3xrF4feDvT=eJ zs*W*mrM1ZL8tJuV!EJh7?n~#F_YkfBQM)A;I4 zzLSP<>VZ0HaVQzUrqt?#qzuI(%E2@tI#bPtjMyK3Z{IH9{$)UWf>V~Lw3zsB=lZ^u9G<&g?C!>K@um5~2=n(%ymet;IM2TtXEM4dK zZ~$AnOh@>&cg_p0Z!L7h0{6Bw0=)c_c*Op0GSj~nRJ$|cxOqExEOx;Yo3k71_szHb z*}0DMbw^jn{P-NSGvT8Xo?>|PbL@TKv0E!Fx@xb&W$S0lG1Oz^wrkK*wi87OqL1dI z|G`=FZtm!hkZ|io_tq~Ff6K>FDKhxxpD*}=>5d2FI%L0@HqH3US$$Xsaqz5O#Obpf z)9MM{Nd0-Q(=ey;P<56I`BJD--hy|taG|5HAY0*X!4BPe?A|M{t zpxvj(!G!7al@!g(cK zKd2kx6PTyB^{=S6dxzL%C;xT$lxr`>fouKe#=@>wVi;(N!9p@r-_l#}CwAtXdcEbR z5v}P;gJ!&-ToxBM?nily@xV9n1<2`mgy_2xWfPf5cId!*bUfmU;txMrFajf8k>{&z zzc_&%u4~4M-|wgwijQMKA_)WUO<1b)Mkwa_5IiT-U+lbUa-w4S)tT9N!bwP$}CWMs0<#j4NZKHMIz!1P=ov4|IUs5oq|JcgCbNJoc9=Gsle_efz_S7#UA{ zj!rU$hVhazv|Qv7NKaBd9E|VHhy;Wv{d;{Ctgs-nsXiltw%EM!%hu{B`l%<=HrwBRis2gv}*(#=oB1MZD6pu&=<80-ijNH_RRp^~{-2F8+ zML*LBLLeKq?~Q7=2{^4lh;sWg&I&L&t1S8qgi{r6h1yR3nTDX6EdqTA@LJ0z!oZM) zimX%5XTnfr1xRvj00zdSUIVDq-ZOxFAP|G+1**CftruVuU+har0~dDmue6N z*Xd}*Bbw2g#vqSqP%cr<$Qvm}#DMMDrh)@iUe%WeYy8hBei*fXTsW)XI z|1!M<85@|7khU7uY5dFJ>y0tr>)Gc<$LK6SDJ+ZqOdA7?i3PzRNp#`~l?#&o?)_*N zQ^ghAP5#9>g$ZBTq_`(tHA}tqiv-bjq%$Vfb6uPY;OplU11Zy=40LHYmMn$1cqD#3 zPvi3nPCQ)PZ+}p^h~%?dXyR4ANJTPKsH2J+qavbwb>*p(ky)8iGDdq( zbQ>;uTllzJDU{bw-L%iHLS^Gi#DI^MN$Q#%G=R=*2m=`j0j~T>c74Qrc`~tLy2a^S zMG6`G=c8+D*{rzw$e2v2?z60|l*%Yu^$&innNW`z){tK}n@(_$7*^(iwYb^!>7m2w z;0OVec0)SY#Pkbs2YW#&(Ah=1!8sWSRW3+9>HCDu34L^{#~7a|C9zA$MJ9;>vG*JmLV+q#2}Pj&PpSew#kRab?=5>e$? z$=kNPb&h^f5xrNqd#l4((2l6$os=*kI@6Yy&y9ApSz5i*VX23mNTG09RAMC1*%<`w zTtLWMtd#JQLv?|y@r>JGf$X=*prWTsS?-gbd~7M&ETfcv@uCIU0@uG*zx_IZTxF+v z=bKj2_j(Vf?lVX#?m-7om(OYg#NQ;OY2`w46ZJ-u3C0TCxHs zv@--%t5YbDfb7N5DDNVbl53k!%QdUi8rMU_OloF>M0(D2p3CX-gg`V1gF)v26Eg5T zJrre&w4bIxX@{maCbc@Bv_4zR3F^Em+5>pf6e-p|UNM})??8yD{;rd3x3Q6AX zX*u|S?kJ~#X!4#qS zx}Rk-rPx9nJ{*h~qH!tb(nb%1WLfQ>H5Y&@D`PnKxbK>^%82RTT(jZh8 zrR$~({M!UVZr9!<#CgkfrG_@};vnRgji!7MB=Wg7B!iRm$!ip)si7a zG=(6yNwAr6@xd9-Pc03H_nw1I|rOxmI7!DB(7 z(D6Z_)Fdf_bU9zM&Nd9)nTtNz@CeO=zQA715ll)dg^A|sG)eXYq&=nge+0lmpy}fQ zHLA4B|08_5h35NzBxAG3lPZ%%9U7tqJ;TF-%DhB#8_&lBwl_Pud<2Ek%jIrYZg7Y{ zLHv)~*@GNvfiFyr;&9KI2a4u?h8iBeieK#fn{M!*m=U$<>Ar^y(#^DJg8f*~L+*I{ z<*iFQTWK-BtKa4L*MB55iu%tuGk$KT2I`IpiBN4sLs^MBsPjhWw|B z%h6x=w_g%jP(!}nf;q^79^JF*fGRIlr*{G70)IVTDF9m*ttNGk-kU5?LC?Y)SkNm- zt&e<`Gd8mL?e?@4(ncnc(P1!UzWU~-&tm=lL_}t!BwD9?{z=DTBPPOxMm~4DLKQXA z($d1`1B}S{`#h&tPQ7|Q-&~QG`q(^!av(hecJe;m2&ME)I+Fs}%b*3Di8(}9H zkt&(7_9huLUyBF4w+?T!I0sk_Ab;l2p+eeCjPJ-jnB97kq{W5C>^7L%cZWjKkv?5s za=?4m>d4t=V`9?T48CZqSYFJz-3tZ52!ZM58*xYZR-$Aym2^VjjHX{I>{N5>lG0)u z8l=#oz6bsdl~07%z7q+>4XN)BbrADULQMy}dmI-7ehhdYE|HbP++8lJn%|4?7#JUX z>(HS7d^;wwI(+Rf2?w#n@G#=i7Xv2k zk&9o!Qzhi9Hr%(KD2#aw4cGurmP=~1o~e@hMI^5g4?S?kOj(Ha>FzbNdXXbavMf;R zvH9zZ{9^MQ4Ay&Bg~SF&8;4f1({duD)zeW=dyJI$j=mQ0zAP6>YHnf95Orc8$6fcF zYY{Q}ie@}IG8Q;nuN4obKzKx63M4-LLir0gGD8DwDGht9K-Zf=mZ3=$-$XRa7z!7t-J=w!8DgU;Be*_+&cpT}eKvO;ajWk$xD3ypTgnS|vaEG4vS`qU`IJzc?BPm$+%;9=z2rlMD2BDi#2rbJIUt z@a1pZ4Szf=XE7mcG3c=$hw>CEx4e>g_7rR-Hnue>vidm(O+Da#qtaqxUZCQNvs!_8tF*ItO8 zeA;It6oX`^jzwQxNL>P5$34-{gMsJPN zkcCfAm3GHwdYDY+RZIe8!&PAk(pPd%6i>_tv|2+y1au_UGON*VPDa~@7U1XGSAvyT zCK!-Q+J*hiynXsD&7bqpWUKtZZC?9>$y}+tW;AHfjtmC9Tgk5p*q`bGBfp3)nfXPo zEg$%pX31BVjMG+q;M`rS`9UlF!LFA~8bwQc_$xV4U_Sqg=wlIN8{$+llg&Qt=YwO) z(rU>+PG`Rc7IZrn`mFGov>_Cwq&;eYVEG!B%AkVh(3_IAaG5K)>i+Tmz5{|l73F)1 zR>{C7Nq6wo@9?pYPXmB-2Ps;d@3=oa{URxQ+Tc~oaNHUD7&?%td%AkZrW{HFb}(b7 zMhA4Mj{lMi7~Kp8p}Vo5c)_6mf^d<4?uaToJ?S&^dwp3HCwjm>_t6R-Li*rIwnQx# zK$@-~bv@IQcw7vflNui(&V0X|wi$?hX0io2aLvsw)7~{a0Wvkj$i^G<7qzj_K#Nlr z^PS#=G)-o$*3X%pyxba+(OBOe^4+fKD!wI^P}`We+jEu8NX+BO7BKmv$wF9cVfsl? zZ6iMyERHS`$61zb@zvEoKg6#P^1a1I5VGH1{6079WzhU)SosygO#k6$#cN+xui4QA ziLWAdYIfZ#5%=zGu9_e_hB!lxWWKC&%{@#g3cYfK#fHCYm3~j~0896bf09D24hwq( z+GvOOxb-jK%d`tip~D`(2551y5bJ;D%dX8v;WJtMeP={DDsNk^}YK+gEVHb zQa+ViX)S<&hbVK?^j9jK{e;sRoN3|rleO4Y-JMA+s-``_>ZEG3bfnq{A=8;ZvZPW~ zyCAjL-mX)+vClQ75dI>|Pc~kZ*>Fw^INe&~)b5A(O3y}=*={r|FTz;0f|lc@XNcli z+G%MaSpx~w*&rL4-GX2>S0HP6rQP6%4(C~PI0K(RiK$wd@J>kd*r6 ztB5+MN}VaM+urXjSPVt))?p&ax#dZFPh5xsS{Z<7RUo(Q&e#|iRWiLg-|aAU{49HV z`mHGZTK{s{T9fmY5!wm3fCJTJw`2XEf77{b48}$vL(NJ0>iN*tB8%Ehsis}9ow9vx zaz`zQww{Tetm?*p1_gULJ#z4$E%~Y7$=fpngOMM<4wl3&gBlMeekH2+Fq% zo{@jTgZt9Z+h8&W&S9LcEU5TB4~H2?ErE$Cs-l@t3lkFy(|VY|mR`%7ZMJW5ezT5W zVr1!W$NL`-kNk!oJ`{Z4)z4B)-CCvYQ3i07S-`Pw3&Y8FbR_esI6zPym_2ruYDjIW zDr#(uL#O*+UjaNGqys@vF{4{ResoJ2V2y6YgD#rU6nrYjo)m&K!JforS*{5fGS%0}^6&)^DPM`P+ zNVv*P&WX}^o|$PX!tC-%zY*}ABxhjmC+kc+4v{UJiveLw^2D`%e;zz#GIS@)HxZG_ zA*?mYVOgC-@TUNt%s|;N0X7kN`NqPJ_WGO9T8Ha&ov?o0f^wMcg(za!5GG_U`my_C zalzV-bk>_MZ3(iu?Yj&QI#WND72>Zs<#~v*(!GA~Ku4yXamqFAbVQ+1@yKr-+7UpZ zPicIiWcW-~g|5@ytLo9AX*?r)%80PJLmO-@ksw)6d1Vlzpf}XJ=K>oTJwa1r@e}ZP_pWH@P@MYKoF85UnQC>UFnu@g zvoD^Q-qK`VQ5HR}Z9~y8CC^yCzkm~`cFQR<+<2~pcw4oA?Wuu4#Ka>bb@P?)XuyqL z(s}*E^lAXCM&DrPoJMzG&h^IQ; zsxiR!jlgkYHObTeSuMZLnXL@e1#P2N|Cwtqx&?w(6ey!!E|JG0A7m5}yjs8cbAbGc zXp{awgA4-DW6z;^=WTpky&Wb9&RdQ$pgPQuj=?$#ZSwzUSVf`z;Xlua`sWQ)T+BK) z59!kmb4_N|vphp(elwHWX9R}feN?yM*%GdA)?%@YKsU4s4#*-j5?162rVO8Yz`N1%?=jL zT3ahydVl#-FYUcDyBw1K@NKzKO$A#E^{dPnLe-Srto!g; zm{y0YpBQ5kUDA4xVktn`;_+XXoXh2uF1{*B3kM92I+-U>u7}-525pO5Q;^T1?;}V- zTrY`2N%-p7cklsVOWEL$r63l5WLPs)F&Y&783q}WBc)!Zq$3H`4f2STR)GBu)P_`^ zVM2MKFl0YoJPY0G8VN^t_~&g6cs`40ox{42HIDy0(*6*P+!$v?_P81~kjyrL#g&0A zK(~m7%JhRcHK{P|^h8ZV{nS!-LQoD7s zH@^jN7?7&gRM5Sl@?46k^c9Sc2BO@@gH%aYR7H`wMuevL5ytB}XFKkVN=0LK@)jjM zH+74^+K>1byIUkZ{ExK}A~{VBM~$j4caK<%GT`a}rW~5T8>4@PxnD#TyqF zWi%n~R!(VbfzNy|gr_=kcZFXNTe>yYraiTYMfMo9Rhq7M%^HI5C!-dDD0P-M>|~n} za3Te#8~%v;DQ~x29F_Sb2n|5d170bmX>C{J#EaZ3R941$!Visnezk&7@3eSs6ucB|0A5h5fl;D1IgffVq6r~{GCJ!O5g@G%3_l*IsZ_CIl zS>e8d)zM04t9_SN$DtN|{Z|WnM#3F%;M&)vH5QbT6)7L3+)fD(LR zR@0Y3s-<*cMDwQi1!Yqhg*|2NZiGO83;m2wM|AKjEiIn=qz8(jkUCnEk6N=$ zw_ewQ1j|OAQ=a$Plj?Ywzl_lzoUE-sng8SC%w|y9*+^3MebM%VNt7l8wnrFnRPDE! zTMH0(SQl`aIXc;^pzF=qSULM^vMKaS{?ifmw}@pBu0>t;AqUhH^f-PquIU+>F3H-K z$~W7_QUOpYom*5W6qCW$CkY+H<}V1n?R1}$j@ae6e4BqK+a@D9R+M;LB2KxOy5S~F zS<8;g3k_1MN-2H&{i_0yd@4G9R|&t$FlZFEkAjJrinpW%M=AnvJ|`jG^ZZVgd*^w# zTmJgE)TdklfEFag=noUrAYxTbmPqB};n%Uek&%(g%KiORlj`?vlD8IJo94zo_|7ga z&dydx<-wPh)8$T}s+a5G>J)Orpf)fv56+@$CvthQ{C6*C>EO`Wc5*ow`rcg7ZHz10 zpV=UYvDNd5Oe+vmPN_$`G-zvf_pvkXhm7EFp_BsM`qq^h56y&K@xjP(V_GgmG_Cm& z_FwL1@2;265LX&^6g%~sDgmwDXs6#!0!zK!{+v-E9K1Mtntr+*X1LlMb{n#t^%w2%r5V;uW^{`Y9aKsXz&cqBF->Ze6R`zTTr8m9)svrQV* z1RI78^vS|Tlu=gqeK%{Wn1?4sChAc#28bh*AhHrIT_9pVy9u#kuG7tmfUKG^)rs2x zx5GBoh7oomiEe^-F2!%_P9o08OK3|(IY~L!pB@Ru= zBr)?*h&M(m&w%2m^Jw3aIFf5dZ}<0G#v4A!g#Xlte+Bkwc{rG`4uK1Jcn6)_)qSgs zhIZi3)hLjKqodwASR$*wdku<<+z{y$pumjhj8=`+j>45U>*>PA*Wr4*+}Fxo>ocb)e&{G%kdgC#8+^`{coqQe~s8p}&h&qHnx;qtU+X?3SkP($r(*_M4yr#KV=U*L{{ zNPEkZz+8BzJZWL$;inR8dAawG1xpj&Qu%Q7R7)l~e9Bs;JcgQFWzYJxA!C8ITuc z2*)s59(t!#_4IEe;AHhy3bWkyKO#n^kvCd^@a+i~yCtlTZ09bd0{cKYqAmv|0V$cW zwCscIXM)x)4#gc92w*JogCYvYCDL5%!^AQ#jR8Utc)XF$2Q#>2l#kH40f8HGU&n}c zlkXCaPvo+`PNVwB3b=}y^J(Ee?FBmlIy&)qkPG(y1#p&|^LAfK45Z5a(X|ZOJXO5ObZWCMtRnN*R|m}%*%BaoRv>?NW-Y%@unwr&D92jby;H@ zuJKJB^t+L!k-x@zFOoqnZeBa2yu)v00%IsXCA5dK8{8^!nr6fnvuS2o@@+-v`31D% z;*b4|obRT*j7N5JHbJ$q-?6i4X4CjOI}!3AU*0ey!jh0r-s&c)21BkLv_amn_OMyc z2w;eLe{LEZAk_FZ$NoXeYG1K=b*2y7-$`iotfxOEww>H-TILRm?&k!7 z)9wr$pa{lh?sx4>kw04Q_vY$dou$bcXlZ@Pv5FyOxD<_JKR?nGm&C;gAeOT2SmWTZ zZg=;n=U6d{Kq3z46Tc5(qyvp!5t~eEt*?|nD~uDj5WeyYUQHX(C?tjn$42uM*Jlho zWc}LnKe=vlTxg08H4oo@pRiiaeRTNN$+65Nej~&MU}tREb*Qqg zVY``y!F)VJPRdvmw*vH9$F39{vp^hOU=#T0*w_fB7Kvq~b+>y6_st40TY1)DhwP&g z3JzjJkuHO=BGzeCAS9g=6*g2iNL)bWfaE6R^mWZwx&adxnTlpeU+W1 zUiqOzM1g}s6iYOHBAt3?;#lLXEPaKJAT1W&F*anuCOGii!8+4e?{VabJ;0&>#eV)V z2XluETI~zeup$BB`J8@Hx8tbUlpr@`E4p7j3njg*W?67M^R}SNI}qs5=Gm>AGRS<4 zAj}10-p*LU1P8e@6cMJ;{hUyF|x8rh1mkxoM^ zGNG!XX~;BTH6CZGH(j>;JGZUGWz)_Y*0hSBu#7`J!2Vgjdwr z-qBH<13)D!66XcGDxliHyLz-ABh<}tDAsehKJStC> z9mJ?8lZBk8FA%l|MuQ2s$LiFPgY=1TzEKJfDNWK!TpHxLnesC$g#^h62(2#V_Gs{OPJpBgQ{nRUdxFbwC0l@PZ+ru>8_4(Ae0Qa2rYKgnbm(w{1a$cu!D34g$)4I7jo=N+QE4@7edU2v~0#RdZt4;T! z5e18mz#+l%4rmMa;?z^W5vM>7@lQYvd1=3sbpm-Dz&A0!i^Y4qPt%~c-PJkLw-$U6Yvzq&Rkaf_s>nZ!z{sA z%$$O2Z-zK1p8XL-uU8V7bcLdEma)g6T={`?a18^>-;F1fVI>w#^N5EACg=pb_MN%L zN>p}iPyuT3;4rQFPBo<}b`2c6`so#Ybnp%&iO)Bj%E%uq!lzt_sVcEm5vI*Rrn^sb z3yKD{Nu7^WBUZIsxwS>dw{F5ij znSt)-*;K=w87}v{HN`RQoA(Sn(Y~{h9$E~`UW+1#G|1VEViFjODEt$#XAF4}rT12^XRb~oeW6FtEixbiW~izmzuXvgDdKsc)0SV~13t>IY~{Gqza2n(R6GjCI6iNO)&ls3TyfBPGBGH9Mw^G*} z;;b`i=|cj|qwWMH!EFm@3U!HCIDcpf=rJQ0Z2+5h+)w-9^XPb_poB{BWtNH6h&PwQ zQ}JMWMa9Yn@Pgm~-hp$xsR`!tiEYX%UnZrPcP1Ir2dNYPiBzw#&pf1Dq2W=XIA%4G z;rLMmPqE{-KqhY_yYg0h5PaQLz_k4uL|oG^{-w@}{1q?v?^i89ohW!N*bVGnS9s(c zKXM$}nmf9FUDm>cxa)k~fJa&fe}yh4ufQg_uXL6)QC^K+BnkGfUI|5z)Mnb^KSeg- z=3isIai&fS2k1Y2yW9~?D%M(bJ*MJ|A(#h!&#dv0smUTK|B~{Ul_p@pGY5R)jnGCW z$G%-MddKPzZohUZ;^O3q>?hb6$1wy`u6PCzkM7m@1yBn*`m7BRB3>I&(BKQ=9YxBUk1fnhvg((9BKXp{bh=y<3@cIqA{NOQe{uNDg;IS5ph|% z!(;uy3kDjr1+*y020eTK>F_tE`C)3CP2XzZYiFVd$d0r%pF@st=JV0IO`^_+-!E{` zpHU!L=ix}ELOgGTk*>`Wfog*?DFAYCLGq%Yjzn&MZ|X6}zNGWJ1nIT~+xRewjq4?$ zj(r~zaK>>4ayvScgaO%T8Gu_z>-em$wjP`Blm}1Z(5d2C&L*);y#=9R>nOTb%!vaT zgnAtA*6E3psPyfY%=Q;B|&+tr}lTBw_Y>_?D~zJ z($c2&l*7%YcsR6E2DXjiy$JcERxn*4o-Nz&&@w6n{@Qwf=rRo(P*{HBkU(l^o@1(zD>)4 zI?O5q&QBokX6@DfvRmIO!d(3+$g9l1sgibbNhCSWRy#jSeJ1j9VrNT7#d3AY;W&> zi9C-$e!+eTg~cLYYauVg!Llg*;dp{B`{;R)-`F{N{R+S%B9(6b2Fuup(K@<#u;Km9 zYlLxstA$0VGwQoG%X5T*k#0k6597_o5Q#0o0{MZf0cMEi`ug3Y8Q@M7vNt;pQf>-r z%GA3*9Rli(ZvCqK^XM%s2Q1#@UAb5ZazF%uwj+){+;lP2*juliyhhqjkrS~(RW=Eo zO%C57Tt755;2b)svn4+K`>uj^;Y9RrUlhg#hTI@4&N?o$2jSvd1I}&JIZ9z#(q|Fd zf_yh@!^(>F5qNm^aYS+k8FAXB3*H)GiaCa*?@M?0K#EV=+UvG{z4P^%P_J&wp3C+) zxPRW_(~)gKARVyv%Xd_t!9l%!QQYMBPiysTN|oD^AA~atHDG4?fb7qux1;?_hIMj0W;7>f zwAgplvfJO@!8~iHSldX#)i(7C4x;oeimTgcoiX)UzS|k+QLfGXU`~TVQxFp#0S29% zyT62?Q;_2k8`8{e=N%9FxoGWZ23fk|)2S;9yQ7DfaFyZ*kubLj3Q+QU-~(-4iM&^k zeK;j*7Qpir5pY$Gi+30XbLUS3NS2%DjJ53sS3fSlIvUoApVNadG_rAmPU}tb*DQ)P zV4|d~vBDE{hsiuSyKTyb&|yd`ue@(v)(1s@SNEIqo%4e`b)ZKUByF7^HsNgUuSD$m zYu?5g^+hLstOW6S<#MOIspYFUa|eG$a33XcgrlPsc+a;cD#}psvoYt3RD{*BMyo^i z5cyyRVq1--%VWUA7og0hOYI*vDS}r?q}W@+B=79OS0X|)u-ygD30!lE%8*{s4-^z1 zZIhDth~oVLXLdd_w|P^*G~iuN53=}PtH7F6z>xNgLVvlo39M?`aK#zI5PFEv#BOwqk072fER+`*pC$-3His5g6y-?D zFa7Pu;mbZZ4-|P$w-K1M!El63m|6Z^-6y|l+QDs{Ya7I z7opsHvZL=rjRR*U#362HTr>kSZ|X*0ASQL<@%0KSO}5{)R>pqyQ0xl!j*ZdrFcWkEMa0DES$zy{BWu8@1~r)! z&?DKTLFBA|Klx8SPx$afesqg&bUcMp&_q=rL+o}PyKCD}Fz!*&vlmP0kL_(s`%O7P zzOR-6Ve_?ixG%@!v~`j=$P6UJ`u zbuIZVGH{!~*Pla1l&@p^+;`)&$i>x1+$z6Sd7_1B(`B>1#F%?x8Z=LV4r->Z*dOeq z>qq9M8z&wyr?cyL+}=qZSamDZm#sd=@b<>~T)sK~dg$DW=F$5=j5mcqzv`*qg*rq@ z$)a?eVpCtwj>x<09_duLQfzY8xje~IFJ{O57ASq1chRtr-4q}Dnv+)z%BBry5B4fY z`ty_Em7mtXm+YLWcxMEq6z~8Hnhpnx+G4?4$3Nb?C^h@K#V@nU>_-q=tejctnS0`7 zQdR@lzAJts*ov4_q9fblc1Tfv3DHX`5NL2I1bt&7kH6emp5NRp!?t938L^cDP2Qw$ zmmcdew4b@g3B3_&u181$zWYyw3B9Hze)?!upaS_3yOXuwS@ez(L!H^L=X-<>um|Z* zYDsR)78X8u(e#2aGH2lKC-f&IAO-DZC zrnkqs_)wAV;IH!pC#8!)WW(+ClM;}~ubS)AF{WHP%Z0sKS(%q}oK-mb8NO$&Ci5A( z0KEGhHVi#B?GBG0FzoMArJblG#?BSY(IlV+%!7`jFu{GD#2A|LNrv-{oH7?f*E?!O z7EaRZZ=t`N^lXU93mb9iN)i2^pVr-S_liJ2t(uXm5GQ?mf7YKB`RfH`17`sbNT6ZA zvd0iGHt*{|%RIMhyGTn6Fx5OKy_gX(d}9FfTc60pxZ*8-eX^i>r$G6;1Rnzetz@T? zpgSNVcOKI_lc*_)p$q6hru2+W$cu6pjIEv;gBxU=8;frD_|S=rjl3`o;h+a?;6O&s zI2D!{Iow&NFsGX{h}`2$D4$)s=|jcN`TKazeKdM9*;kiHbx($KPy7Cm(x$1^Dqb#$U#$LC- zS%{gRq#xoYPgjIyW*`Wl=FoQA5S+F30kWUzDQk7kT6zyjuX0$JyD zH;yAr9P(?5)&NHQT$psu;79Y+gjUW-XXl`b;5O*g3*vkM*yns3>Argq%i`a#4Jg^( zVaUPi(%w`PNsPx~PH;fK-n3E@r$pC&kgPI`|7zn|P-cVj?+Od9eU(S1)ni4o3|3ze zSBN~4a{J#4_7oP4E-a+97t^96XkIE%h{&(@yBX=lM20nrN^gSQa@pZRqzTlldDLd& ztRxO?_%J1?_xcI@-R@5r>@MXdIv%gpT0_!z-&fX~&0 zGhcov-{7rTqjJ)0M<~`4S!A)eF${sVc*xV`p=ag+rRq)o4f94&j{u4-%1X7@lcn$` zsK;qvNLfF&K*`}GZdl%M=C6HQs|X-lZT!vwTe~Y?UKbf!`x~jT* zx~96j0Mu?WLfP$o!M;=if{yN9rvEG)^z{9(KQpdFp*$^PDv|ZKwQmhc2s*AXE9Qq%4B`0M8ns1ZRKeD`D4K?F4vvi zz{0ATvPG)EFFzy2t{!FdhZ%nqH2(W@qB0+mP+X*@2fxj&ZeQ@>z_7!e`)S-w1{2;% zPHx}2eQw{_nZmOxkLddwKUvhGxoIl9v*}o3KjA)$Kvqi!gJXfU7DKO!7@A@&%=3^M zvDcB>x!SZ*HB?5PKGM70#rWucw=&$hMjuT9_{ran!RpMhIr-F`6fxY}?yWE|D3CS=4~`S6c2rdJ=IUhSEz;EvgzN<|EQCwFMloK3z@_z;XhPTs{?1iX^Ku}A|A zjc82*B6Cn#jgY3Q=Cq9_2EN%FBxZsFnV@YzGhtA7>;9e87s5F8;dEZy+{8Q!?(iBe z8+i~3mYZ!b;O6~v(>97;f`=bH?&*6<-HZTd&O*WW`1olBJUQ-FCPLCaKqA9w;l1p_ zVyGA{cC7r1G~gJ>0N-JS%VSojJAa0oWz~3h z^(lCV2$Gx;E|2fy#xVnxdb%y@d2yi0tNMV|3hcHFcE}Goq(;a%Pv~XCdK`t!}X;g_T)V5#-()B2+ft272h&iehuan z%|N-dZ}@>7!&MQWIJXEHT?)rqWZV3J(k<+bn(RzoW4QofuJ!PYIQ(Dfp7~#rf>aT)elL5r%3e%_^6X^IWzDu1AelEt_}eoLo!#F z23+5&3IV@=bzXv#Ky__=GC<2HDC4s#p-@#V3yyAwb$)}FhJfF7stwc$w79VA5QZEr zf*Koe8P|FlKqpG#c;P3Sg#SG0)}o9^ftzq2+%Gho6Zz!1XBvn_0kt_cL_ixcutwv7 ztmH>k9!ieO?usEh&?pO(!*7g$HWa|gc{|J%q~L$iJGL$FYk58I09TPK;t&UqM1V?# z26a59gbBS619b;5p*M3$p%3tr0e0H4K!)hk@K$ht~{gyG^eZ;i2JuWtwC2MA6blAVa3?k8RWV|Kj z=5O8g?tnrhT%POTes{?czaYWtaTe9Px(cirp^K?Wzm zrGpWy#%Z}n>+7CxUV_YLNMC5p4;*Kt0F$XTMdhmzG&yF|n{PXB{IY?{!4~5tu(ZZWEG z$cjiRV<3aEpTeHWCnXL>0?n9_7*vV=n7%=eEvDTlkG7lD+NDC5mzIQs1GKSS2KFbV z(DQKME#S`^!=74Gq!Q(y8!;}E2z^o!qQNOYh>8YMn0_rP{4AcgJ7C=i2#I2Sj@U!? zSgg-7&=DSC64)mvJn#?Z*@3S{kiW$O<~eR?!?C_Z7*?4*7sk9G1p7_55xhJ~BKJTA zA82PnhtoIcwZ&NQrDCIgTMO@BVmt8vO&E4&7DLbT0?=E6LL_Yk?fMtcMtLh5fdV}3 zOp;1H>fyz}$ED`)(HVm_7lv-)TG`p2Ba)HvWE6#;ePx51o|XqWo3xvYR&)vAcITW* zdBVVsMC6c>XyA(uRadikc^g_cr-$7D?YwikBD%F?G@G-FjU)Q`1M&PcMoylSe zk{KtX6XkAZk%m{kQ@@w5g!DZg#d=Heq37v=lrE6wyVCILeg$wOJtX@#3Nc`P_|D@g z{5CID^zZ4Ww!ZHzd{k2+wDjvYW!As@Hr%p}KIzfpZvxodixKMZGVIr-Iy5i?Q zTZ->&7}!&*AUfSe$DOw?$Q|9OQRYBZC38Xl0f17*j^Z1{oO{ ze%cfVJbL@%y?<7443S@Ce}8)?R)%-B`COV0a1vShN-)0JL@P~Pk z9{xkqNSqnNYhx&r_z=_pWn?4kSFX({hE&5@j#LK|k;E1eu;A<6+b;O)^ zlnEGLLO1|4g9Wq>B|rh)&TnrH!iZ^aBI2YO_69UIg2UKjZAk^OQM|wdS|2q+-Y|sh z5k7N?Zin81nVtYLe~Ws`f~h{;^hc>Dfb_Yr&kx)!eSI&&`}=)sctNGBab0>)d*-UU z0^OMdOmu%nUyd^U1fHY{G}$~2C>(vtp@Er%13bAlSVk6>CjW(=?e_N4Za=dD{*Rzj zJAxHKbrWZ0jtl#qi$q%ldTGeI=K+>}(Y2VDR-#Rpo=jgADmR(qyR((6)MuNxM<-P1 z5*Y1@kd%>4tu~S}*pvv#kf|%+(no(%e7+p&mrT++%MC)*yPyLZnOd>lj#F*ZH zmPzB;4TbOvFmAkhwMOIv+y_*TwFnB`o}NoePeDPMOG~Cq{mw?4w>7evl}IU(6i#th z4U@ezNil}Fr>wDyxTi8F&mi>{uRSpJS$yQD0zo){(+GfK+v9BOJ3zxom-458>udP5$O(Jylgra|cY0rh=SKfDof z^s!ncwLE0@{NORsU%(~REZSwAr%Bv=l^vw!3_?=9u;B)Dj+|1_3|pzvH~N9u<+U~M zS5m+=l=@m!*@u?|CE;IZLH8f3|;duHHF&vGEnH(vizMP~$mb965V`>K$D%&{#0|Lev~Tt!B4!I>{UJ zB_XQ&5Ez#LL<(D=?b?!r&N0)Ns?G+!XGPlN(R;8q;FSi;E4h*P4 z{GlJPRpk8GRsNAJi4-2HKTietKthjs2DVF19veozYx)IATuU%m`A0-mDorHEPGN>4 zl2S1wJ5JkDE&Kwe-#*egA}g+o9~S}^-{&}ZsfsyGIO|7Tx1D~}uB-&{1Di+*my{=H zX3DB?)2A4k|{2w?L(+ zT58KuCGozNla!ZAurnqIE-m{gDT@!D%_Aa-l-Kp*L%cSG=+vBl`9ZXxAFnA=LV`527@|E&w%@|YW#WPO;eRwilwrV1s63Nz#w2gW`w&Dg!X1BqSw&*MV87 z0}nN<-tIRh4bq+i(r){{re?josQ$f#rQ=B3Z@+$w*|Fr-e*RtKEaRla+@A6JB~iIL z`HcaWAW5%7(>w>-CEahinGL>^KBr=i^}cT(CV7jSL+!B!)IZHSp+A|ifuVQ`lra&Nxj*R%pG2zNz&W0_x&5eG1Jn=8XZ5l0V$M~28=Me zXCb@R1vwrfY(|+LM@a-V^0S`2W__#(smsFUMZR(L2=nbT!_#SIs7M4<>HPX73b|#@ zw--hJyZ$}=KP9Bj{HqChQX`gw9k2#@&0ioOQr~f=&g<+`;?il;-S-A?xMesZKjhcqjE2J)rL3`t z)LW;LYmmU*xL~x!;r+6F;4j6b^L}>l$ZyrxM+98GkC{kC zO!5~hVUGPQ9;VbvAkVVK&hiZOUB#Ids~FJ;*n<8jtAyFkj8@IyX5)u?KL zfb9E*dOpP?`o&bXbPhS(Mx!5TD?cgO2@)(6KiNOv6Y!ac0cqZJJuHTg*$XjVGk1Cx zuFF|%A7LkNHCs1ka;fW{wX{raKmcs+sR?B>Yw2R2|Gghu!mLw%oom$`>VOTx|GL8X z!qqeQCI4umdevgp;vy0Z_vZ7+dd>vS#ZEd#5y=S>{!J7Oibn5-!(AgiK<0IZ2j4Gm zM(1|A2B|mWQHi)jT;PPM=KC_7l$3y$@HD5U>B<_K8_QjSCERrYR!c~)0BX~mOt)Pg zK22|wVfJJqP^9RUgI9U(TdH%=XO6m|m^esHJ^{<6ck>4E+ zD8G!&FpUla*xRwQ%zS-voRnQ?9PnEbTL;mah>M`qbj9RyGmM6<2+`6rj5%JOuDd_uKeI?Ay`!r$ZH?ORm!F2KZyQsX}!?9SFul!rrL!p zAqvVZu6xBV>cG+u9fVU$PAK4Avf}|I5Q902y3E?HaD^&3l6wC3xaD3D;6A90qV$qL z2}=;oWs*5KbBHdLTk)dbwoN{3dHjwI?^ojxW{3TbUD@+f0(jFdEhm<;-=Fx*Q1QtE z6w=@F7k?V;xGCMlVDdcCp%Wa`EEC}6?G`RHe4q!2=VBf703ME%crTd&z4>l$o~<#mV8hs~_g5<2Jgx_7YQCZ!UUQGq6YCnD zY))CH@FG9sX^Oid^1Y?Zf6LpKmc5Q%Mk#Z~2bJ~eLMovOf{a|DHR{xCUQ+h0xBvWz zO_;O-T_G8?WrMKvLW+Hr>Kw+lOM&m=SjEMwpMMI|3ZWoFD}E)_Q?F1IhUJ-JVCJ4e zS|W=aL+<9K!4#$%QnWEn3ofaar4Ag~&imovwZV^c-he|cQUkaKUj>mYY4U({r z?R+C~7w!*$LE<$4j;6h zG4Pexh%=aUC+k_f1?XtiFLB<(NwS?8ix(Fm@)7x8y66;LO83b>Q~$BkqzS zx@E?+_Q(et&PC;3NaBP_Ij1v~oD0X(1UUiwKj^k5<>EJ>sXUN~u)q{*-7i8~-~U7} zsiJC9)K#J3YbpM?)KY)zm8sD^bwb+ri!Di9Hm@?fwnU4OvH5EMIuy5vq1!?@^MF1q z_{}i-Q1M6lM~{bc5?9*?2ozFXpE4`?# z28+z)NZvQiDKE)|^?SKzmk>4fQ__&JxaWT=evl+!JeN3%!DNYjnJOn~aJsf_%6irP zWynX_THIwhG)05zSe*22>d>YF@U5*YOnuN#B+H>(Id{-L_lK6RE^NBQEOv1{?Vy*8 z)SRHS%o7_$@ct}gE}6;uhBREgpg!fSL@!U*JzuE^jg)T0S{crj$LmTxDzUuM`h~yXzg@#fxW5wEUZV zpVO`dlpX!+F1mzomX6Kl$-@~hYC#g+ywijm=PEQqp8lKOq2o*m((2?N7#k0phY*)e zjDEOt@w7?7DmZMZoyX(eYUP8!+>XWSixfYe@;~q^f%@P4HJXeEpH1&vzKhS(k{gOk z8?ob-s;6gadBMN(_R><3iF)niwHQ~DUNY@nC2a^RWaAM#@wiz28=d1{07UX9|O93$}*UGEL^E%wzxw=Z}@lgzVgc(2K2`PV>gwZQHHN{xGg=XTM z!$+7|d-#K+L>$!ilqhY}h;;OdA`-Yhy>}2Oomr9#PTc&u#B-K`8*uD0069x}S+KCd zzhaL!vDV>O{4*>Jd*NMAxl2#mD0k|_q<1+NI|laU8cnF((5%=Ril(OfHL0Zf>^s$B zQZ%=)ePy4s1QGSbl}?Yczvk*@AwbnI5TfO%d-V3O>iv_qRY51+ zFa4V{`xZqm7j=D^F%f3C$VinF)(RA9Q586V&c%=fXxr9G{Ii>}cq}Hz8EcN1TME_s zK+M7|?wr5`CoWsfvdj~5?d=hSfP`#In zm=bcZ6=!j*`MVM;xI7| z1qwV4NuxjRHjN>;q@C+caZxm2a4PEn^@v-Gc|WpoSS&%)wO14kEPEvj*FGDn_>p$M zmwV;vZr-&$v?5|8Zi-Z29j1$H#9d*5+J62A`;;Cv@k!>%YsAkd`fzxzr5&+u-V2xO zGU<}-ZC%llQGV|TSEyNM*Gh%vDag2=EV$(;O6`Gfx`x=Rs$f&ImvS2NE|M99N%O~o16#S{YejUN9yzv z>(|jc?6nbb{(ALi`Tc>)#j0Ug%q#u3QG$LCm8G}FZLnpVfgysxx8kjl|-fsJIM1ogm@9C?5W}#Q?UY)+um?Y0Ak6vDs zd1k&pMlFq(%4qR0;8kX<_4YFS_2R+%?AJXQSmvSPjRAFw+QQ6M%9uiDU1CmQAHLNj zZX>$O0O>MM)qD|YH#h|ieAlSU!Stojg6st+ar>ZBQmtv4$M1M(3t=m#I3*1qGX z41InjGF0Tp4i-nG6wEPwCR3B*F~G;vFsCt|(Y?NXVz}8%wi0=bO+%Kr;#&DP?jfh^ zFBf~c)n%_=qsYODVE}5047>V&?~~DxUt=r|V#Qco)QB5*&xDSk@&F7=$9>V+eQR`o zGQix?T-r&p_lsi=dQq9j6`D6@VpLX{x=Yc9-V`<>Op43(iFDg-8!q0lcpICE$=8=mq}gchjdF=YZ`fJckurY^APU}kwz+XX3I5Mu z`FYOzp_z)KgDa14J=xZ~+XKU&I{L^6h9oQ6a>ly<4+B^aeDRXgQ z_evo9{9?D2huif`?xf1;*TX~ON81U+AAWW3%bamP8~B=W^@sRPzLSCemvmdkl)>5Q z?;?z{EgaUi7(Mz;Ghz6u6`C~ z?7XffXMg1H~W{K;;DDflW+Q5u)k^fjP&sho|_id z8ue3ERFa>^EGn87hnJ5?o^X*qJ8tFj+TgG^t_wKA5kV7AS>)3bT9y2h4JViHx_@Xp zm7y6KI&c2+`o}BMMkmW=I~sX=FIdkL+dCgyuE<`$3I#B9W+V3@C(r-?`~NHlm=z(B zDfg+%!Qjk6(CzMdtjJ#|_=H2G?{!lJ1%P&c3lUEyV%6`WM+|f_K|a1l-osxzV*g!^ z5BGVkE~Yet7AjT@Ic`*~ylD+-tA@H9ctoCpe+%3eB;YY~NF zNVfmojX-xJV*lk{_y_6#x?cgHh_eSaqoo}G)DNG@GJn5@TQ>RGKgYz-P;2oRUo_O} zWytr^<;eVh*U@Dt8fVFU6+(B8u|}oslMF=2%DLY&TNSD%k9lII6Et``6>2rJ21KWh z-k#-kT&&1B1@TmMLkQ%i8I%mMcKb3QGP+ncK9k*b9@R4O@KgSheO1cJi;=KZed~c{ zGMqovc?6lD`R-@WAERYo+@Dd>XT(dcP;nM9msHXbE0(4wDyVu^;m7)eoNtI-%=B9h=jS);3m<6<2>T&24!FX{oIJKK3?` zb%g4L*yan@9WQjhESywky>B|OD2&obnTnC*EDIF9Gc1;RxU`~VejQ1E0Z<-Mw^ZI_ zHBPZ-G>8?R=Fa~yoF2)W4T&2aOwrxw087Q8cJ42(C`x%AZAnR*tVA!e}EM_WL` zvZhs$R$_Nvf@hyD3g%Xnc?ox{j7Wmavixz1SmmL`gfhH6tiwoI`x&SrDdU214Vxv@&Z{PxiJGh zWe<-6w{HPhHW;@*n26|+DIGtp`@T0*RJU(ctmRZp_i_djJ~U%i~*T z7r4YZ>4LmOabujEbiGNB|2kfVddwpvd6dF>yb8vKP~2qnn;JjryWUX)uGS}g5*N2I zv@WI;hbKq+# zIy0)VviKYkAWw+RLsojTzh5$@?&yiZFaP~aVe|sQfz_qPKPi@7)xBIbVBdALI#{lM znSFJ7XXuxa*<4AH8pdXnab=IS#~?z%VPT$fbnp^T9}MpMT7Jo0S`Qiw;?0PgS;tgA zQz{{+SuGd)oepO~h7N7n8CpwVGn3!+{ifo`n1FUvwWk#GOxtN@rd2qRM(6RwmQJl3 zvN?Srzi(H@v+ncPTe?vN(^Ma<)PtKjRcsnz^|CTB7)!?GCrs4yqzSAAmYO8OJDTwN zyGR_I`GDN1N<^V3>k4k07_g(RlVL;kNcEmHTe zjOQ6Hrrw}%P3;`Z!TXGl(>chU9{W5O9(q33(OmG5E&MKf~UW)+^*mE@)Vt{vx3@ZfiHF|R; z^Oc82C1sr!$rEfEjaTT%!L`pBl!fB&Re&6R%Ca|fK+%V$^t3=x7Q^!I9|TW6%!n)= zQ|_|-I?EMabGXA~Sb7JAm8FE+Qs5MTMYH>3qHZs8EJUa73;MNk1uoD8sD;912AHM?p`%X~AbNF|t&C~`34Lx}c&+9_V zHj+28#zuycv-T9s_?sx9*Sr)n0(H=rffXBg!uw5^ zt0c=qe?^dolnqutCc4H>|F$5BTHlf6K#T34sqq$z?M|DBiS~c5N&B{J-1NlbrD=bC zJ{P}K$Bv!wy*Gd@HC9}Ad?}1*?4k!5y^sN3^4DIvRhqBP?s#qw5XRjP z|FSCLjX%NxwJ)**7ff2F#Cy&jtgPqlNMJ+(!qO$t@>UwrvW%b1n0bd&8r=8ksJg#q zm0f`ko&3(!;OhbVQD!cjL$}__GnQGO<~L6D>|*@O&pgXwhzD6n}G>=m{*d> z=_~BP1Z1OdERwkE$A30Sb1aihJcYj-lF7drNNGE!SgKW76S>eU%OLpv3mO0Wd3tC) zdGFUBH)oBG`x@DwseU56Xw4$jRk?ZUCAMNqN)l(=yNINkIM{Y(VGgmgx!3iFA~neW zQ#0?hg%816{yv~h$xFxfo)0~8dXK3Q|NDvek1IB9)>+zv3sk#}2_J!57{;0G9fdf7 zRTdRIb(u19$d_ofrOVPQ50de!a|;X&w2!)gcfPh#%h)MqESEV~pd)jN=iefd@u z4%TE33Z`@;RC_L|#dTeI$+cKv+lRb;wIHny{>kw6)xv@t_%+7r*M_>dFQe9Q2}@|& z0-|uH6=tTRjH=9;r+=o;ZZz;bnVM8#4g7jK=IT_$cIN8U#wy|D$F3E-v)biipnshN zclcsb`@)TbrLs}u4<(dep04qapX5s5yghSd#qiYCm~Q|KcRcDpQ`bGTH&z?W?k*^& z!7G>Y$Rr#2)x^VOnLo_Tl=hdhHIIoWfATxJOd4`hj;C|TA@b1R#jbit-s}`oSp#}^ zzN5d%&t;4o=mk~px|{MI_?L?NmWX)obNG?bjSz%i4H$f!Q70B=ylEW z4hXT1w6t)z8(y9RT)3@-k4>7atrFpDuu%4_A@#3FBnoG}*1 z?HltyehKizL;JDIH9eLeyGI1nIH%;=eAOTI`We6C^>{=UGwO&&U;T;e@%SNg9bW8F z&fnGqMwpWEgj@lCtl~*Buy^sQH&W-!s}RcSRMJ;TbB5HY?UXxj6sbN*VCsi4x-v-B z#0%OdqO`v`f7at&h6|vlR24sH_k43D)BZ`wlT2{66td8KAo_6O-eV#hk_0~WnzNnv zPS$X@?+v&XCeMfc%4-5yU6>F&ASA2_;1%|SUNgV^B$?I4oBKQNFL;x=I0)K!3J`qh zmn}oUamNdWT)9Q-16AWe*@VH10D40z!4Q04ecU%3>*g#Whxa%2c;~au-)f0YzO+48 zXWVM)PGp!SN8o(JX1GTTFAlhZ&LfJ^o!*yAFOuB;aqBBOIX6@leos0|X-f_{%Nx%T zj|59;vsKkxe@fuiL@&eD3N7LnKiKE5b=6X}ixxS?x7W(SoZEN_Psya4G_?jYS1vlB zJY)Ne7FM#*+tTVKIz%d|2=M1f4o9K028eWw=Nd`vW+=U)LVyFOFwq)kGFyu9jGVH& z%N2JX_P&cryYl(U3sS&ebH!sZm`?>sz7I$$>Hc(v*DaP;n?T1}{N;j|R`AiqjI}9T z1B9cL@P7UnS@-m51GFe5az$}*4U+U=g9)Pwp%=z$Fvk3_gx2;8VXSq*nVnK}X!Ehj z)~ATnHXT?7IhkvJhy)Hfa1obh z{wlg8sRQ6n?6|Gz(%pxF&H#B1lE?gxEg2-i{jl%gpEIOV!4R^q25XrkgKc=ef`=S% zfQf{B7E3ootU7ZgSvEU<teY`hP%jk7g%k%i2=JOoV;MPb1iK}B5XR_saZ#o;Ju*PZ+go{5EvC&J^ z%2QWII*!}NB5Xcu#$kMHAe9{<{pRGQTxc`*VRy1lZt{zX**zN-{g_eHr{l;;%kW;0 z#cvY0dHN-btZL%_NpmnN@4;{q|x!hs3MAYgO;=#Jfk0gPpJVxn_9)m$>OJDg#MsTsS9^k3B)Oh+D1vR_?E5>*G zUE`A1R#rvS=TRowGSggA=7XPI zsCEoTryQ5_h&;aeh{}6PL$1y6V;J*u(5nqU{`Y|A@Z;TBG)E>%W z@tvX@3T$OBLyl|d{89waAuEADq!wMtE}qL9Ax?Zy`I{cMiNDNjWBfI{KjZS-mc+rM zM@s!aFI*4y4ZK7oByhIm9)z5vQ^z^4#-&aqR?7V#wbrS!^vklKrV%2!ynb`x z^Mt^!-M#+28Uc&j2aQx4j8fb7f*ME8fxhwrit_?ndu$3UB}+#Nvt~>vR)GR5b!Yyd z6iSikoB0=;>~+YD|HA9%{a36>V$+xyZ`S?#@VY-E-Jx<_l)0uULSA^Hm7GHELgS;$j5?33#wI+; z;T^p33eUWwiCRe?(QRV(or4&yOX>#Q_*- zUi2{N?)@eV6q9SU_3g2^91JOS2`FboUIfI&vL+`#NREZSWw|^kKb5<*L6<8o4?}{V z3)bWj==p2Vj`+v4D(Lg0Pml^P!v^JFLO18ECInPK%c4!fw(X^(l#dW_ZAb^Mh)* zh;bn`0c~H+s1J>Gc5IIHU)P8oX{+(Qo{-zpll?Y%>Z1bJqEXC-+HUvqFdFeG<)GTT z`Yp0orK7L_&Eo=xg*-2{e^8_%uH(G?mE11-BOXx5Rq7D!9h@V|f%TvEdwgOsd(WIb zI43hr*nSfvbt-e<6-Q3Wm5hw3*8E*_14*UrxIg_jGE?YDF#F!U!&>CwS8c+=E^?Ar zfAp(p|7<}re1cb<{%?7*Fb$vc@#yzo3)i4#+YPjs9kw06f?Wa@`O%_@L7w*7E?#$? z#c=%JP>1_kI@_iV@KRC7uMGre1uCEXu z&dQ}MD`^n*Q=sp%J@#k>;KfoV<$rhlsAr(TX?}McT3#q)s%J)XndRi-3x@lT~kSn zVbiNGI#oQrntV1bCi`v=_}0Fg5|$^2RC}NA8Ii&xx!^srjI@k9)}r}wdF#5}v0o%v z-s#^ff$Nu3KIS- zak`_OADpk5yaugX6hv{gn;U=+I_4k?Nxpx+%Li_*(gK`%sq{Z4T$OcpUaJbpUs|@O zhaZTd;l2mFNTbo(9)SpuK5J@jtVCsb+WTErtqvF5#5QM1Wm?CgiM7@Z7rmcx*GqW9 zxu1jGC_lo>JC=Vk$EKnYiPgr9q`JHreXL&4TEz^=APf> zE562TK#-aWc;fh4fB~M8&GvxDjh?f3SeZZ69sJq%T;Ir zCYK?8z-wRlZ9CRpn5qC_F1jl;3qRyT~zSX^VM^-kg`+7-I9!I{$xhQP= zhX$4t!TSs??DGyfx&gvjw1L7iY8a%B@1F&;777CtcVRP!)?`kEk?)9)m#I+Qz(TFvfKd5(D6Zyz5)xQX8veOZ(=^gXzYq%;YIr8iU$DT!+B`=_j#IJiQzh*Y%OFVlo|03yHxgKubynNR!-Sf90vt*H?RPR_FKVW@<1v+8>fWe) z(AG${mZ3zjEQq*WiL4CN)h%4=oJP<4$IRW;#-)?kIUSSs6n@5{9niWP9o7C}lQ~_Y zd*WdO(l>K5^SkcpWmV|&=AtzBSVVS(~k{;x4f^IT6|R{Dcos@P;aOwdgVQE(O1d;!+WFAL-bh{sE3r|%Ujy& zdE8;m@bfR$!eDe)cFQCU9JYcp=SAdoXz34X%IQZPj`t4g9i46n{GFF)8BFLeE6@u1)o>8_>P#@m4|iPzSJ3Ql@*c{4 z%u=r88S1bp5)QUqhHIUu6D%+p2$6=V6}T4FZGc*-7bWE-b#lno`EtQEu)^ld-U_SB z!3}+1IXxun9A5r#5vZZi!l_%;wlrpysu6}aFcwzW?UDPKg?AC-2OZ!^Rf-x0A7OBi zlnZ(w4|(7;QF&c`*~l3$GE!<6BN`OGOK*;;i>RI1@LK#`_`6{GepA{Wod{#K*Vgp?DXppd z>jN1?$nn7^NZ<4p+130aDcTO4=f3mrpFeZrsvEB4i)d^Q^o6b5o0=l}a#D6f^d#-2 zTlj;)5=DZOj_%ACmO*&O12}D{vbX3fH7oKEg=MrlT#=9hb{+=SMuXD4v%jyvO)5#F zzmUfE*%$qGMzWP?5Ch*2i&$y+3YIn>wBy2nWFZEv+af1**I-l?nI_Pt45*%ph+(iE zu(!1dQMD37(}yUoS$RZCKPv_R{OVR0Llm~2lSaf9p+l6e0juct2kly-ytq#hgos@$F&oNu zxouw#1%zW(Qch7gf>&zuI-h|k?-RACZqk>^B;A@J=%NrsUTf9F)teiGv8vT^;Jb)~ z)e>Ow9cj#;uD;J}mzdQ_z!If&!$PGd_(HdbB!B8dh!w;tbHNsz3W5+fM3ak{;0q}p zT)<961dW3j9m#pN`>0Gx@pS@GI2CxH+3q{Wgrmi_1ETuX8niUguA&!ji!dTDAed5b z)pNjBZ;lU2A$ux{95GKql$Y_sDHK-sks*p?7!!~QSC5=&`?BC<$^FKqWBaF zdQzG&XXGsf@X&Wm4buh|OP;eO)k3UR?jP4AN8*aIKx0BHNXE{|E*i`(4f5=DYkE|^ zgt9f2sFhHdGV2p~DA1^qW}e9K3C5W+p~`mSTE3!Ngtr$hl?P^|HsTX$G)~z&MD2eq z%`Aqa#p$S`LXt|B_Z8r&pAjMw3j>Rm_@pV{n#}Vsz-f!aqWXZ@N==KlXc!~`Epsv2 z{PB>2M3Mqmhm#UtSJ)yAomD{p7PPn@N-@*2(_nG72x-(%iRxD}@ah%8guN9Q7HS<9Do%^)x^9U*O*oPQ3_cd1%c2BYoGgyG zOi>PuUi4HXF_6#A$F0Y$fW^b*dghdwj|3V8cnk#$1&?~PfC66ZEW5IsvV~WF!O1gh z9JNl060`VSdVqn!@A`Hp$iG1#mn=NIpoF7CqH~sUpuB6u6p4j`G7l%}&Fo#z&vj6M zk1^0|3#%(I2$q7Z0cHaQ7Uzco0y>7f+5Y zgl{VuWEBGyT~Fia5CaxBx!t!U7G8{I2j;GV3{Dphmw+pYg1`ur+p9bIj0guPpmwP= zEpioW6qI6hZvg6K2W6z(jtLo-t`S=tlpPjLp4rPUz@b~n(P^b|Xpx#*hRX_|va%(+ zoE^4A0B8C^x$^~^k+I03g>sz>GIqLVl+2c!xl(|y)B`wM3bfs~Gi8~h#T8aTk*~=j za(feJoH?Q{@~WMq>zD4K#nLCv1gr=L8gsy7;u(__{9xA9Ghdc-gN#0LW=$~gxDXy? zV_{%aZo7WsObJKVw#z^Ym|owS8X3zrE#kE?G8TQce8qib!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10A|${k#P$FG|GReY@eK&l z(Kqr74BowGuZp^svYO_ez54=!LIZ21sKjz@&Fy{Yqv$7K?l<(={7-Dhy?nG;$1_d6Miwul^8ajUL z{%_0b?Vi-T-eclfmD!QU>O3zqiMQyfD0zj-U6^6muP0V>t*I;7bhncr0V4trO$q6BL!5%;OXk;vd$@? F2>^HLtfl|} literal 0 HcmV?d00001 diff --git a/doc/kwineffects/dialog-information.png b/doc/kwineffects/dialog-information.png new file mode 100644 index 0000000000000000000000000000000000000000..f2d21888cd00ba0294a9411b026310479381549b GIT binary patch literal 745 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvVtU&J%W50 z7^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MSD+10;y{2;i0l9V|DQg6ws6ss zgrrngcP|}%qeY9CJbU&W2r4S8O)ac<@7a6z-o0<%zU|t*r@XSNqN?V_ix<896Fz_b z{OQxDp1%G)d-u&>xX9MQ>B^O>mo8tnv3FdsaIvzQ=F68a`zK85?&+(nt~D{YDyyhk zyku!&a_Y`qyOUGXom|}?Jb0*UVEpy#SA8Q>7dOv)_wV1id)Ljwd&kaQC1n*$mM#DI z@l$74PfcC@g^QP7zItVC=dgG0zP;OkH}&M2ELad%*bc#Bm)#=FY)wsWq-`U%PFA7$?#`0Q0R@Pi(`nz>9Z3~iZvPV zxGr9FrK6!~MMIZGGuOGz|Nh7CTr_3d^BrH;#?6{-!g-*6Wym^>tE+-muDoQjGI{Sp z)7i7~7Tgs2Y_)CencK1@KWesLPj|lldFL;=>r*OPkPIR($upd2h1j*3E2Ne~KH#2keeH$>6)ttSi+)<}bq<)2*}D@_1aT@rY%6^GPwg zjpv5^iqMNtnx3r{nP@7a`IYQx03a(ng1gWqG!_S`vj;N-yuzbc!D z%q+aeAG-KIvY2Uq__}C{(&cg~*O!l+AMF3anmJi$u@|$L6VTtPC9V-ADTyViR>?)F zK#IZ0z|d0Hz(Uv1IK;rx%FxWp*h1UDz{nC}Q!>*kaclVUR67T#K@wy` saDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$Qb0uvp00i_>zopr0MJr7ZU6uP literal 0 HcmV?d00001 diff --git a/doc/kwineffects/index.docbook b/doc/kwineffects/index.docbook new file mode 100644 index 0000000..a659e7d --- /dev/null +++ b/doc/kwineffects/index.docbook @@ -0,0 +1,86 @@ + + + +]> + +
    + + +Desktop Effects + +&Mike.McBride; &Mike.McBride.mail; + + + +2021-04-09 +Plasma 5.20 + + +KDE +KControl +desktop +effects + + + + + +This module is used to enable and configure desktop effects +for Plasma. + +The main part of this page is a list of all available effects grouped +by Accessibility, Appearance, +Focus, Show Desktop Animation, Tools, +Virtual Desktop Switching Animation, Window Management, +and Window Open/Close Animation. +Use the incremental search bar above the list window to find items in the list. + +Normally there is no reason for users to change that, but +there is a + configuration button to modify the filtering of the list to show +also those effects. + + +The easiest way of installing new effects is by using the built-in +KNewStuff support in &kwin;. Press the Get New Desktop Effects button to open +a dialog with a list of available effects from the Internet and to install and uninstall effects. +Please keep in mind that changing these sensible defaults can break your system. + + +Check an effect in the list to enable it. Display information about Author and License by +clicking the + info button at the right side of the list item. + +Some effects have settings options, in this case there is a + configure button +at the left of the info button. Click it to open a configuration dialog. +To see a video preview of an effect click on the + button. + +Some effects are mutual exclusive to other effects. For example one would only want to activate the +Maximize or the Magic Lamp effect. Both activated at the same +time result in broken animations. + + +For effects in a mutual exclusive group the &GUI; uses radio buttons and manages that only one of these +effects can be activated. + + +All effects which are not supported by the currently used compositing backend +are hidden by default (⪚ OpenGL effects when using software renderer). + + +Also all internal or helper effects are hidden by default. These are effects which replace +functionality from KWin Core or provide interaction with other elements of the desktop shell. + + + + +
    diff --git a/doc/kwineffects/video.png b/doc/kwineffects/video.png new file mode 100644 index 0000000000000000000000000000000000000000..1e67c0a0f04bf1c8b626f5bf943bb742c08a1be8 GIT binary patch literal 375 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@K3?z5nYFPoKSkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=Y7GC&cyt|NqKrnlE3z zw6L;0*PF8#D9)JV?e4vL>4nJ@Vx|KMm}RF8K5A0iKnkC`(qAX4hy|# zmkl5xJx>?M5RU7~2?y9DoPnSrgn^m4LntAk;Na28QCqcG7cFF2$UmPgM9ziTjDg{K zC(mid=+mo!I#o+tBT7;dOH!?pi&B9UgOP!urLKX6uAy;=frXW+nU#r|wt<0_fx#wT z@5v|{a`RI%(<*UmI2`a + + +]> + +
    + + +Screen Edges + +&Mike.McBride; &Mike.McBride.mail; + + + +2021-04-09 +Plasma 5.20 + + +KDE +Systemsettings +desktop +effects + + + +Active screen edges allow you to activate effects by pushing your mouse +cursor against the edge of the screen. Here you can configure which effect +will get activated on each edge and corner of the screen. + + +Click with any mouse button onto a square and select an effect +in the context menu. Edges with a blue square have already an attached effect, +a grey-colored square indicates that no effect is selected for this edge. + +The number of accessible items in the context menu depends on the settings in the module + +Desktop Effects in the Workspace +category. Select your favorite effects from the Window Management +group. This activates the corresponding items in the context menu. + +If you are looking for the setting to enable switching of desktops by +pushing your mouse cursor against the edge of the screen choose one of the Present +Windows effects from the context menu. + +You can enable Maximize: Windows dragged to top edge +or Tile: Windows dragged to left or right edge +and set a percentage of the screen to trigger the tiling. + + +Using the Switch desktop on edge item, configure if you want to switch +to another desktop when pushing the mouse cursor to an edge of the screen, ⪚ only when +moving windows. + +Activation delay is the amount of time required for the mouse cursor +to be pushed against the edge of the screen before the action is triggered. + +Reactivation delay is the amount of time required after triggering +an action until the next trigger can occur. + +
    diff --git a/doc/kwintabbox/CMakeLists.txt b/doc/kwintabbox/CMakeLists.txt new file mode 100644 index 0000000..46313f8 --- /dev/null +++ b/doc/kwintabbox/CMakeLists.txt @@ -0,0 +1,2 @@ +########### install files ############### +kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kcontrol/kwintabbox) diff --git a/doc/kwintabbox/index.docbook b/doc/kwintabbox/index.docbook new file mode 100644 index 0000000..479455c --- /dev/null +++ b/doc/kwintabbox/index.docbook @@ -0,0 +1,104 @@ + + + +]> +
    + +Task Switcher + +&Martin.Graesslin;&Martin.Graesslin.mail; + + + +2020-12-31 +&plasma; 5.21 + + +KDE +System Settings +desktop +window +navigation +switch +alt-tab + + + + +Navigating through Windows + +The Task Switcher allows the user to easily switch between currently open windows using the keyboard. It is highly configurable, and allows the user to control its behavior, visual appearance, keyboard shortcuts, and window filtering. + +The Task Switcher is often invoked using the key combination &Alt; , but this can be changed. When invoked, it shows a list of all the currently open windows, optionally filtered and augmented according to the configuration settings. For example, the list may be filtered to show only windows that meet certain criteria, such as windows that are currently visible. Once the window list is shown, the user can cycle forward and backward through all the listed windows by repeatedly hitting the Task Switcher key combination. Releasing the Task Switcher key combination will activate the window that was selected in the list. + +Because the Task Switcher offers so many configuration options, two distinct collections of configuration settings can be defined. These collections are called Main and Alternative, and each can have a unique set of key combinations assigned to them. + +The configuration options for each of the Main and Alternative collections are presented in four groupings, as follows: + + +Visualization +This group of configuration options controls how the list of windows is displayed on the screen. The default visualization is called Breeze. It lists all open windows along the left-hand side of the screen. Other visualizations include Cover Switch (a 3D carousel), Flip Switch (a 3D stack of cards), and Medium Rounded (a Microsoft &Windows;-style list of icons). Many more visualizations can be downloaded and installed by clicking the Get New Task Switchers... button at the bottom right of the dialog box. + +Once a visualization has been selected from the drop-down list, the button to the right of the list can be clicked to see a preview or to configure visualization-specific options. + +The Show selected window checkbox determines how clearly the user will see which window will be activated. If this box is checked, then all windows will be dimmed except for the one that is currently highlighted in the Task Switcher. + +There may be cases where the desired Task Switcher visualization cannot be shown. One of these situations can be when a process called 'compositing' is turned off or disabled. If this ever happens, the window list will still be shown, but in a very simple format. + + + +Shortcuts +This section allows you to define up to four Task Switcher keyboard shortcuts for the Main configuration and four more for the Alternative configuration. The Main shortcuts are predefined, while the Alternative shortcuts need to be defined manually. +In the All Windows section, the Forward and Reverse shortcuts will cycle forward and backward through the list of open windows. +In the Current Application section, the Forward and Reverse shortcuts can be set to cycle through the windows of the currently active application. For example, if you have three &dolphin; file browser windows open, then you would be able to use these shortcuts to just cycle among the three &dolphin; windows. +To change a keyboard shortcut, click the Forward or Reverse button and type the desired shortcut combination. Be sure to use a modifier key like &Ctrl; or &Alt; as part of the shortcut, otherwise you might not be able to cycle through the window list properly. + +Any of the defined keyboard shortcuts can be used to invoke the Task Switcher. To invoke the Task Switcher without using the keyboard, you can define screen edge actions in the &systemsettings; module Screen Edges. + + + +Content +The options in this section partially control which windows will appear in the Task Switcher list. + +The Sort Order drop-down list specifies whether the windows should be listed in Stacking Order or Recently Used order. Stacking Order is the order in which the windows appear on the screen, while Recently Used order is the order in which the windows have been used. Recently Used order makes it very easy to switch between the two most frequently used windows because they will always appear in the top 2 positions in the list. + +The Include "Show Desktop" icon option will add a Show Desktop option to the window list. This allows the user to easily select the Desktop as the 'window' to show. + +The Only one window per application option reduces clutter by only showing one window for each open application. If an application has multiple windows open, then its most recently activated window will be shown in the list and the others will not be shown. + + + +Filter Windows By +This section contains options for additionally filtering the Task Switcher's list of windows. + +The Virtual Desktops option filters the list of windows according to which virtual desktop is currently active. If you consistently put specific windows on specific virtual desktops, then this filtering option can make it easy to switch to windows within or across those virtual desktops. Select Current desktop to only show windows on the current virtual desktop. Select All other desktops to show only the windows on the virtual desktops that are not currently active. + +The Activities option filters the list of windows according to which Activity is currently active. As with Virtual Desktop filtering, this option can make it easier to switch to applications within or across all Activities. Select Current activity to only show windows that are part of the current Activity. Select All other activities to only show windows that are part of the Activities that are not currently active. + +The Screens option filters the list of windows according to which display screen is currently active. Select Current screen to only show windows that are on the display that currently has the mouse pointer on it. Select All other screens to show the windows that are on all other displays. This option can be useful to users who want to quickly switch between windows that are on the same monitor in a multi-monitor setup. +The active screen is the one that the mouse pointer is currently on, not the screen that the currently active window is on. + +The Minimization option filters the list of windows according to whether they are hidden or not. Select Visible windows to only show windows that have not been minimized. Select Hidden windows to only show the minimized windows. + + If you uncheck an option in this section, then no filtering will be applied for that option. For example, if you check the Screens option and clear the other three options, then the Task Switcher window list will only be filtered according to which windows are on the current display. +All of the options described in the above sections work together to provide very fine-grained control of the Task Switcher's behavior and appearance. For example, you could define the Main settings collection to be invoked with the &Alt; key combination, to show the open windows in a carousel, to only show one window per application, and to only list windows that are on the current desktop and on the currently active screen. This can provide very fast context-sensitive window switching if you have both 'work' and 'home' virtual desktops, and then keep all of your spreadsheets for work and home on the same monitor. + +The availability of the Alternative Task Switcher configuration gives you a second way to easily filter and browse through the window lists. With eight key combinations available across the two Task Switcher configurations, it should be possible to easily and quickly navigate through large numbers of windows. + + +
    + + diff --git a/doc/kwintouchscreen/CMakeLists.txt b/doc/kwintouchscreen/CMakeLists.txt new file mode 100644 index 0000000..3c3bb44 --- /dev/null +++ b/doc/kwintouchscreen/CMakeLists.txt @@ -0,0 +1,2 @@ +########### install files ############### +kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kcontrol/kwintouchscreen) diff --git a/doc/kwintouchscreen/index.docbook b/doc/kwintouchscreen/index.docbook new file mode 100644 index 0000000..6f60667 --- /dev/null +++ b/doc/kwintouchscreen/index.docbook @@ -0,0 +1,40 @@ + + + +]> + +
    + + +Touch Screen + +&Mike.McBride; &Mike.McBride.mail; + + + +2021-04-10 +Plasma 5.20 + + +KDE +Systemsettings +touch +screen + + + +Swiping from the screen edge towards the center of the screen allow you to activate effects. Here you can configure which effect will get activated on each edge of the screen. + + +Click with any mouse button onto a square and select an effect +in the context menu. Edges with a blue square have already an attached effect, +a grey-colored square indicates that no effect is selected for this edge. + +The number of accessible items in the context menu depends on the settings in the module + +Desktop Effects in the Workspace +category. Select your favorite effects from the Window Management +group. This activates the corresponding items in the context menu. +
    diff --git a/doc/kwinvirtualkeyboard/CMakeLists.txt b/doc/kwinvirtualkeyboard/CMakeLists.txt new file mode 100644 index 0000000..ec32b7e --- /dev/null +++ b/doc/kwinvirtualkeyboard/CMakeLists.txt @@ -0,0 +1,2 @@ +########### install files ############### +kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kcontrol/kwinvirtualkeyboard) diff --git a/doc/kwinvirtualkeyboard/index.docbook b/doc/kwinvirtualkeyboard/index.docbook new file mode 100644 index 0000000..40d18ec --- /dev/null +++ b/doc/kwinvirtualkeyboard/index.docbook @@ -0,0 +1,44 @@ + + + +]> + +
    + + +Virtual Keyboard + + +Yuri +Chornoivan + + + + +2021-04-27 +Plasma 5.22 + + +KDE +Systemsettings +virtual +keyboard + + + + + This module lets you choose the virtual keyboard to use. The virtual keyboard will be automatically enabled when there is no hardware keyboard detected. + + + + Select a virtual keyboard + from the list or choose None if you do not want to use any virtual keyboard. + + + + It is advisable to install corresponding input method engines before using this module. + + +
    diff --git a/doc/windowbehaviour/CMakeLists.txt b/doc/windowbehaviour/CMakeLists.txt new file mode 100644 index 0000000..1f48a01 --- /dev/null +++ b/doc/windowbehaviour/CMakeLists.txt @@ -0,0 +1,2 @@ +########### install files ############### +kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kcontrol/windowbehaviour) diff --git a/doc/windowbehaviour/index.docbook b/doc/windowbehaviour/index.docbook new file mode 100644 index 0000000..5cd88bb --- /dev/null +++ b/doc/windowbehaviour/index.docbook @@ -0,0 +1,1210 @@ + + + +]> + + + +
    + +Window Behavior + +&Mike.McBride; &Mike.McBride.mail; +&Jost.Schenck; &Jost.Schenck.mail; +NatalieClariusnatalie_clarius@yahoo.de + + + +2022-08-31 +Plasma 5.26 + + +KDE +KControl +system settings +actions +window +window placement +window size +window management +window behavior +focus +raise +titlebar +screen +snap + + + + +Window Behavior + + In the upper part of this control module you can see several +tabs: Focus, Titlebar Actions, +Window Actions, Movement and +Advanced. In the +Focus panel you can configure how windows gain or +lose focus, &ie; become active or inactive. Using +Titlebar Actions and Window Actions +you can configure how titlebars and windows react to +mouse clicks. Movement allows you to configure how +windows move and place themselves when started. The +Advanced options cover some specialized options +like window shading. + + + + +Please note that the configuration in this module will not take effect +if you do not use &plasma;'s native window manager, &kwin;. If you do use a +different window manager, please refer to its documentation for how to +customize window behavior. + + + + +Focus + + +The focus of the workspace refers to the window which the +user is currently working on. The window with focus is often referred to +as the active window. + + + +Focus does not necessarily mean the window is the one at the +front — this is referred to as raised, and +although this is configured here as well, focus and raising of windows +are configured independently. + + + +Windows activation policy + + +There are six methods &kwin; can use to determine the current focus: + + + + + +Click to focus + + +A window becomes active when you click into it. +This behaviour is common on other operating systems and likely what you want. + + + + +Click to focus (mouse precedence) + + +This is mostly the same as Click to focus. +If an active window has to be chosen by the system +(⪚ because the currently active one was closed) +the window under the mouse is the preferred candidate. +Unusual, but possible variant of Click to focus. + + + + + +Focus follows mouse + + +Moving the mouse pointer actively over a normal window activates it. New +windows such as the mini command line invoked with +&Alt;F2 will receive the focus, +without you having to point the mouse at them explicitly. +⪚ windows randomly appearing under the mouse will not gain the focus. +Focus stealing prevention takes place as usual. +Think as Click to focus just without having to actually click. + + +In other window managers, this is sometimes known as Sloppy focus +follows mouse. + + + + + +Focus follows mouse (mouse precedence) + + +This is mostly the same as Focus follows mouse. +If an active window has to be chosen by the system +(⪚ because the currently active one was closed) +the window under the mouse is the preferred candidate. +Choose this, if you want a hover controlled focus. + + + + + +Focus under mouse + + +The window that happens to be under the mouse pointer becomes active. If +the mouse is not over a window (for instance, it's over the desktop wallpaper) the last +window that was under the mouse has focus. New windows such as the mini +command line invoked with &Alt;F2 will +not receive the focus, you must move the mouse over them to type. + + + + + +Focus strictly under mouse + + +Similar to Focus under mouse, but even more +strict with its interpretation. Only the window under the mouse pointer is +active. If the mouse pointer is not over a window, no window has focus. +New windows such as the mini command line invoked with +&Alt;F2 will not receive the focus, +you must move the mouse over them to type. + + + + + + + + +Note that Focus under mouse and +Focus strictly under mouse prevent certain +features, such as Focus stealing prevention and the +&Alt; +walk-through-windows dialog, from working properly. + + + + + + +Delay focus by + + +This is the delay after which the window the mouse pointer is over will automatically receive focus. + + + + + +Focus stealing prevention + + +This option specifies how much KWin will try to prevent unwanted focus +stealing caused by unexpected activation of new windows. + + + + + +None +Prevention is turned off and new windows always become activated. + + + +Low +Prevention is enabled; when some window does not have support +for the underlying mechanism and KWin cannot reliably decide whether to activate +the window or not, it will be activated. This setting may have both worse and better +results than the medium level, depending on the applications. + + + +Medium +Prevention is enabled. + + + +High +New windows get activated only +if no window is currently active or if they belong to the currently active +application. This setting is probably not really usable when not using mouse +focus policy. + + + +Extreme +All windows must be explicitly activated by the user. + + + + + +Windows that are prevented from stealing focus are marked as demanding +attention, which by default means their taskbar entry will be highlighted. +This can be changed in the Notifications control module. + + + + + +Raising windows + + +Besides receiving focus, you can also control under which conditions windows get raised, &ie; brought to the front. + + + +You should make sure that at least one of the raising options is enabled, otherwise windows will not be raised at all. + + + +Click raises active window will bring a window to the front when it is clicked on. This is enabled by default with a click to focus policy. + + + +By activating Raise on hover, delayed by you can alternatively bring a window to the front if the mouse pointer is over that window for a specified period of time. You can determine the delay for this option by using the spin box control. This auto-raising option is only available with a hover to focus policy. + + + + +Setting the delay too short will cause a rapid fire changing of +windows, which can be quite distracting. Most people will like a delay +of 100-300 ms. This is responsive, but it will let you slide over the +corners of a window on your way to your destination without bringing +that window to the front. + + + + + + +Multiscreen behavior + + +This controls the behavior of window focus with multiple screens. Note that these options appear only when more than one screen is currently connected. + + + + + +Active screen follows mouse + + +When this option is enabled, the active screen (where new windows appear, for example) is the screen containing the mouse pointer. When disabled, the active screen is the screen containing the focused window. + + + + + +Separate screen focus + + +When this option is enabled, focus operations are limited only to the active screen. For instance, when you close a window, then the next window to receive focus will be a window on the active screen, even if there is a more recently used window on a different screen. + + + + + + + + + + + +Titlebar Actions + + +In this panel you can configure what happens to windows when a mousebutton is +clicked on their titlebars. + + + +<guilabel>Titlebar Actions</guilabel> + + +This section allows you to determine what happens when you double-click +or scroll the mouse wheel on the titlebar of a window. + + + +The following actions are available for Double-click: + + + + + +Maximize + + +Resizes the window to fill the height and width of the screen. + + + + + +Vertically maximize + + +Resizes the window to the height of the screen. + + + + + +Horizontally maximize + + +Resizes the window to the width of the screen. + + + + + +Minimize + + +Hides the window into its minimized state, from which it can be restored ⪚ via the Task Manager or Task Switcher. + + + + + +Shade + + +Causes the window to be +reduced to simply the titlebar. Double-clicking on the titlebar again +restores the window to its normal size. + + + + + +Close + + +Closes the window. + + + + + +Show on all desktops + + +Makes the window be visible on all Virtual Desktops. + + + + + +Do nothing + + +Nothing happens on double-click. + + + + + + + +The Mouse wheel can be used to trigger an action depending on whether it is scrolled up or down: + + + + + +Raise/lower + + +Scrolling up will move the window on top of other windows. + + +Scrolling down will move the window below other windows. + + + + + +Shade/unshade + + +Scrolling up will collapse the window to just its titlebar. + + +Scrolling down will restore the window to its normal size. + + + + + +Maximize/restore + + +Scrolling up will maximize the window to fill the whole screen. + + +Scrolling down will restore it to its previous size. + + + + + +Keep above/below + + +Scrolling up will make the window stay on top, covering other windows. + + +Scrolling down will make the window stay covered below other windows. + + + + + +Move to previous/next desktop + + +Scrolling up will move the window to the previous Virtual Desktop. + + +Scrolling down will move the window to the next Virtual Desktop. + + + + + +Change opacity + + +Scrolling up will make the window less transparent. + + +Scrolling down will make the window more transparent. + + + + + +Do nothing + + +Nothing happens when scrolling up or down on the window's titlebar. + + + + + + + + +You can have windows automatically unshade when you simply place the +mouse over their shaded titlebar. Just check the Window +unshading check box in the Advanced tab of +this module. This is a great way to reclaim screen space when you are +cutting and pasting between a lot of windows, for example. + + + + + + +<guilabel>Titlebar and Frame Actions</guilabel> + + +This section allows you to determine what happens when you single click +on the titlebar or frame of a window. Notice that you can have +different actions associated with the same click depending on whether +the window is active or not. + + + For each combination of mousebuttons, Active and +Inactive, you can select the most appropriate choice. The actions are +as follows: + + + + + +Raise + + +Will bring the window to the top of the window stack. All other windows +which overlap with this one will be hidden below it. + + + + + +Lower + + +Will move this window to the bottom of the window stack. This will get the +window out of the way. + + + + + +Toggle raise and lower + + +This will raise windows which are not on top, and lower windows which +are already on top. + + + + + +Minimize + + +Hides the window into its minimized state, from which it can be restored ⪚ via the Task Manager or Task Switcher. + + + + + +Shade + + +Causes the window to be +reduced to simply the titlebar. Double-clicking on the titlebar again +restores the window to its normal size. + + + + + +Close + + +Closes the window. + + + + + +Show actions menu + + +Will bring up a small submenu where you can choose window related +commands (&ie; Move to Desktop, Move to Screen, Maximize, Minimize, Close, &etc;). + + + + + +Do nothing + + +Nothing happens on click. + + + + + + + + + +<guilabel>Maximize Button Actions</guilabel> + + +This section allows you to determine the behavior of the three mouse buttons +onto the maximize button. + + + + + +Maximize + + +Resizes the window to the height and width of the screen. + + + + + +Vertically maximize + + +Resizes the window to the height of the screen. + + + + + +Horizontally maximize + + +Resizes the window to the width of the screen. + + + + + + + + + + + +Window Actions + + +<guilabel>Inactive Inner Window</guilabel> + + +This part of the module, allows you to configure what happens when you +click on an inactive window, with any of the three mouse buttons or use +the mouse wheel. + + + +Your choices are as follows: + + + + + +Activate, raise and pass click + + +This makes the clicked window active, raises it to the top of the +display, and passes a mouse click to the application within the window. + + + + + +Activate and pass click + + +This makes the clicked window active and passes a mouse click to the +application within the window. + + + + + +Activate + + +This simply makes the clicked window active. The mouse click is not +passed on to the application within the window. + + + + + +Activate and raise + + +This makes the clicked window active and raises the window to the top of +the display. The mouse click is not passed on to the application within +the window. + + + + + + + +Your choices for Mouse wheel are as follows: + + + + + +Scroll + + +Just scrolls the content within the window. + + + + + +Activate and scroll + + +This makes the clicked window active and scrolls the content. + + + + + +Activate, raise and scroll + + +This makes the clicked window active, raises the window to the top of +the display, and scrolls the content. + + + + + + + + + +<guilabel>Inner Window, Titlebar and Frame</guilabel> + + +This bottom section allows you to configure additional actions when +clicking on a window with a modifier key pressed. + + + +As a Modifier key, you can select between Meta (default) or Alt. + + + +Once again, you can select different actions for +Left, Middle and +Right button clicks and the Mouse +wheel. + + + +Your choices for the mouse buttons are: + + + + + +Move + + +Allows you to drag the selected window around the workspace. + + + + + +Activate, raise and move + + +This makes the clicked window active, raises it to the top of the +window stack, and drags the window around the workspace. + + + + + +Toggle raise and lower + + +This will raise windows which are not on top, and lower windows which +are already on top. + + + + + +Resize + + +Allows you to change the size of the selected window. + + + + + +Raise + + +Will bring the window to the top of the window stack. All other windows +which overlap with this one will be hidden below it. + + + + + +Lower + + + Will move this window to the bottom of the window stack. This will get the +window out of the way. + + + + + +Minimize + + +Hides the window into its minimized state, from which it can be restored ⪚ via the Task Manager or Task Switcher. + + + + + +Decrease opacity + + +Makes the window more transparent. + + + + + +Increase opacity + + +Makes the window less transparent. + + + + + +Do nothing + + +Nothing happens on click. + + + + + + + +Your choices for the mouse wheel are: + + + + + +Raise/lower + + +Scrolling up will move the window on top of other windows. + + +Scrolling down will move the window below other windows. + + + + + +Shade/unshade + + +Scrolling up will collapse the window to just its titlebar. + + +Scrolling down will restore the window to its normal size. + + + + + +Maximize/restore + + +Scrolling up will maximize the window to fill the whole screen. + + +Scrolling down will restore it to its previous size. + + + + + +Keep above/below + + +Scrolling up will make the window stay on top, covering other windows. + + +Scrolling down will make the window stay covered below other windows. + + + + + +Move to previous/next desktop + + +Scrolling up will move the window to the previous Virtual Desktop. + + +Scrolling down will move the window to the next Virtual Desktop. + + + + + +Change opacity + + +Scrolling up will make the window less transparent. + + +Scrolling down will make the window more transparent. + + + + + +Do nothing + + +Nothing happens on when scrolling up or down the window's titlebar. + + + + + + + + + + + +Movement + +This page allows you to configure the Snap +Zones. These are like a magnetic field along the side of +the screen and each window, which will make windows snap alongside +when moved near. + + + + + +Screen edge snap zone + +Here you can set the snap zone for screen borders. Moving a +window within the configured distance will make it snap to the edge of +the screen. + + + + + +Window snap zone + + +Here you can set the snap zone for windows. As with screen +borders, moving a window near to another will make it snap to the edge +as if the windows were magnetized. + + + + + +Center snap zone + + +Here you can set the snap zone for the screen center, &ie; the +strength of the magnetic field which will make windows snap +to the center of the screen when moved near it. + + + + + +Snap windows: Only when overlapping + + +If checked, windows will not snap together if they are only near +each other, they must be overlapping, by the configured amount or +less. + + + + + + + + +In the Screen Edges settings module in the Workspace Behavior section of the system settings, you can configure windows to be quick-tiled to the whole, half, or quarter of the screen when dragged near the screen edges. + + + + + + +Advanced + + +In the Advanced panel you can do more advanced fine +tuning to the window behavior. + + + +Window unshading + + + +On titlebar hover after + + +If this option is enabled, a shaded window will un-shade automatically +when the mouse pointer has been over the titlebar for some time. Use +the spinbox to configure the delay un-shading. + + + + + + + +Window placement + + +The placement policy determines where a new window will appear +on the screen. + + + +In a multi-monitor setup, the screen for windows to appear on is always the active screen (that is, the screen that has the mouse pointer or the focused window; see Multiscreen behavior), with the exception of windows remembering their previous position (see below). + + + + + +Minimal Overlapping + + +Will place all new windows in such a manner as to overlap existing windows as little as possible. + + + + + +Maximized + + +Will try to maximize all new windows to fill the whole screen. + + + + + +Cascaded + + +Will cascade all new windows, opening each one down and to the right of the active window, starting from the top left corner of the screen when no windows are already open. + + + + + +Random + + +Will place all new windows in random locations. + + + + + +Centered + + +Will place all new windows in the center of the screen. + + + + + +In Top-Left Corner + + +Will place all new windows with their top left corner in the top left corner of +the screen. + + + + + +Under Mouse + + +Will place all new windows centered under the mouse pointer. + + + + + + + +Check the Allow apps to remember the positions of +their own windows item to open windows where they previously were rather than by the placement method chosen above. Note that this remembered position includes the screen assignment, so windows may open on a screen other than the active one if this is where they were last located. Note also that this option is only available on X11, not on Wayland, and is only supported by some KDE applications. + + + + +If you would like some windows to appear on specific positions, screens, or Virtual Desktops, you can set up Window Rules to configure special window or application settings. You can find this by right-clicking on the titlebar of a window and choosing More Actions, or in the Window Rules module in the Window Management section of system settings. + + + + + + + +Special windows + + + + +Hide utility windows for inactive applications + + +When turned on, utility windows (tool windows, torn-off menus, ...) of +inactive applications will be hidden and will be shown only when the +application becomes active. Note that applications have to mark the windows +with the proper window type for this feature to work. + + + + + + + + + +Virtual Desktop behavior + + +Sometimes calling an application will activate an existing window rather than opening a new window. This setting controls what should happen if that activated window is located on a Virtual Desktop other than the current one. + + + + + +Switch to that Virtual Desktop + + +Will switch to the Virtual Desktop where the window is currently located. + + +Choose this option if you would like the active Virtual Desktop to automatically follow windows to their assigned Virtual Desktop. + + + + + +Bring window to current Virtual Desktop + + +Will cause the window to jump to the active Virtual Desktop. + + +Choose this option if you would like windows to always open on the current Virtual Desktop, and the active Virtual Desktop to only switch when navigating there manually. + + + + + + + + + + + + +
    diff --git a/doc/windowspecific/CMakeLists.txt b/doc/windowspecific/CMakeLists.txt new file mode 100644 index 0000000..7320149 --- /dev/null +++ b/doc/windowspecific/CMakeLists.txt @@ -0,0 +1,2 @@ +########### install files ############### +kdoctools_create_handbook(index.docbook INSTALL_DESTINATION ${KDE_INSTALL_DOCBUNDLEDIR}/en SUBDIR kcontrol/windowspecific) diff --git a/doc/windowspecific/Face-smile.png b/doc/windowspecific/Face-smile.png new file mode 100644 index 0000000000000000000000000000000000000000..501cc796a4c4d3350bc54e5b9db44e5a5ef5bd4e GIT binary patch literal 1233 zcmV;?1TOoDP)J2(Y|dwPHGf4hw-aoe;1?|#4MdG7iD-}n6=1Moy> zcG}6S5?$h}4|^5V9hYg_FUT!*S$^8uA7usCzw?T(Ip8WM-c)$3(VV~>ig(Ua4HPf6 z;`d?CgNsym(ib>PYh1RCg;XcG>Tz`?(6$cf+y?aQ1Nx5v z#%u!B)T5c;G-k;3RbRV=OleG}y6+cESSyzUt!ue33h4btG8-xZ9@G-3rXJ0RBe=aq zeiM~@=T9=X-F{{49Q|nd3gB)M(6yVVN&pPU0aGD2tO3Su6R4)1H0w{}S0&K24)tlC z`nF_yhe^hH;Y?*d`y9}%2JR*EBs+MbgHkf{FOu0v6M<^#(M;kPG656sRlkNyvCb-+ z@s%A+u1lF|eRS&(PpOs`t1AZROMx;j0Lu7Pz_5l41(bWw)n<6M>sNiEa}oe+l#^LAlFcq{iUSfJr5w+#MwK`i~NaxRU0d zbd0H2qGFe0fn*%Xw8o#DFjn#s(C{X3=R=?~66lT>iKMNTY1?j8Q;%lEAuef1OCIVt zN3AffiD4E)<~)oPF9vGf09xJy^x>3*c80V-ecy7kBLu3cM>FCOmo%gm9u4~(Ew;pc zKiI>f@&u~n7;g+lOR)mz*h(i&O4XA{w@sj$dScd6z8d4VdD=hTCav%&i=M5uJ>#A< zJDAV7;ZdnCP#cJbvq8{guHfcd=n#+zEfRrh(bJp^K!Yk6xV4&V0!S-7E=N66VA~a4 zsSL)f`FN1O2-YjF0xcil+UZq@4RuD&9v{@_DRBShdVYNiISDevu5rP&b3EnU&A^Qy z(vVhoke_WI-`&rwJ;&z2l)F$oC#z-|&>Dt;wg{A+2u5nei`Ww6gw#kMlx4qzf!k5Q zU!MSVfy5OW^EnUbQf67nZ_>%@PM;0K2^VoYBSqerQ2X&T3T_Y%+^2<%13IDs-RD4K z2vF@$9KjvR_l6<+S&+|T?+W#HrtdrD4&x~|k=%08OKkY@idQHVEe?Td>d}li+;lMp z3uPcZ`8@IM*1q3v!dUn+Oqb;t$Z-|vEqTw2=l8Ji1p?KgH+0?u!&enBsb5F?ff*B| z|KIm11seU_EIiPfsYSx0p=8@=!;>&yycza0u?XTts2R?d@gICDHTjq#)( vn91S9;m+aB;mhH}v4~?K$6Su5r_%oi_kJ@#1rdiF00000NkvXXu0mjfmYY*6 literal 0 HcmV?d00001 diff --git a/doc/windowspecific/akgregator-info.png b/doc/windowspecific/akgregator-info.png new file mode 100644 index 0000000000000000000000000000000000000000..1b10b82ab98fb3e65f94d8f13832936adcb0ceee GIT binary patch literal 46542 zcmce8bx@UE+wVrY1O@3v=@jV(0R^PH8|jixgKp_=0cntu?(PNw>F(GxXYJ>EznF8* zJ9GXz<2a6+d*89{wXW+|i(mygDNHmXGzbKO`TpHoB?#mRE(8J>jDiF{8Mpko0scWS z7yTd#fmB4I-x(r;|5F&hQ~Cgbc+fx~e%~OF8}O0eHU#3#27&AvKp=dH5D30~>Tg8> z@B<_x8L79BN7$dtru-Q2399`&O(zKCX$R~tT#sFW3HT8C)B6t+$bV7sF_;jTQFecT zGeX|K6;*MY-(NKMQt2XUU3rW@U&48}9X0A{r}S0?cPI0lhEzgy!zd?&f{qm7?b|{l z(VZsfw`94%b3HE8DoWO~Mo zU9Lpc4<26LXzUD;;OAuWs`1A`D3^!ABFjQeq24;z?!O*BOfpzo+Z1T!p)I{lR- z-@dsZcJqku?CeDFi=CV_nz9kKU1|=m%7L$`skydw+By{Mk7bEFy6NnEdr2zr_U+r3 z@tb&WJ}O|5A4zvHsB38OEVv#R_O|-?+cvm9idq+!e4NdE;3&(;ci1}AjUW{kouCQF zJc`RMhOcut!%Kf9LvV}T>C`_y{ya|FUo17@WPjkL$;tD|bMAtHey%(& zBFXg)TMXsoo6`8}_i}QQm{I9T7W=0tuoDW?339{?4i4^9VkNoJgTsRI^QJ7TJn86O zW-1h_jJy;lW@H@acs-VD5$3%whCZ?biL88<-nyAg;^dW0St{;b+=zM7y-pnvh=IS^8I>64KpT+<_f5x= z_xg6=IA=u7gHV-0Kf-}J(7?h_G1n$vmSp5hUJ(jfliIpKWo6|dQ~}rVvv1J||H%1Y zpU2HAql3rky@RTWYtJnX<^=G#k6aua%38G_B_|{=9Go1GD5w~cjcG^9A4e6zV~6Q> z`o=Y+$V7h)MrLAWBH(sJnP^UzcY8bL!SIPsckD^XOyi(~tAhs(vMVjqm*tQ#@e^?V zox2+=0lHd*7h-o+Kkn_Q_PA_bvGGjKO1PWRgp+p>E~At1comP@q;T4b4EzXBVu$h@ z<7?x#xBp4S>Ir{I$^JQm+h#*g0|D|%;LpcfX^!3LpKX5-WyW&z2pb9>(fq~QGF?3g zzXbbb8oYj8Jz1c|J95CP(;Sjih$lFQ9(xy;jZ=m99W!aU`C_;kS|vYRYlkO`ba5DE&{tP+c1cYf z>TxmMUW^?4X?8l&=Z!~JV@VCAVNQbzUt^STG4}kXyuTf7bP#XWJ#8B7dCvQ$Ks)99 z{1YC_BV|uDjSwZ=W2=EsI$Hpevz62KEUGbn^}F54f~T$5p$X}hk3WUt3RJQhSDZa0 zY0mIi4EKYwzzs9@)R=qUYt&i4A`c_B(NHTCbN_jxvRdhD5Piz1ge9T3C!c;{y7Myw z?N6ESk!|Djk5`t}zemqcvz6%k`uoXlcgG9GBMeq8#&M`v?OfdA&^G#CI~}^dd`Yu% zxcNn^#Ao-czhfv)?m&OzxFY7!ZEk32$j$ehZQAZSZ7qMq$kt4*>(n$jG?Mk#oqJrB zZAo%0vvSB8KADf7Ad${p%5$8U0^J;<{E=(Ic+JEVi?{i&A#&jTI?Z@vTWd zh5Or($LQ2Z8~W2{QHxh^rdZ9tf;ynmey*QHwCk&Z5g4C(57ynO!mhj`IEntUceUZ0 zs@HrRdt$U!W=UG#laoX8@-{j$#T4CqqP&ZCU90jdJPwegJ>v#B60f`$p8DKc_28&i zVn;eUJJ3+jB?jGACENXJ#Psv`M?`N;YU+1NbbpBoq)(&TYPn%MFRZ75>2bGD4E zuIAJ#i6?Dq)}$~y=K7-T%)I1uyaXm%9`NFn-p>q|O%(X(PBOeOzh|j595^yD zGaGMwFK($?CN2Fl;M7#c*utW(`K#1uZr)g>rt#p=U>!bL+o`IRlk?R4!n~2CW&eq? z`8ydI+7B_^NlwiH7uOqmyZf<4sh*eQKYFy&6be;UE($x7&U~zAPx+Sz@4ie4mu!Xj zQgk3)zM^E09@k5sU05*8R;%)c3Oyy`edhCivVpDvbvo_epDw@LX1p?$NywIuWO0i>rr>?$ z?;RLX>A5W0VXofeV)Jp~C*tO&St_(4wn-I{#Wd!55@u;B_BvC0_g zg8^+@lrNH{XT7;Ri~!}a!olIL@niRPAPnxR%}KeeesyCn>Dz}Ckr*dJ`=w=Tl^je2_u+qY4ti&F7&$A z8Q7gFjiWArI3f~6o|Y;!_x3nG){Kcg2@!~QcZ>TmM63%IOKv`=M^k!O7)Gr0E9t!F z{$h$BeIWBp%th0s=I7<2iHYlEN!K?2%#vFi)lzJCUWXf|qo;=>CH$_u?wiF~p@OP0 z{e2u;7E8aLadTNCW$G;^f0KIaczdWTl=kNHh}q!ga1)!gb#Vn72kmRVnyHNQ&G%r4HE=^5MSptqP+MxTFQY^T4OTQ~dYv(t&%Mqo z|MkmYYF^(lH>c&1eA{BKm>rxUc{@=k!%+}T&P<>27B`qhE@{f7@5$0sr1{iH=4%(Gc7Jvj{UpZKK+AAq$#0A_4J zbK_;l8i7md5eQRdlg#OOmqrh;or6XLlq~cPG8_dpUYH6b%BYBqhBtlGl(A zT6|7;OrVOc6pq)sO=aXU`kJhd!ZGPy8<&ROf_kviPhP*S|I6;&-Th4(vO8|9ohS+E z{;AFbMZuMA9~uOlHr<@S;n+|gM(DB8A8;xt2@Wq zK6iGSP-SOV!Cy-?j|yE`E#*F2aM`Eief0u7?d?<2JUy^hFIJ)zuaE;WXF4v3Yr)so zM=Nl)KY3JEenRlq>scJ)RG)L^6Xa;oYz^10y|eQEGONJM%g6i7@4@ zfg8Tod&$6u*F<*s2a}qf7nNHb|J>uFM%hzvK^k7(xbraCn!6L|Jpx_%@VWbX;aIN2 zW+V9!F}GM*;JBV%Vsi3x0+M7w*1dL zijK*z_vXe(lN$kTwK7n)cZ=ZcC%(AeRB(9s)_T&}_QhY%mDj<+eJYowAJ5M;9vmNe zXBW=;kk@8?w+ZwIlLxAaYeVCfcw0i5u7z$_^%}Z6pmqmDoCLg|ATrI5(M~@28GQG5 z<9)2l?GNd3*_#)W;$)Ev&y%HX%$14ilCsszh$|Sr+Y63Rk)OWz6Dw3X zUC3CN`~AU#rnBk=Hm)9VvC^Y^6}7bkhac)%geWN~Z|^;QAHDXNSUm)hBT39B%Rk@M*zlJmbFYj> zuho}cMO>V?I2Ece!AZ(Vi zka(|_fHNO?u9qhTma1N8vRh+bE!ve|WYp1)5QD=*dpZ`GXHJowXuu+SiYu`Gj2K^k z-KpWDIGMydCQ9)U=||lD`JX4{{XQzSN`-Ipln5X6vlKFC_Co)7E7huD!Y-A)Q3N62 z@Iv9XU1m9b5ECd2Dxl-Cx&OM8;S(lSD3U)>0q|?`o1c+QP0T(CEHSaE7$Ray0Egc9 z|5&a)afvJqBY5q%f$>y8K;C$5RTGp@ona@(iTm`1p5Z;Ih6VQA#K-!wDN~25 ztM|LprNN*E>!6}Au`M_DU;n*NQWpv+Idj7jo~31Hf9C0_ZM)c)?gXIcXX(N>#Uo|I zj$3hQW(Mz22nh)%OLV{9I9r{JnNN@rdi6c9vQVab$Z2V~w$O*GAp5eYv^E!_S*NV;}vSX>;6V-Siful)74L`kv2i1q0*e#y! z^XV9w#G!s8<|9pHr5P)~_i&_KSyM=TJ)yM!7jFJ$GYx%(w2ce*v%ky8k!0rY9&77~ zu-}Rqjo)8*h^L!-v;N)Repl6vsYr)g$HzZWp-cRfbilTVW6R58DVGwk5V!*Ue7skJ ze=vJ+@yO)pS>>4qhsO3URG9Q5UgtHE72XCOI4vDffg<-I(Jhv(_*e&n`JufFwf`%J6F6ARyw?4=I}uZ`8@@H?C%w z|2^J*JA|H4wmG{sndBtVkB%WuhKP)g#l`XtdKzD+xq0`D-G6oE?I4N8i|?t@c}BCX zt{RPZ$ivsVY)ryio^U`=0I*&x$UM(6F9gFYAAed+E%%3I9T9ftm%L@-;fXgNU|3wK zL6Vb2uNlolfdTh?|CA>EmB6T(MQ~V%*;HhUA6cnxOJDGWc@qD4ga^PHNO2P6?`d>zTo#AKT(RaGxtc`wQ>_A+31dS)i~N~XwkYY85J z;U|bQn(2r6?(4Jk9xm=2x0EXGlTwaKXjNOR+4>n|7~2yuow+Z86w%(+(IbjwKBAxb zt%0AsMHmloTNo=J!^y|k(Qn)zDXiK`y1q7f{yZiqB6y{9rwS|bEV>aC;-rhACpQ;| zZTx}_*$Q;vX;?tjrPY0O6#RVBl1VpHy2`WVnROL>_mxE-^CcS(!coWz2pgv&hv7b* zrCQN12F<-1b(gr`%zWyql%p z7fff~BK#5aPEVZoveH~G4xKXH4PuA@ls<@xcC&=L{y3cr zD?>$(q#M6Xk5H3Hi%s%ODye=!lTH?(Ek=%EBOqi>HS#M2o72pq`cdHm?ZgDOvKEu4 zu8)27dpjG%Vp%pMBqfnno<)NW`Mc$s&-KVZIx*2?KRY<#J6V$+RH?xku0{b3Ntga?1>$qtQ35ZRanHL z>7`Ee+b`J4I1G5cs##uLv9h<55uH?My3@~jy&O(~L9&0~)?m(^N?_XH(f<_q zE(_)6=bJ($>0;?((PI1e$x!l?KFj5g_}Iru$9dms2(@*>TlwA~0(D00&J!06ajNKQ zv|Ln>LH(y+P7XYzqQc<{+24NmT$4o?)9oiKBu~eh?2bpHjlWEn7S0c*FX_j7C-kba z=+$f8b)IH2`8)@5(Kolb_ad-!1{uZp!TyikMFZy#j;0LxfEhLK`?+G^pPxSdBvr12 z4-wW*A#R|99d{L({7XDQ;2+@%PYAw$?>qUtfJ^| z@K8EO+B;LK*HLUudg(H ztPogRTeFxs`A~E8=c0`KD2rMO(2*h6d-8>c?8nTk#!C-#aMgjqvQ2_C%3X^ku!%)>GS3TOM9Mz~3$R#UX!>$8t!79T<8lyD^CkY-}zf zH|mXN$QW*~$`6i6-AmOO9+PS-^~%go#y4`;aTHRa7i+hwR*6?_o_8mElFeyQ&JwNa zw^<5z#?B`Njq|IT1q#h1pycy2A)6d#%c9 zObs5J5s(n8Oy;0}>fCX(iHZ3@eOUb62n1XMF#0<6jxSi{<$d&8qT`v9IaZn~&Ud(@ zTyM@-k4PKdNlUjc4M(^JVw&A|GXn)VDmuC^N)#Rg|C@6DNcm=dMsQfz+d`GG5*c$3 z=>G6$@j%V`8>wGf%v)aG7ITe+)B#v|@-3get{&WWrwhZ?icE**o%e<9Q9dLi0d1U+ z+XMHNv>UF+pgU;NcTL8$F~Z5c=SfwlPf_!X{7-pn>K&&t5JTg!iHW6>UYlP1y|0Kp zJ$*vK#TC&dli;*6{t6fE)dwPOlg@HV4Gj%!0^Gq{a-Ysi;aZ~c`Cr%ScPx5F^Y|I3 zD^fAR5fO%ib*OZz(s+Xzp`le(h6|pChOtbFUjR9PQ>c84DjiQ}s6n0))#{3l=d{!R zVo-Kydn$+6uKC0xy@!zGdHWX$nR+KEODPLW7~w@J0%{ZSyHtu?w53QxM;hRg+uPk=#ypY_g7Z*8}as99-AbZxfaF0}B-ovHB@WT5BGZ&m;f6+DS{QOO<^ozBgh-|GX2H+w1 zbGSaZA_RW>yZsmTn)?(3?&gOkCu3`WL(MJ+EmR8k;YG@7Ze; z@|4&&&I-NQEM5=bv1aPby-wvn8rag88yQwE>|9(-ixl$8c%3JM<8eTA6hS1lr>-^r z(-i#){MUDKaw{FEYi9!K&Mnq|+q-0#GqnHJSOqv)@__{x#I)0J=&9M`t5vL($OIm!R~vg z4vJXCLD_*)yrn3xSeg zY-7^}#t$r3%j)E_W}i2ZEZ^-vGlq`o}T=Nf0`1uYI@|; zv)`g(Y1Mil`ip%I3oPXA9H~&PMSu}4a?Kh6Zj7p1NxrT;H*I060QEJTCl$+ z4$3&BK(UFakO&jBXb23jB#r06(dn0x49m#@jSY9l&E;TDX1UhW*v>8zi2LSH+uVF} zdoXG9`8I8Q?Y6>a_@9ifRJ)Mye4?9?I3AkeH!?QWfT5 z8Jl%4h3tn96~D?2w*QLm&oyL(g@qZ_riE5<48${0aIr+VB4HTLhm~RUJR^Jo8w4b* z?MUgo_KuFU%?~N|=d&k&4m?(I!9xFKIrpTOOsulDwg=iSK}DTYXMgfMM-ro>d!9%} zMn-&5u(VtNi&Ut6!P~zxOx8 zBRgS43wT)n`0-;_LmP*fI1uQ*YHINs0?#$->wkl$92^`xRCVW(ZY`D<*uWnn)8b={A+;27%}{S|rtQIe{diDV$m;X~KWIjy<+3^Nso5!`0k$YE zW~!)U@QRQ&bnou&TG?DR(%4#GPdxI%GB>A{jyr8XYL}2p6B@8On13>w>maUHtOK<6 za5i?%KF+&yIw|Bp5}VYxI7AUyYbMZg0YMiUJc>T!V_^8*>Qba%#v-F${I+PyasLW+ ziI^M7$;nABRlpz2jJ72*`H}DPlM!OL2?$`M(@maa#Khn{i>7sC3JnVD?CQEM93L2n zD|+za@P!M-AI$$$xbKd_YrDnx(%YPt7R(&jT#ph&rlNX{HY3GiBEh^Cq1#}wRE%mY z8NtTH@3M-p@Gu`uo#{4O^&~1iUDQbFyCIbo0P2N>g#n@Jz`g~RGk8oT&cga9d3AMH z`9Ec>tQZ5ZNC#_LT(Lb4_+AMJC@{noYt}#EsQN-X`J0DdIm?R|&*%0-sk5Qs%3M$8 z*!s3Ze-X1zY8v|B1ejmji3I&|1I^Sx8!2q%R|3d_lRByGMrC@5%s zHaG@IBe#Xa+)`4=-<6esh0*cn4~gHm&bJ*N=Ct`w7KVTcO-n2FluSs>OYF(iPqnu8 z_AjhIhFa(6wLECQDfUJMUdda`*FAd{9lbwag`XzZ)8uJrJ%7h|u+Vffcp4NM+0*`q zB;mFRqv3M@m5@+d`O?@@`X0A!R9sx#R#$=et;^l{8IqBa@t9?`^=uuXvNDdPO4;Sx z4PZ;%HF?vPxqghy$@#icrn^w{r^z+4HPfJ@#@G5bw64w#EVwK8dnD! zK_wbcE2a_@{5m}`ufm}zoSA_fMR6+K-}$H&jae{XJX!us;_=azI6 zIV?JjZ|&cq5QQU!OLhz|dXDaWiltX30xQgNs<`dDDI-R0T7cABY(m1gJq==R+uo*f zHkbW*0DL6MW^hQJOKWKnk&zv$T%MR+hp2vhLo`0Y0lamybQ?vD^q~~ajRee;chYi_ zMN_8(0pW->qW8a@XRD4NrUS8E-Q7qK92^`Nkcc2{KuZ-?jCxK+7R#s|&uKN2HD&o; zR(2gKTthgGKW!UDx(@~^=|?y*Ko>B4IWNl`7)g@n)0EGcfZat2=YKU*tkV=g-4js- zz$-u^rz#e+^YgDgH{U$mcp}@m9Q5V(DQ{0!z?1SfUJmxp6^MFUP8S1gf;@4X2KT1{ zUlJA$*weRyGBCu2un2lo8H|nV+(Nw@xT~#ZOs;y_KOHUN18E~s&^UQTOhLtbI9)1# zVk5crqnVjBX!w<&UqR?zNPApt4||f!u9h4&)dQfO;mbF7Jal~~PYcvQ*+`)yHiO=V z*|~+2rP+N-I7UWBnlN%0&_6yba2UMWwVJOUa<6R*S`kk?8>&6uUQ7U>j(6H0WGpt$ zH9zf)1(JE+R9Hdv%!U#}nshI*2lHb<`hfcD!`HyELMLt-`D?14(GhsA>FL31OPbqD z@?7naTK6g|c|2xKIWsdFNXCcjZ*_G%9#GPE(50ht*X7S%FqjvJLFAW`!g!DL6aZXN zZ~#8f8-*%KXCP91{6n{e(DUO1(X))G<4HIGwEv#bX8>#}n?UAG0Db7x*|HZsKvHP2 z)dixBXw{DoWa$mzMD}}^q?R*3uT?glXY=4H=gUn+l5#g+(f^Z}$a`b48aOTvqmeWhai? z%99WfK*3|ulRj#OcRRQkhP@>4-K?3taNS>2s3<7yJv|ErrlUKbK&O68`jHeB)jRqu z2erwir1gL*Cp#!SG)E~fM6%>-!S=`DH0ZthsfS*~_4o!OSm#HD&~Pb9V+5gsR;@^0|g1Nb;{YKtOS~`|}Z2MIPXhH5+UNQG-zv6B9>^Jh^{6 zdxxeqTpojpa94M8EA};kS=-lmZLN9W9Ix2a?#lxtsw4lVt3G>-@=Q5zuE^k+uAs{9)3ic?lP9d>8w6-~%!>FI;PXGwTNDJLC~!Y3|AWaqnMY31ei&AN4ojOzCe zE{?0d8M4`c!_FOj#!Q*Lxy^utkqUkX-L=B{1#l&pZMz@2b6#XG)h1_I~KoX;!{$Ys>0> zAOQ2e4}D79;dIhM&uZpJZ__i?qO0SBKV@^j-34{|TH0Jg@!;)G#!WBDc{+FF2Cmj- z!et*%IrJ#~|Z8p`f+|2dYzle)n-3{zukiHHQ_E~(Z zqeFuHl&rgFAtkyaFk7wY+Oqo%0E+&Ab!2$+lHyb+hYOc&G}vvHyDYNmTwp;cUcSKv zxT2<|hQs_4L+Ez7hc`kUG}5*C3_?CP3W$Y;h1c;ItxAbf5;*@mSPL?lL+fCZ2eVA| z=M{{~+coR9!X}w|jcoJv;FaZ50KKd=pwwTH&&}83_2G`OP)u$H=fcR?Q(?DgX~}T= zlq9$R9vxa-w)LD8XJACQ)M$50Ig!s8Z)qtGRtb(<9{?n-I_yzpy0Ik#FjzM6oZUvN zshzyV*67&JM>b6;^!yw{TraYACJ+_2{IhEQL*@{q=a3lbb z!PRrris&`lzb^Yc#sC#+|6u?4*uEu0O|?6mP}RT(WNgT%OO5xppSihv7#SLhURRo) zob)wAu8|QGfmZQjC^bB^D3lV&%~?@$r2L~daq+N|D$rf6^i-ekOnpUr(eJc0Nm%#S9*25xmZg*kQ!m>*uso=>d0*jO=Oq zrw&yF#zl|8a1e9by|x{s8yu89IkxXiyok4we$pjjL)HKo`s2>i^vVL48Oho{$XCh0)J z>Kq=%Q&v`fxH@A6xz7(<+EZHdU3Cv*i}(Tf?`&Hvkt42u=ZV$6yA33JABF9Q@XItF|Ft zBIP0g2zGNMZHSeai^FSD@3LH4bC$s4&qwF(JzSqNA( zZzUxa>npK9dk5WP2P$VLu2pNd?C!>niIEK!5nbF|#U>#CyOTfTjI9Of>HF-TonT?) zPBAi{l0HH&8bt1;BA`Hx{AE3Mw{o)lJ3K9o89ddWM&}fe^qs!1gtKB~?C|Go@dBJv z20(lGz-BOc(Mz;y{)Wquj_rK9y3sWl_45g^0UZ>?4vnEe-6FZF%2+gj#BM?16<;q86GLXPRzvR?xnC3rO%anUjbJHqcUsqL-4YwouTX z8n#BQ#L}yVj8O+pD(51qaDK2Y!V#Z9E~TGoNk?O^s=;-Lr4JpKsq@<+%r3S2{+hrAac&8=P z@86R{s>7>SoON^rKy%DA3*Utf;3ck6#os0x+k3wJ8xj%BzK(eejYxA-_egZ@d;UtJ z*^Lr4_&r#Bo6&r|Y4T!5RDkGo2RI8?8a(LyZoHrYdJS+Z0hn@V&_~|cA>3c88ex;C zYWi|=LR zrGVnGb833-AqafGSXS+LkZ^^t=r#}5qT}Oh2uVpv0Y^DV{#|dnMy&xmpnGT=12Iui z?=;+ahElk)I;_eZkMt5(yCaSf1j~im|==k^MBE`hi z)Zy}vJ8*oyM7jd$9ua6Tuow~`#y=%HQtg^w@zT%P!-WS2w}EP2qK57#pC_Fvd?W=z z=0xEE*UQYgHhXHoK8;CDZC`AFI>sF+g0>!DR|AoYh(K7*z^VX2b+3CCf=etEj9iz4 z9*WrY#ZwTOF@mWo4hrSwPpf*W@t8<^VP*YO2l|SZO zI03*5mOvJ$5donNva(F^@$mtC$E7O4FvK~r15$Z^Z&igyTb38DYe3g7I=cI(Ykz+w zvtCyq%(Dqp+3YFHzTV!y{i=^TThl=6fJw7Ie$W8AUNh zK!lPBWMKq?5ji~(Tk|!ZGb|tiJf0^-9?;Am^1IrKFaYKGbDV3VU5usOr4+p*3F9_mv|tD{{Cxwo>{F+(ZCTI|Mi~w<|#pYs{8VIk+~}j zDr%L`u+gEYL!jB@kxFdV2hL6MQ%jbv{!$!{-7GiUhW z73g9oe`=mZhgy|qk)-iCzog^&R`dXOy*bgWs}18-er+A#R6dR+rwJ9jg6 zM`VcPNMb@T&UpBo_x;pT-Y1YX&H4VtwriT}V-tOzX?*J%?!`OzA2G53)xxkzXDNy+-w$~)PqXeAr=u*L(&?(d5 zL%8`{@*Yom$isD?!gP~6sSn9lE1}K{1u(NcOk8u60|9&tEPgSM|HF@`6LEWs08HaM zbvt*MY5x$aceNm?@7crwnh&t^z!Cs<1|M|*X4}xv)T{&!I7W|vRRXJPTw=MxbxWT~ z_lk7;VlbT%I)o0cJ~A%zOaTJEsH3a9J=&bMBK3>nmbR-$KJo3AZPPZlJ)L&VL;TT^ zLnrOaWAJ33j_#j=IW(BD@W55PP;Z5GkBrGY&YJemwv=RipWZdau`%|$3+U6`$zdN1 zz;>x%-qRI3wZ(+Zjh;O^5E{vXj$OleG~mVAvX+U!j7HL%DJX zTL0pC1U?l?Ac^3X)QAh$H47DjRQej#YbOk2kP4vqti4p=Xddoe`^1XJvVFG?N zs2_+FFr+}6PqOg)_iyYMFVYypk%7cPVxtis9WA;z|Mp6_zbQtZm@ zPn00ZT()oDn!E)1vPAxb!;v0A3fE)u7OOJ2xz!|FDng|I#uO4-s*h*YsSE?|Ioh}lgGHpZZRei*I47YT+7z2tbas2Hq^53@WC6L51&vh8I1e#%gl9R%zMm?Z$RA4~4%Pc;%#qSK?Fig7)V=i5s!fVfuv%oxaRoU!gB&G%k6T`EXV-4n2clT+6cr|o9nMhe|;gBTCkO4!;| zZqJ}v3~3!osZ9iO)6)L@SJ4hhAnw^+3K4U2SY7If6NxXAb|p-h5$Q#GAHnvq`4BAO2Oxpm1IyPldu9*t5SX~fo+GL7 zI~HF$?>lgPz#Q{B9YY2IN;lU@4ZtKjkdMN}ZL)tr`QZtKK4Ak>_wJ#G%jedc_-u2q zeXcJUHAbinpfObA1@sFj%T|o z>*(sBpkQ?V8N?X*{tCksU^TDnIb6^dwx{<<0d^K!m#|!d9un@}0 zDN``e&i*9>0azaR!&J;w9OweFtU{Hzt{hAfrYXTMCR*< z(G;M(oA3GWWzhwRWx>D|SoG+<5Aoi>Q|9~Sd*!rouu$Cv+YU;$_}d;7D%+*s5+*9R zfLam=WC}CHp#hwk%x%*?X50e@tI$9VVEBdc00(#F=4otfES`gOG1p|ie82G-#7D$o zlg@g!vlH60w!i-kWE#VXCVPb0Q2c;yk~Q{`;QEk_;nof8d9u*pvg~97qz5E_UotX= zrY|-qfrf#HhX(=r05GYq4G;LbBgj4{F+kWZ*?^TiKR*g_KA=QUF*Aps7)!1EHNX-9VMjn6!0Pt7aThd} zifrvzBn8?>e>}bMEVNd*MzLD@h2GID83-VpnSB8J5gnk1x`Ts5qh@AME}Pk3{-t(J z?*Z?G05rH!!p(08h|m6n9O&U_tg0e#qYu^U_4X%JpvRK%`#b?}m|nj$)v&3$I(%gr z!{V0OJ~n8UqUHuo#H7 z!g4u_YK~~pl#zw01zjXibpbsGlmMhwq``DDA5PsU8MY(XAi)s}N*I&jT!#aChE$AX z_^j_=3Vc?*AAk^UYLLGmwYhQyV;mg(9&~F%L)!?YLX)BX7a*%53>d$(o;H^t0csl4E}) zkOO(H?yjymHqnd!&>2Pwo^>1^5x;9tUs%_40nRfed(|*}35P9-9_-?)gDbnZxnHh1oW6AsokZ=nEf$`BFABsr! z90{JEc%-Z{-NlD(8DIrkJXy7 z3>%0hSZ$x&N=U7I_2f-6_Vq#m_jY1sN*%SD!HD7D;Hds(ae)?fZ_{#WT>A+L0f{Jp ziQ@K&Uj1o@P3a9X!8AV~A5fCTfh!LZ0Dy%4Cw)&$dq(Z>$nz|2e%1Q&qVmIwAGfRx4e!l_F0BOec!J4#9ld7;D)!mc9SHxeK)Q_xP60szX-CJ(l_z^EE5n{^l4m+A-Hw?9IX3cs;=eAiX^yw16yDrM!@mEsaWeKZXkJN07MML15Z}Io$v-=!2k`=^A$lq zHO5y2aPMj)@y$s`LRFyAKv|2Sjg16oLQW-&`@H{ToO=)Ql4ITPI^Q=xY-<0}(Y-$R z&A`1Gghi0WaED}GD7>-w6SQ1w-RpXPlQB)u5ClM7r|-B7?>g*=Aaj|22Siz&ML>>9 zB67+5y}bO0llOm6C(sJ^^cg2=T58XhkYmDhIbo;6anCwBovpGHt}+`La#|!Vb2R~8 z0I)<$SMcfKzkXp?EONSIn-PA+$r;xSA2<#u#S*!2cewxs1xiK_S;@T}cy};>rM^(Z zWyuSpb*_!0a1smCCI487T#q-zpx-5O-pfEh;2vn%>RMdf)-CP>YD8Sck7WqB!ez@r zIbQ`=o;?G*y^#>dpa(*dCA9$eeXxI5@J~QHWZ$3c6d)4-CP5}p+MlY&%3mzn_?___ zw1~&+oB(*CnIP0k!sqx#-Qxrm)r;4LX3?{yga01+Y^$aPbI;{)Ui?v1JoG$wC@AP1 zaDKZo;qFn5e1>g{=oPc@DEl6cEf2%TubjfC_^~Knrw=SJWIaFg>H( zS6@Z?6}iuwByRquVE{0B9nT3knv*+vVuT=nLD=PF;bom=>uJ~0)WuY?(WmW?FyAbog{`hZ(!%GcZa8_l>DDme!}Xi!u`KT105)C)dhMMTdwds0d7T4U;#04*v1E-_Jd!=v80dxHjW`K ziOY!#g@jjm@p3z}~s*%{@&Za)5k6p2P$V%=V}YZfX()rU}f4`j@@4XZJ6A=Px~~f?qgm1fAM@a z4$t3qD}hhRDM7>)7@)72Ztn&NLFz*RsBR!iOvqX_7>YG`!m$TI zMGXd-)(}<3_|1M=J{lx-b@h%t8(*9J(|dlY?3^56baEkJ0KB>0uj(rqPJjf`V8acv zN=nKVH5FWq5uu@5^B2UE#foXOvpm_^IfdHLzAp@*Lb8$(p1OVe`W2p%GE3wzgWc8H zIZJJbh)J{N3t)8=o)HrNv&GB$cKswLc$3M>GCUm6^8A$m3OW|pZ5LgBY!4v1Sm2D_ z#!?d-c(VwVo^%YtE{qnywyg7H4FePhVhbSLr%xC#(Hn$Jf#aspVu<5|&q{RZ`sowc z6C3~pZm@IX+NI41RInHQ3CI*QG)OY&=wL!Yz;E7gcxWVl-`5B7XPdsYQ8gvnD=UW) z0O!4uiEjnLR@lREqtcO$)ylI*r1AWti8efnlwM@WJp_U6^*2~#lKI1niJ)Cm<$&!l zo`x$|H+po(ELc{IR$6cD;*FC)G-_q#8Autg?5=(RGXUW0#<@aj(AfcD`wIS9T}$K0 zJU(aY+kVz@eI8oaIF^U3?|qMH0VN;h{3s#}`{g$vFjQV%-rBbG87x-6{d%$wDQ~im z{6N?Q%G~7PlJ9lxSTHr0rfQ?r(hi9I*1^M9kRtkJGZAugvCC%PYkz}D6aoynm3l%L zxW42%#5`M>&U%cBS#tosU(<{;wkaa(6Q zH$+&^-DsCOg9i4?{lHl=j0_qX-v&0Gk5MmofkchQ6b0;3*kSYki@3KA%c^brM8U#F zMMVU`1{FkFT18Y)Kthogk?!tL1XKi6q)ViwL`tMoO1h-GTe@q0>v`w<_MVw{_8iCT z*~k9t@p!nox$m{sbzSHAt5Zl4#ec?RbBONp!ij7K+s&cWO%ae*#EGbJg!mlH+ad(q zi0*?xW#VPm#c>W?M``b`SzJf2?X7ujOP4@H0ZHh6NnLlfG=#yRE?R{&>_PPvF$E$)k*ebv~E;0|INYt#;b=nUml&EdU)e(>FS~ zXRSdBAG=rh=^xtNxymJw`OYkQD!Ho1h==_wC#q^cnl>JuW@1a`Oi(D0(0zzz;m0#) zH&TM|cX9Wd?)+A(x2nGfN`H~kL$oBQ>JJ`1{0rNEs(Jwr77NM)f5U+Nblk;jSt=~={5RD&+qkfkN`oMve264a(qkobJEpb`dYgC8_j?(V`@i%u>t1F3#pi@4(&h3_to|kP6f2Oih0&>`SSBUPCuUbFlaKvbN_x<$9S2G1t(5RO4gF> zrRHw^A^;I3I40`s-TwfjY&K|{Q|x(R)FAKeeWKj~D)-(!lZ0xd8#s%EO>Hctq-n43 zCaKr3Q-3r$JbXQ(o-{J%Wu&N_0o1`ZC!6l2oo$lDY;Ow$p`~?Gvb+o$Kf4ldtE;RoB zJ$`MlZlK&Q+bAU^o`dV18wGd*No8p7i+V%xye6b(8 z3;`1G3ROs}wD0F%F3(db>WL`1Iy6>3%ArgA9#at4nJWzbkZ01fvmcgbV=MNcErpG5 zYr9J3a~wGYscAT_1qIA|O{%^=3Idb8A(Y%RJpAL!DQB&^ZwKR>5(ZFe5K6s*j7ze~ zJ!sM^{2`uX*QgF~3&}kJT#0bP0m*mWd;A)F4tvMqWs}RW&0*(epv&8&4QRcG1EA>n zvgxz&o)`ZsS6_+M=p0q!+8i5%77RWYznhQf4NqI~<)=j4`idR|zPY1n% zR%gmxal8zj*>7XM?F|!KE&q0^!K(x0z?m`8m&aUZcDis^Y=aI|eR5y4cEOMg!EdZe z=4Efe&^w^>yDBz4FUhU}H2+av?meR7;F_CjpR{83Z%mCO*Ww#k;dWbERyxanCPYeE z*^X~|E6QA)Dv#GzDMmUI_t-8@&jxx%Z zg=AYAn;WZE#9G21C7)y(#ju<=uR*_`hUXrrRv4b_*p0WKtRf-sC*t10761B3nRLcI zZMA%1m*3vo4)liEj7U_{6ZTI5g{^&5ov7$`q`W70-w6K`5f*T-;pPY8Vn9H(8+oFPr*t2SHs z+g3eOvYKa(0~8_wVUf%Me!U6@Co4hRr&ln)~>ZA#d*z= zqz;*cD~FtfZhQ8Q$g#>^k$lu4&^`QEBAK>>05Sm*@1^7L-AYD2tJBuVsI$2C`OBrB=)WX^uc$apdzm~NLpN~R&U z30KzP^Y*MR#%i}}VLKEL=BfNp9X(SY=mQJHAcM5zjMyE~Ctz|Rj?}1F2~^?Kdomt- z5EpuOgtC5VxL_9)VT4gj06pjYSflXEA&h?NxqrMlaFv4pQB9Q%vZ(p zv^n(aUb04|=3i(`mh}@oa>;0||3Z7aVw`mLQIMG1yP<%GL#`%P)tRqeyDs*ixVSh8 zp>D-Xjq%cgmE#mkh7 zx}TqPYLZNOa!THUn~9M@C65s#Q$cI%=tkB$dG~-O_P~s;b1fGZ4Y?q#&9JdT@8~7~H*2j(ojVH-UE>Eatx&Dry-~ zUD2W7K(g1efv%yZUtc=xUpN3ra;9x=`z9+2Plk37TK?n=Z5R2FMTN@ZVxAJ++hn#v zJR~pyxHS6Yt`~fT_ft=wE;>#|!%-9A9i&%{hOeO5KE=dz0#pS+TAq;0U2AZ7!X7$+ zlI~BeatK5$cGDyM^hs@G|E6Z+BRR&av(0gBnX+D^vbe17J=%b%Tk|@EaZ#E=np1z- zXS=&!-DHRWK4=MFy`|BY>S_5Xsqh93I$J$Gnz2U1lk9UmyM-6NH1xH#@z*7ZM(tLh z4x%w%{z!W!;u9Xl{jedkCp2jxmGMr+gXmBFV^ep$+(o18A}dn9eg8h#+}P66a=>Wk zG~YX?8+?9*svrK>Hp}tTIR5|zRiszCiYZ*RSUa1lDF)+YAff`;jDBak&U;A&o0*uL zWxaT@i!bcyCv`nVMbD<%+FA&CPB;~N9G~j=RD|xU8ixPng`nhFG5U)ac}m96Z*rek z5-2YX1En%sxwf5A`AS$_R1OKaw}~5L-5`v=YIc!+QhTEP=ZixqKe^sMf71B!!l$6(d%X8@V zmqmPB37nr-%;CE`Q`y8O8Q-9{s%x8cD)LifCY+_{va!>29saIwJY#NTbe3nu%u{{W zu3f?xBMxkGQq3HwP6Ur#tl~J?-8j+4uJcH+X=Q0?+bxX4{DBWU-L`zI>Y(z`Z-D< zJ9fMHCtio%vsj2Nrc4WT{ey`Lt|9@|@(40KIym^PeR8BFU6Xlw+C?R}HBH+Eg)>Y~ z6{g>|GDvkqN5w>l+$~6by6dE^R3EpBV8IGmfe>tm=;W|IPJ`ToO0lpj|D_FrXIqN{ z)6&xD1vZwJwqakw1;GwU?HUz&tFL%p7Uj`m_#5}}tKsun`^H~g=G3|GHO7@>+CR(! z@g0|8XD>)>2_=J58zL|^g(^TWZ)9v7jU533DaEmD%ZXoUa;b!5D}%FfFE#5+oF3GT zixc(k0V>t%i=FmwjIwxww_&N0hnJUO?Ak{KB%!HdP7#T}IsugW=r9s(g1zjKG7 zs;bIvAz#AC+`4(73nGmKrNupy9w(F@^I*qNuapq%qdV}@Irjw2XWUISRjGAwyY2s% z+fB_tqZn-_A%HTd;=bKR+1hOlOmm zYZ1CkMo!l=8^?D4;t@#LXQv~{zkL0Adajit+HT_<6;%KZ;F=8cOYpm>Xl2oHJy|+1 z$o!}f@bCrH7fqK#S{9lX;PIr^)g83s@IF`m@QdEEsF@8v_$u`kzpy6;1{)PK<~ZY_ zz>(Wnzlf^vc9iImk1H5 zfH>^HNA{yz^xVH>UX)5HqXE`JCcZm5|?kt`jq**5wOb6HwZ$bq7s=1J=Wlto(hgV%Fo$>rZgb0T4$+9a{EN==Ku!BZ>6MeCv(H5v~LIdnh+6KOgK> z(VTPY4ePb+IUP}wE8)iVy(UY6nJ7cxtS!FmYgHu{#vLRUds{I zF^Vl;tNEC3labx>vZG9m3HC};Ych!VQ@gbxUN*|I`tL2K0>KN|uA%aw{N4eP`7r^L zz4D2T&B8~SQz%(K-f7TUdRjJ=X1I7U-(bb1;gavvAayd_KU&Bb8ftDa9fB4k+BWm@ zEvAy5h}-zXGM?%l)SNH?j|L1yJ&d=GQlE`!1!c;+_SL~#a&(xK{`PIc?)Yc*(&60^ zzWG;zGWE{Q+4PVKuiKa#8(V0MO*$UW{@r^{qqxrq!%R6DeVKkjwV8C)n-6*k zc`-65dW6U%GsX3RFLf47Q^XMBg{$lzAnZqoTqOuZYJoH&n#RykPssaoxVCpOw?GB( zQ_~?wx3Df`Jl8>=S%m0_A4m;ATK9Q}Il)e9u0ZQ9Cd8kqX=>gD>O9gp-&15*VM|x@ zNlqFW5`n1#Px>C^m~rPhP$K>^22`cmMFC$DMCGl-9(JJ1S$v5)d*0W=Vjs&?4+XR?#?%7!7+>4Z#m3yb&MIhhB zmOyuKfgg8NQzJ5@5FF;D7o9WQ5CEF(*fx2S6sPG?NHQ}^L_021p?EX1OT9EZR8^cU zC46!S#5l?Lvr&=#h)WN6u;;{C`Ft z|FJ}@cH~&QOXT~D;yR+_DJ*IUK#0iEM~eWVddhs_;aHl1 zTXA6j&b|Hh)iEsd?hpA{(%|1%-zT8_gRn0IC> z$K_Dc0h>eDii_%bT}9G9z)gN59I^70JYpGO(Pyh1Cty-sKjrZF!8-bGyZF`g&v z_&+h$snLi=dhZeBFc8G80{HoY#bieb?w3|=g0_WFY(oxe?@2FeA|^vx)2`^d3g;ED zU{2#Hf78br*=z^R;DaUfWl-O9ZaWOc0wh;#>+bD+rBGlO7j%TsIWEy6i&;vl(_1e7 z;dHoC8z(OH)GA%ul0oeghYe#6N@gXE&1XUh%2|6sc>Hb8atBfZ0ZZ~=h1&9a7bIN= zV%K1g3*@W!eC>Ju)P+|^?otAq2mrk6(DLOKz>dTIi(BEASL<2J0tXyzC*VVVUQDB< zcRP>-R{&Sbuqmc#bsmP^6nI!~8+?>! z(r|ob(LM-oj>YsVs$BL_+EeZ>`&o`CQS0gT&vgFy>x7XnF@OCTWHNosx&&>y-R-fV z;U*Yi^gKKF?mf-T?LtofrrI+5-v;*}V05TIu0Emb%PYe{JE8%COU5gCs4AxJ8;kJ8 zf}S%a{(_5_P@gw1G+E>+eR=6q?$_TOZ8jqHhK3~=lFw9w%8LavzeYEPrzxNV3Zs)v z{xSDX)t6qo>-k}eQ2xK&J!kB=CUxO2tHc?xH0Q3JptMc^3)UI=596a`y$lFq411`n z*YR?|?$+u0x{wX|&y^l_C8xUCCRdMSe_*a>Z#%%K(_POT&9>b4ilD>KopTWhyQkXj zFNx0OIYo>(+dyjETAIc=LSkJ#xuxTKUQxWh{MM;H31lXEHY7dpMtB8cY1}kR4@sDH z?wV%Ic`HPVcF)>WLo(%tb+O}>caV4(A5hThKUl z*e=xOPquwv=y`^?6>zIgh(MW<&z6mQ6wZftYE9RvgN_5aG`hbtVVgV7p-18bA>0Ey zyKmNGoFw2Lqu^j*P>5#7tDBVd?}7vGn)5i$2`GCnib3q*1nr+t4iG%oIAR*eH4+Tu zGqi4jy#pmhGTxFIc5C10(03=~_Zj!}o&(|N3I!qzC+L(1x17uAnkWkW*36mTU7^P2a=CWjmDi zziZX#ck^kUK79tVju<>8+`dhy?0`}{OHY5WkU6eotJRdTFY2xzq$@SKx|D?M@y3nQ zc%+Tzbx2Aif^Q}_QQMr^*aZ_dXPO$2SUjSA*fXzLFqb%+ip9y)<#ohoJs*9Z)0vX1 zDlQCiY44Dcl&~m<_=q%{$q`kYxJw~VN*I~0z{v>VhTSOkwZ(7b+nUpOh5&|>^ zHaY7|OiTnO0(~PcF7VVRR$-x6t&dxBWvfbP4t5wsg;SnFF?fZ$exa%75Grzlj5Ew z8inR*j}v@dIk~3FkKte0yXooSq1Lgo?}e-K?eO1!Uz;2K4hU^JL%AJ7M%=b?aCXzv z3mW*7Zz|M|ongI*uXi^gVK=Q~^?wglG>wr?8Chy>w6_rZI4u9a;`Q66aJ&OnyyEy* z5Bpj`Peid4-1V^GA`A9zs5PHF6rpujo+W(Dp@Wal_FJ;ltHi|go_S}(#MXrIIj{f$ z`Hxxl_%X6Ysx;4vBG`h3i>o@l!`W%3c4aS*17W|%d;mE)Bm>{Z26cslMZMlvMGoO- z>zyM7koHvY7L<5SitsxaU>g;(FD@<~*sU?6>z#w>z5Y9kKe-hTyENDX594d=6=}_^ z`CdB2SMHu0qU6~GEDKtV>b*_2Chxp#imTmjNn^1U-I?EZ2@)-dzi?o z-`ma3--BEX@jX>g*qi8WTCEu7bUsVz6Z~kOodF3sgqyjF%RJmXgoN_*E$=?+o<1V| zEv1h>fM54GI!J#I);4GDeYG1gc?BosXz zlr>!<@Q{i#`70L~C+P=z$17ee79StXJnRB_G2#)jE<1VowGtBTdtoHVf-*cH6c(gRZB`n~(R>k537?v?HbmO7jsn>ljG&2MV3GEg_ z2TwyBRW6%kGVogP!A05Ph6Wd+>LuLzri{PNoZjoTX#l@i<9bdgGNF*}vvdEh?H>zt zCz{hW>7HHx33#Mxb;Y>1|3YggT^r*)NPzJZWWx{U-f=KS`nys>a{rPG`hkqBrKGj&HDbkRQj5+Y6$~Gp#M5&> zVNLW!hHLx)s78DYoV5%p8ROFY)gM3Rgxzvmm{@`q`q!DSZes7>duLg!-+H$C4FOpX zPY>MZ*QU~Pk0`tmkBaF-XXy&%}th#HlJE4Ev=m<)AgSETWF<-qY`%Z<<{n57t+ww+5Kl<<80yK2O@o8rze?)r-alkg z=(ZI6Vb!iXp*(;h4gp9(oQd)*4&$-(JR2Iwwp*=I5(u8#1u7i{o>w5pXFh1dE z&tv}CVf@>4k;KS_idBnKcs^GDtS*c+obX!h^J9+B&zyW(@|T~nET&xMa7QTuVRFp2 z3nnQ`4tH$Z&1LS_ZnHZ{s2m+6*G0TzpcubDu3MqPSnGPXf;0wqPX-OMya$H=J-1*l1i zU~>mU-g{2-kJGRAiZ@UWV(`QAD~I87gf&o82T>*|tcdhm(z_gh1dnyw`4>w~RQ6KQ z5TR66?awaxOGXLQs?gD`cI#`$UqP$&Z8tk;Xft9W!)!MHL08_-+#8Gj24xPYMetHw7LvQoiCJf1eCp#*_};yJd!I0m zXCK>9tzf$;+a+E7l~!6x>MDFowhMpzKId=d+t3sxI-Uys@Xr zTJjZkSY!A=NOCSc_=S3>S=iA4!3D*H$FY~6hm>t2KNR@waVD-seCnt)-d=k^3!vE;rbbxBQSt7RcSGmG>NV0*xD zPVw`1eBnbP0VFgeFqS@jKZGcVyc;0tCF3`9wK;dI(ao_PhPmKop(FE0NHtHf&$Y6N z-0hS`Y~yQGy(FRof{VsMvc%ux!tJ;hz^b_HXZxtov3k$)?7-zJamD#Xcyo}y^Xj!{ zF%m}7Ma5vEjLOM@tfjE9SStFSHpv1V-%XFv1|x(X;amcrhwSk?@;1jYEMuU|C+dSA zB?-zz#Vi}2m%kC~y&W*C@ceH#7l`Vvo_&$M`0ICB86HSqod=&EV}ueqOJo~=w97If zL6X=VvwQ6N3Yj|)O8E?-!!=3z)CgqvdxRjaxO&alhv^snk`!Szn-X;&1bk}RAV^f% zhl!(IKtLdN);}Q-We9SYFw;UPN=7P4QBKtFqK|Gi4pr2J3_U%nk9^<0{Q-L-X6K+3 zl!=$x4v8|#$({T6U-yo_{_HucdfELvMLDM%W7Edy^bozN-V`q-a)Fuo;zI_*M~=v7 zMdMTYyP^$@K z21Fvdh7aw@^dk8+Z(rAtM5ad0Mr+yrpLFAHvB&X-(^o3|rz;80{%hZ%z+lC}MFc`f|)>rm@HRC~GWZc8_ z-6R0`Fz&<^)!dek4LU8*@rxbjz^!Y%FAMaDNIOLWCS+!{(CsgrxTr-2ab`JC3 zFt4o$qy%Ee;JLdW-|*u0Q?kP_6`^pY`RjN^;O_$HG~L#15Jsb>;uYE3*AUQNI^~H93yz6XT^g$SisizXk}o}`1m~;nZpP<5kgYq zkrN&PaTlQN2TY298)w80VBi|5nEY`h_Q0@04xKd#2!bEQ#et2l?%e>OB|8q5a&$gM-NZPz`xLb4Q}OyWLtv=V7uZ zRD-)-y@KqsxTat}79G{uvuEE1#1YJ~>wvI+{PVJ7$89{Cn;$`p{QO2-|9FcjMEo$2 z^-`o;-WldVAc)qVj{-QfAtHt;G`3ed;L^qhu$$AeHbhP-Jv|yWwENIBlTuJ{)UvRk z90mqY#GQQq&gVCIAy+xfFn7>AG%_-}Zq>4+JUKJG$Zj`+|h)P$Ln?cq8;<0NMo z`Dk=6T@t!7{V|CMD18@E5{u*~9B9GvjhRTnPLA2`M>xwz2tm<%BEth%mGfW@KUl^7MXcYJ8?6%hV4k zB~SGA2^>f-fVHr=2x714n?o*AaTvTvEcs8hk%)NH@js?`pZ1;PJ6Je-dfwTqeo0L) z>gY)Py(}Q2a73oK&2mx-E~$I>?%j`YQik1yv}j zi)II0#>1V)$@9?Tpjl%U(966iC@3gGq`n)T*SJ@^jHjpJV(A|2eIDU`Mm9;2NTtU$8(4a{dR2GKjqW7T zFu4qn!nBHR3Nj~d8}2&xmSis)UV}%`_3Az2A65$lrGs82mKKyM5?M9mfh8S#_5cOT zeyTolpJarJijRjY@1u=fdtWXHdJR{((LnZn=(V`FoJ^M&v$44qntNiqHUuxH!kezz zj2}ZYCzB|@-FF9Qm5~ z#{fomHA77|K!Q-51GqUrNhyJZhp=y#NoQ>656ehMv>6cJ3)|x!(>?6k1~aOAiiU4UvxbnUUrcR=0)3OaOPYdB;V_Zd=I^x9UGNPaPZxo`_Yxm^2P}WN)C@pYEbx4j1+05ScnOkmschI6{&uPK z+t?}E+_Ld(BEG~Pcb6>g1q%x++0QasJfcyoB}|U335;K9M+CSyfYqzhp|FGRhu~vz zORhh&E-WeW_Vt~8l|&V=-TvDk-``)YqsH5PQV>+&nlia|xUmxp60%^E;0R&u%MgJB z5+JM|pu#}t;c*|bTSwNx%tQzSd3P7o12T- z+!0Y5Ni}mvL<}#pbxqO4XM%-a2;!Bu{A(1Bn_XT=nmf(OSvB?3zOPuU;5%8zj7=^r zHbM^%k5kPI4CIuulqduCA^kes9_P}v> zA;^O8p|WO}6LRl8crVrS_8EjYAoU8 z`h&CwLnn=tR5v^pWvX9F_3ZL_U3WNVtlZ``kdBAX`y+kIR0pkdP1n3FkdMJzdo^ zIDvY9zLDQ^q!5(pWtCj01l&?JU;AAO>hpLA#sh}u+ny(clIuY}-^N%Z5Tt}h;q2avis!4s6%KeFRu27+?tS2heN6x1P?3`9h(^n4OgDk z-8U4kQp`7|2oZO~@qlz4l!!1?{PwvdD;d`S>ECuPSy>p3xQ(U`0Deb);kWD~_R2r| zP+_b4^F>R=F-#1o`=??^#WjQwX4x(8M#?OXJp5Wg#}2QHNTGyn$$0qp!Fyu&jLpp0 z=RWd%yL+1Z!mgrqmiglipN|H*;e^9{gC$eOlq_l-}Mj)`#K(8&L0$ zpF+6Lii(O;(+%ZPnj+ zYkHg*wSkz79=WgR9#0pVmM;F( zKY!HJH1!F8hQi^5Tj`%s#+7-qy8I^A6q4Yq1xf~l37~eKnk!B|>oxJ~rngSaU~VxV zpj6kkl-Fg*dMD$}_S;Cw+dQkF&}KKOP1&pNgWrMDL-L{zC_nz{UFponL0mu*T-RK5 zV3YLLJ_ghV82un-VlqR)Pv7}3K*DiOG6R7kSMkyUimNUMxc?K5sG4GsC-8-}#ow}a z#6SKIj!TLVu;GPW1%auDsHo;|u5$Bo*QRQ6;edN1_s`#I2Y~XV-nR=WOna5OY-V3M zyk5)c+RkavegmnPpbg(x?#1Cl84gVmJ>WCa{)Ufh0S4+=%l*h571pr_Ik=X zW$UsWG&_TcHn??nGpuS;&e+W8G3*R1t7Jn@*Qu@fE&FK4Ueg1OI~QnUSp7L z-@AU6Qgb7708_aH4{mF{L|1~3ck4c|eGCTfj0MY+UIzoc8)&nwG6Ho?!bSP}h#@_b@AsYGLPAi%!{ZJhz znRpQ~ecK*`i6J~6zWW3b&x$~pr@bZ8OZMeE3SQL1mLagF#fq4&H&k4tQKBOC#U4lw zdP32}=_T&`9=j3O7U6s+4bz7gksF9Svcwx))ShMRkl#!MPwvU|76@9n= zwCEn;+Az^cCZ}sRNKVZi!bFHj{jZaB&wS3E4f;|@GP%r09@ufgb~1~XP4jPizyLz5 z?}hNagZ#2SdLV{Iz$W-b;kMUuNBYW{cSyH39C7-#WObBLv0~Ysl7)pc+`g@meKS(q z^O38I(kZ?*+Ihj)cxyxOjtL5McW&I+g5AE?J>1#I#B5u)p3zRAAM~eBzZdnnc&K}_ zWT0FXsT!i1+1m{|%aBuZNAwo$vpfV?zw zDZJnN^BR&OVFZI4fQ3><+wqc8Q&We`XDwBqJ9=6EhTiy@vJV#`8eGK!GoN$@2goGJ zuw*ADDGs7h+-7olj_+<>Sc$L4*wmbn=AY(iaKd&uxjO|YcR!`y9a9}#^xdCBRW^yFCHK5? zD6PC@G9vAx#X@)f_~g=+VmxuYHN&7NE?;7HSkfW@hbxi1Z5**9a6o*+K9HL&W~Q%9 zeQCAfX>X)9Y)2%~;$bd{AxuGyn-#$U^^a05_%)q{ObUA@*))miAFi$~SwqWvDCGzl zj`*>1<{+;78E>coFm5?{`Ld^R*S$}=L*i@GZ4WWny!87#^=ya1gZP|+HSW|U^YUBr z8A~s=yC0?eHi0=`w@{EQna|!DtPCjaQ7bVGX{xDtYctu@6Vq^s`2XYMaB1!kOn4*;meW7qNHT9UL3ce% zjPPBS`RK0Y*ql??+Ju;VIn;3^sEOau@Oy$L`l5gLWJk8Sdix6zKtKKR3tj>V)t?9M z`marFdzhqLP~`6SD8uTMuEV42CMK+oYw5W^^!HgCZc04(EOYS8nKQqBea-ApD|t># zwYg3&PTE4YUNcKMH9cLGzLr(Ap}xq1oFs|G<1hGHN?s^sAu)bCZU7?Mu35||)VI0g z#fS?!5HHKrx2VF&GSIf`1GCD0l1q2*0{QsSl6q=+C3H8bq4o{Z?um)gmhJ7ifK)V`(QY1qq(a(vB%3!j(_t zBRLBV*G{m(F@^Vul^)~w%t7!TZSuC(w=iTzU|x+_5;@p+N|M0K1b2b_Md;9h3aXneVts+LWXcb9*rfU*U
    )A2+SpmUQ{V5y{_P7=85^*Xr3b(h%N^rnH`TA_KNEAlc84z~U-pL*{GK z@7{(!m4%h{Q={!hdoK0sBOn->_QB-I6{pZLPm4apCap9KH^Y0bU!vmsKa)OnZI_1K-Rt^}IHQ)AP*obte`_b9CACp_GPXHoxcFrA42N(xWAM^$}xqK;?AHKQkS8(;H z*>e&{m3Cbj&7M!{W+Q)|L8Yx)IHkJ$xsP5f=!;zgxtUgzYwqg)scAPlt#fyLeIEwq z@7uR8B`xhF=fc6#(w4p}Hj8wz0ioy#GGjD7t6^U*o(V{yK(?kLH*MiNwG#G8IgB4# z9X<#-26D8tnI?y|!jju;ft2iO&+%c?bS4_`D5-(5duwtKqXQb$>fr8wY#27SmXhN2 zD3j&wSd-zW`_~i|?K~|UTSJc?9Skg|CMgP`Asicpg`)|JWaeEZ&A(ju)@;DJW2*A!m@iylU_a5w?lWVa~by zXq7%N`*qmEH1z7_o4Pk_Q_~B*io@2=#e<&~e-I%=vl!R_qtb=H{gK>kbq_-EWMF%* zC~~}v{#Fu-g(wdGi#+nPme@}L@4-BxrlIp1t4;S|;*Y|@tJPNn75u&DDmqS}_RW#b zI>awH>8sX>#}*>*TVuIPFAD9_WxEFx5lQzvpq|K&D}|kPq%+kp&)2>T{8?oGQ>-5r z4i5X7%5buN;B5> zZsxTQhSm}UGe@#=C(-0X-?QYf(QfCHa&o@hP=V5{L!eWcQ8DcU%7^>)CDh2j>zrNe zD|{v0p6wVW5jenXY!@o9dQ5ZZ5Uz(Ye69p82&M^nA@_p$jsP#y5is2q4KDEBG2TGf zrt?BrJ_8 z46zVNr<^ikdnF=*!h>_0L0w=cw4IGf37^E)7TyEc(-13L2q)$Rxw?R_>S@*Tz^Lc~ zSrg>fDb&lqNwzj@$6PeN_J7n%mHM&dXHF|G?KJa~ic0(bgc=iHRN5?wZ5X^3OkhSR z7D+bp1Td-9`=fVD)7yfAN#m(*)yl74v_&lX#d?X({n7}i0sjV;?79sS8aEd5w`ATG z(bU9#SsobH6fPBa=gyt(%&yJf<{Qw(pgR18a^$vGFT0;t=+L<0^WzQiDSAcZkx@~z zOVer(6XeC#$w!|&eHxkC2~BQI@JJT+(YmttL}<-w0^fxTi*3e^n|%_r{IAbImo-nl z)%enc=ty0QGpmx4B75Hp7(pF3vXpe?6ciK^C+}V#{%q>ds8Ul+zH_Q?!w)Fz#`A|s1Uf@; z9f<^>N?Q;K$UK^h;xcmz4YQ0ub@1I{Qn(~3tZ#VuBc=t6{2`T)fG3{rX3&16oxAv} zE8@I4c%K+DlOs4&EJS`%+jrO0BIYc$Em;jICpp;Lu-g8$Yqc2u1%VO?9QvpHF1vv& zai*DdoKv+geiL+O%pwHJ;G+~JUJb$49(1!DkmA`jMsQWir)gAtdv){Hz&!?{=;fyT zJn}aY5u7S}PZZ|3xG(qDbEb{`@e$l?C3ONXk2g>+`rN>7*x=jJ#Z)tTLiEnPe!V+8 z!3x;!L(w1tt0+^OMWk#OsgWl}8At5OHu3S*=Vk48KZrE9E7evT(@p#3 z)5i*zUU_TzlvueN7JHpyx$r9E1aEz!rb>c-0u`s#l1w%w zxpaA#>*MI^?~lt@Sqj*(duwZ^e!&;!+cVlh`(I#MJ6B(cR;$s}4+qFuUMky@U9l@9 zzFS&VLbjP+b;?JIG`+TGxvS)d8GOHw28Yd4&dnw=G6nFf9Ve!&nP-dR+j*kD2lyn} z{dKIqar;1DUu8&kPvG?3b9Z^v^z>dp`c?G$h;5wtXf{rOiAo5!V%IAKio3e}cr6R2 z=ZwgZdwE_wuq;NOrzX#o3!w6FdkX*CjtFb+@z;YHHm7EUShVZT&Uc>@a_=w558C7} zaNFlFqRyLP=$x8r`tHNJ`9U-(o$F*N97Z9Hrl)xt&?n5M3tL#{`qwt`F#6)%)1UR%&8gAGs2Be!6;?Vt?Rt#6 zH~!N^xY3tP`m1Lnf0QeIvpRU2K4x=~R}}?JS%a=td+g6WbUY7g4F0z7rQ>{oNh!aZ zqWNsPTg%_1b#J;0^e$g-HRSr%6-sl- zLh9%{Wa=5ZO@U%TnI_%c@3t@vA=Ls#s@?79HaO+7Z1!P=hQJSA8#6TIvLbin(g)3s zK)M-@NlztPe|}2=&`^g{zkX78AN*s#S!s3SqkwXBCxC$4*c*OQnUyxnmyEAzgSK4m*h@Qi-&;9qpKa!jvA@OM< zBqp3o`%iFx_PNQS(+r5RtK9rMJi5BPfCw2! zFK%jvdJR;Mao&d*e4{zT1(uW=)cGifUVr!J0qnrCb!Aaix0|OkStyjtb8c*K^5|6x>eZL~L$as4e-?a7iuTQ94WF8xIeWD~ zco5SNS!I)y`~G$qJyE06?i8f9+mExAWXqZzN??Z3PeY=K4+)g;eV7Qrcwf_&ZBmTM zPr$&yhY2h1er{boVxGRqR$Y9k8*lMh#+s4@aeCR4GR09crJpWqgreL5Q4>CzJ^AGE zW2bP{PNB%qe#D^&)d{!x3OmwGezdm-t6jftSC_U6LgBY>O(7oKMS=o% zD4yB1cVeKlqNNJ~q2++L@IfHRa$cB_uTKzBJ84^zi_|QXhbWC-B4X{yOvMR$(5)PT z$!hQ?LG^GyOG?;vFU_^m_f-h-+Q;$vL6n-C1 zw1I7I!Dsl-I3wDlHd%sMqiRE(qI?$xy=mVdQ=UrNha>S1Q=g2ShBf+T%U1(5SqGhP zsC-fF)1c+Ea|#JL!4@M{8(`F*aSx?25rZFDcou?`W6k-@=!7}C%5m+#T6SHptCM26 za-Y>~iUbysPw9UV%+0!*y-YGk$Nr%%N-2QDemA|udSzhx7)B%%{rSWZpI}8 zP7)}#toKkFW5n;DQAVuZAi#WhOY}??0hvzFHrCbEGfa$@#U51YMI8uuVDI{qZDE{- z#c*7T+`Suv3Mr44g$4u8NUC`nf44|Z-Q$}6t;^e zPM+g9qCidUx{vCmn=J-6e2}#z3|Z)=&^x%GN|#(*=fTPLC{@oOWvn@!SM9dP)8#U{ zZy2bq+w{-)-APJjWVNhqKhy9I#%#2vxDU8fO67bbc$#R5&7Xf9m`Tw3f}ttz$nGI< zG_+xi@?V4hJ4DkBTpmth4@tF>RGR@-R6tj)M-6!_v~Gi1CSc9tV%IO1WUXRM)T?%c z6_nQtjr~0W=U`eXg?8&F4)>!DZV}GT59Q3o$k6<2;O@b%;Y2=~Cs9)>{U+$jt90Fm zk_nGk60Ie|cAfvxMd@^FpI~&?kMO{Lh#OK3yGRjBM)Kp&NGg)YRO?s5hzTFvH9Z6x zQeMB+>49KEPwM3mA_4x^Ems;oJO94x^w#;}7M5!m{IeXpLB?Gbjo|50H@ZK4^+&_r z`ubMo+Agx!)HBjouUfa- z;>4Gxmx#2?ziu|t{tQp)&`b5-S@-i)a^93ult>SBSbjqsPE;t3zlnDfE1*;%XDeS^h#(uR2-@0=fNC?eEbZQeys^^a;U2P-VkHArfN?DCG9b88yX?9`5{o|)=O8OORCYMxq`}(ZfGf?y_G9j ze`dszIrD0Mtm1M@sEI-s&=Pva@!cXKrFI;4!+X>+Fs-2~c%R1KiV*-bGeFbDwj2TO zPUk2S2KVR8uq4g;j(tB@W9cWG;#|VRPtA8G7f=fAP}S3`h>T+cHvIOW%KojyeAOi! zeE7f^k~BIer?<1cqMLO6rMtUYJD2ydG`0X zm>dA^2Fg+HQJ=5{J{!KBfDG55KrF82KYlW~xf0chiUZ4w889~J9TChv(Uwu1dy9}1 zj<7wxLbjiFu+Qsj+Z1AF?gjC^OTN9A9wE%iM6i9cF~sIRLmNYKS^6r=+I??L!x`ni z1B8gVj{)O1s$5tRG~ml5@8o{>!pX_Lw?0wlaCUZ&w;X`$=)>eh%9UInKR%M2Tz+n9 z8e$x6%!Yu_H7B}_jl37Zdj^a}cLOw?GE07^n`tz$xP?KveU}rffdS-)6b_RDgwHY@ zPWeZ2a>OOmRN~QQ%GRw`{%$tv1hc%Wt81EWJ)%g=s$4=bO4xmt@-nil&||$$HQlvk z&7MO^4R0Si(l=k$XUsQz7UYKA{YCqe*<%RlaqTt@=N`<3f5#x|uI{KZNB}oan(f_q zbXaAmmKx_yAptD)=c&2j1u6&^095>mo2P}@xhVnD*mUe#dnur=!vh1}fmwDx)J3$nZuZ++*R0z&sh1@2o9T2N1w;b<(Nd%w2KDSI zYx)Y0X5>>5%~8kuEHEY#7hdvfBpa?b zlzdvz+}zxCkQD=Qwy?;6k5oz5DIq_M8M`EaZ983?+3xTK^Mytx1(FuO89sjgZ?ln# z*D*_|wZR3WWf3|Vi*s{6^pAvW2s~pVqT)|VJeBywMSg;R!j)id2;H$`2kh;8u?vH# zpE;3=_v-#H&0T3Ym3_a}(<3j5iVP9;s7z(bJU_CDqDf><<~cHC+?8e|sZ0?SY1kAQ z3z2zDM45KxP{KBkXWjL_?{&^~&iQn{oGYyH;hu~>@_fj0$t z78kV-H*;b;-x7LzSHi?>t*Qs`nUor(pAPK@+E#{ygK{!3();mcNhP8cRNw4{XWaO# zZChetoZqA(%Nj_pubBFd^($#z{y`xmgC59BcJF_1Fk0BoHs#Cq+IxH74>#L0vM5|F zcCR;!&dttl@~0SxU>Jd}Ox0i0qmtY>j?9nOEJB$3l$ZbPYymS1)C&;1m3Gv!jh-&g z^HK=2{#y`yLOIqNxI(RH{cRW-=htH9pQ-7CZ+baeUa#-%<}?df<}*$(Dh%rB2Zs&p zQwqV2-mbBGSsGJV&}~mlX!Acb5V?-ji6r0E3vqtU&CL@tQ!Q_UeYEGi%+)C7+HgM` zYP5WpVjx@-EgJoZ837}Gv#ZP1{loiSNyyosS}kfxTlt9uWnaUb9NDr;!CR{Vd% z1moMvQjKEg7<>-#J+#_AQL(W)D5&l$@EpQ=g}4VKflNdKg)-=#a?Chs(;x%OHoLYl z6>#y;LtP7v5QL$Ox^3HZ5INF*6md@V1gx=AJ>88TT%elTZbikd+5rKSS!ly#sD<_e zpNjlkqr};UmRt@myf!uP@=7l}Fw zF4z@wu(5?kMfqI(@y#J4UqvlLQ7Kxe1e*pyaH(Z;JW9#EgAD#F7 zf|;_!Vl`BuJEf=o2)F#;y5W?A!+}x52Or^cH}k!#vCX<7%e7XkehT@%x_9gNvc|#B zV|*4Jv0p2#B;+bE)jUCIT>!4|eU8yi|3MgT?l5~y31}>$ zH}Q27TRD3@pT?1Su1C33*PgY=rK{*v;rgV-Ma=x(p0CrF#$Z40i1n~SW4OY~^CDmN ze)LeWIQ%~}HJxVdG&Qw)zs1lp6EXFh{^PlG{2qsVrc9NA%u;2+jxX(-V-Ie2x>cD8 z_lJ^CDePzA9|kZ}ZKX4@Z>FbnW(y{}!C*KPaz3~f^HVWe+~_8wwKm(fZSQlBr@q3r zTtIFmy$PReN-UVMEjZ0^3!0^90c~3OhmydP#e8&8Pa3L>c-3cD7$#o@4GTRfEZhcl zPy4HD*UIuWCju+~Qka+I@K^TlIT7G2?guviSaP7>N@vmWeSCCZRmfx(KIO0zlFxbV z8|P18kOHXkZb@NApgUL8f7t&m?bdQWdvq=47f30`Hsv|4uc*+9V{XF^6>^ou+Q#Nm zys0B--|^?e`vE?;Oicv$*vG}iowlMS$KSJB$TxyKL%HAPRS1;bDWQh~!-!w_{$`>S!M*w0iznC$x`Uob)fz30_a{X+ zP&#tgcdqaUkFNn#f!S6GGy$_e)wT-@b7z~>7cS1i$Fpiy>c2tL(Q{fwpC9GP*B#RH z2=ufo!mi!h%M>j8XmoLGWTX@)B16r0EnZEQfjz>yr8%0Sp3#}p$2P-$-M`2FOMAOh z&{b#pIkrvQ)!<%1boH(*Gq`k5t`FbZe@K(&WNF^w-q_iP`BM7)$<{piHlRZcYW?=8M=wqXc4} zmxnLWTu#aK{=qxjr(+*aKE3#QsJ4P-yUgD3J1qwF>cePv(T(k-K1g<6U0aYVZa&}w zg*I#%1-{puz{Nqq@ef6h+YyOj&D9ZTH61EU(UG)Y`U8$EQLs>lOyrqghFN|l#tV9* zBC=9Cr}cEmZJ3oRCQwF*wnDvHQ8+i8#ntW#vT`RE?5NxNf=Og zJ1s9VpQj_M0j~~^@Esc%X?r4=1-}p1t74tx@g();{}FI49{)N76|SinE{)?00FA#9qn5#9eqIuN3&$>wz1d4A7ZaZ zJ&|(ikB&=s*}s4PGef^rzs+H?jZy5L#C^?|Ma%=xlb4{+CneZ z910yuSy$n@1l7^MuQqh$fd$X*UN1E6?-fMt$Z;fLlj`!HvitB-Tqk(xWR%w*UM^G) zvL$~JuR>hvUX$iGKh?#|^5#k#M?St%sl2FC$YwU8@AC&H{whs z-5V#k>0KM6uXid@F}y2KZZ&{ndXcHXrES995R+XOqnMu-`p3JfO_p0+SubPr!>|;+#aL;1 zsJx=W@bqW5NF|urEle%IYlh})XmG@o{N+kY3aKdl>Co=n^oED*U>-RRCzk9eix@sZQlhp-pZYICUE(n;i_oTP)1EHy4|=`!)M?gQ^i1n5l~s+%Gq zQGT15ZWVbv^l)4gBvTn)2>=r$oW}xhC@EkN+Mz2Xqgz6}qhoj3=xLP2b%{z3roxzS z7#lZblFp74Sw2Y%-IZwlZ@625eRvmZRng@=!opA0i~qKAbGv!>F5td_y@XhmEbUol zL?U;jK_A`Tj$kn7Wh27D{MuhTp8P_PKg91O@e*B3s%r%mrj* zC~2XST2hjdCHa@wT3cngxcY-v;u8}aQfjSG^>KJdQV*cm&%5i>#BFcBGZ`OR!*jV^ z1}c`X{rv_lBtHR?rj{p!FR0dKWpnZye1*R=rI_jV_J2LuLJAfjkw_kkZt}KXWyYNO z{gC`#MnKXQL2bb!0;?9imLA%IACEPA&5S=*){nH&V1)?&aq735Sz>UD#B(*5>&WG` zN7p)Kr*l1{Nk&w~uSnjxgiFm7$varK%6a<;Tn3Ym9@I*fWBeMgu^CaKLVKL*$z`=Q z+H*Tg7Xr5Y`|00T){ZQNWo`8Hr|*gS^%B>;j(ig*_e5^L z-Jun+^#ZD+quNRDQ2c$E&*J;uy`pwU*LH>Dwj_RA6AL0E45leh>?M3kb1_SeCh_ju z+GmDY({MvujwsIK31+pn7C@Mn#!t90n+%xO2U>=40izXH^mbrS2bG9$*+<4vT5+=$ zLClu*W{s`h?Ni23F2_1X6_?jGoA0ZK_=Xy%x|Cnzn@NLWIJ8JbOM3fWV#8}4wpgj4 zbtNS`Oq#ba(L=wsW1na!w1_SFMV2|s4KMO(b90XtmhwEgN69975pL!l9>OP*JPT{c z^x@Udx2xcC9d&47FYgK?h*^~x9tcZgq^)>$Q>+pPFoJPJ@79})Y2}<7Ynwt|Q9u?b7 zPQ`}tFT?s_b;f{;i;M91L}mEUu}>>MT@+b0C!V)S`1eP*JT*T^D{|N4ucyZ6ES26& zc%ySj_aPKYYZsdSLqJRz#oaWpbA zq*%O%a3@C1*U;*+vEc99#bb%%*H1v$iqZb;Zb(I}`(q|fni#7_9r|`JCgz&CSP1J6 zhzDzqMaA4n^K%E2|K|fGZ4)P_WGZ8123L`0(kJ89m1UP>n>Ksu(~2%=%QQxo<`5oWdp9<_sNRX74 zE`wm^e8QXAaWv|^h6RKtSSv+!ptPKfkXfNJ^&veXXZqpBEr!Q>zJ7goOI+*a_#5Lp zKB9Wa9Tw1zsKl3zn#t!upxeiJSv-6eSOjRejPMaZ1gsbF~taS z)TlDrexi!TMX#zk_UM?cLxky(kZ?+P>m)EP3{#~_Ep}|h#NP0KTojns`xKa zGoeJ|VGE6oHJ*94URqUy&joQ(8Lvu_`->6khw3F5ABvH*-F)y~29eq`VO`XqnkDvb zsGu!PA63150Ys|^eA7^8$S3LNp;IzGS4WMDbO zl`(q7OS<)O-CSHt9qSQM)R5OCfc4Ko&v{hW&aO_tYgUoHXx_xyftBgBWAByB=h z1QwQad2uSxZiPJB-(!)`_{Q6+^s(^HUAs8+Rn}WEllrta91&qYNo!VwlJ#|}U*4O2 zJ}n0-^yfHW)4f%$Mk)D>*O?>|&5kHYpt~R>e-Zp@gCn1ucOr9HNRcvmp*_tD7ru?o&&mO(1_awuvkr^}hRgqXqXh#=hQAqd&= z>8hSD&)Z&&1s4V!>#V;1uHH08iM&I1?OF^@aj6YDn*_DrAKo|g0rl;RJ(O?resX|H zCx22B4nHuQ?6g-dG;2yZ%K#9TdA?T^)(iw(c-Bt;5l)O-(SZ(Dmp#BMxOljGe*Qc- zpnKP|Q#OvuHFEG@f{$s-C}7R9hS8wsev&0!8i$tRGJ9_eQhz9wxgwXN+ogq?}H}=XH zwy3ep$W~E9C_5|s1L*%*g@l`YWGvMAllr^5u)5vXL)NG`!mdInCLX?IFNp{#^tNFx zC;kb?7Ux41HX|7?gLw}rYvT_BH7!$9cWY{C!jbpL@5+mG+Ro`2ZO!piJN%#2^KH9F zFgLKKnAl;IP`^(VxN;5do%ud}CPz63!fhZ&gj*h7$gjcZRiFYApQ?Wm|5fgoDm`vy zraK(=uR~wu4HV1&=f`z)!V&zO-b5VaKPJ}s-@ng%{sMd&;Z7?wGD*Be?*^}h zI!7Rp5(Xl7?<)jt#bh;jamjsh;v<=i#1mv@uB z3l%9d6_8YjJ$>&U$vo^YWDH+{T>u)rCH{woS)bkab|0prqub?0I_YI&)x!HcjE6(va|E#NhVB1Ne1;T;adOzfC`e8R09BDA<*v=5&|>^>l@uA0Onl>NK)*h zJIv8?pXZ@8KEme}1@`ojpi~niCu1ZTJ~MOk-J7>VBMPM)jX5@{UT+Nd=KfTh?tKca z8E$kZLw{}P`}1^p>UZ~;<#)bx>i5~p3sF~h>CVV$`tlh)?e64}?CjWWjSLra(!$U? zj5gB?Ge+q7i9Jqu>h{alhs@=eiYeqPO$C!|hG0n-T#2s)J)SsJ^@sSA(&cHK)BM2?r$K)et4!ZDe_S!)f)U=K z$j(rrylV=~UUgeltXi$gYigXu_n82!w^T_0w;d(Td7=> zP?rm5L*C#&)XehjU3LIyZ`7u~_9MKnb$=2vy5b3e}Z+SJwri+lJv+h#6&rV)=oi8_`v2_Wc zj5@xG>O;BEjX|qbX$6`g)7cd~yN*YSiuP+;g3Bh1zals9>4(RNykcE2Y|%|gFOnYK zwE&T2Rs#h&8sA&qebc>_IygXN^Vrwtp!u7fB(QoWJoYNU^<0Bzv(V5N)ivZNNO#{! zf&HtJa@T!Ud+Cww4nnS9;q&541c!+ETh9M>P9tbZ%^43RLY`I8wThcE3qpIDBXU3D z%+0A*q_(J@v-CUdF!;{YpU8MInp9JIr=6e(6rb?v6c>8lKpghX;6 zW3jxA&U~q3R6l!e4yJazc*`bd7=Y<8!}Gz@{CT9!`0M^3k6}mhNR(BJKY#XVo7JS! zmRjHArb+7i1Xlh|Vf;!&Pu2K`84?iWW!_NN&Nxtgt9j4G%b~;$A;?_}8K9n?-yUHk ztqm%@Rj@SnjOO=xg#6}7IZ2rZ#Hxny*EN4Q2Y8TVq-+Y>yIX(rM3f;vlp611TRijm z(;br1v5F@T)>!oMba>oJHk57k`-~y-`h@cQe7iQj4@o$PARok1#YI8pJ;WvEakcHp zm9248f!A-f>)%d`VH%|6RjV{mlV*uh7V6A4#Yy<{Al$71FbLVpMT?0}9TlLN-QL8$ zT8!UrN>I?zUi$0+@9^d8;bwd!Ci!>ykH&0jxa%^ez6D>VVO8_VOxu$hD z;p4EZibayIBUZnc<3f#6T2FhmAS{fEz|-b!y7VEo;??%TaY|IkncCUS$*!nqMs>p5 z1%?3)DJ7}%DC|7<^b(>J6mmw1wqoq;-`kt}xOHN{Air!6F@HTh@o69yB{+Zw5^_t% zZM(fvL9+DPtuW+9N!y!TZqYLyat$Qp5ueWo$Y8S(V8JxNnF7DX29;l`=EF6;1l#Ac+86Ii!D zHF-PTB2x{pgZ33Vo_l{h^%$QKZ>07wEKe#@Od?nI>D5=&g=5!R3_Hk^Y!lFy?%lt_ zEr^du)}zL}0cHC3vB#~ipCG1xS}UartENx2s6F0Lw(a;;*Z@~7x#aZlEkYr^RZKV{ z>PLSPHiYekkZ!BU_6z3p94GL8h$K*@sQzkmmXF)*Ai}d=U*c-TZB0lf!Hj{+I`b_C zL8>@NSS&6f!!S3Q{O8jL0W9F>jcV768Qt5iK{l#EpMp}n z*G7_rt5Z%B=y+ET}-)m6ZTTIA_c zH^|~beAMHm)$IgY-uLI%UK$H~eT6zmYBOwBE|!|5r>&UEi?uh_bj>7dW?YY9EQW#K zRt{gi%XLkBaD5ar(?r`z1ko*hXA>;9{T_04mwQ$O%J+2f{D{_&z6|~6D3F587`*S# z(F#47Zxkb=73pnoJ<3;9ybPH;hBR6=jM#39))=h4seNyo#a>wqV3-;ZWd`Q=w#w{m z=c4M<_v(MPW+jd?Gu1NIX}Bn!McpRn4UhKEqFt6ZR40}krllGd)jHb`;0W%bd{`lpu5$cUP8d|vd=>-qF9?#@)r zD`Y=JR^UC#EVirNJf90rSsuoG2?dGaIRLjXF`Mca+hhVm{%l+z7}s% z-ey9i+QERLBNtopHGDccqQPZ$_4^Bc`fM1>R5$+IX~!!}y3343o`?Cb8m^TRyP*NQ z%u{1w_Gkk(?ljmPa{vM4XGYBd`8}&o=!bkBInj&hQ*TXyy@tKCsX*3t{l}|mWh+CU zYmMW=EZ+;A?duA`CCUN0Q$PD{RlY@HKy#EJL&;!wkDTTYbE5^H^@}P|hr*OkUVkPB z0=7Lp5h9by`{DXstM53%gRdR{B#4|Zbxu6|Hprs<)0lq55qup4A?Q{sYU4q6#~!#8 z-c1+^!&fa%<1icZeJ_Z;M||$hRV`*ooB)-@?FAGVFLG_qCcDV-u5&`oLs`TZ7#oxf zHvgUUCDL1~ zMsw?_K6=&ZX8<}qES+Wq;3S@A^R7ubbUI@tp6~lgc>ob1eIAp|8seM6CT5Hb2;$ggXW- zL3M$`6(W~{7nK9?0S^_Q{5-?qCJwgXvp>3>!$~VnRGK+jF1!}6)Z$eRcE|5AjdW`* zyq6>y++9T^&8?J~(Qm1KD;XnP#&evct2r)1>V>u5%M&ScTKJMrwHfU5B!SlDKE(54-Jj0U%ltm@^f%XD$A4Eps68`A6U*Wwa$(gG=3JO z-cNOYUe31IEzGqZh1(t4$L%_3 zbkS)X{$sxCeC6Zm{W1G#Zxs&nrD*)G+P#PbY>V6yOeGk^^flex8ob+87J1Dj^}Q`6 zBbv%@$A!C!VC_I!+pe@)^LrI~HT8hhDsseS4TaVs#V+}F=7xt0L#{wi-#--zzidwf z#7z#yEBKDB$(ux#A zm9`T7O2vC_2Ma{ZH|{S1PmV-G0`5CpWi#}w**O%?bxzzmP3K)7O1TYEqYIdOy~*w^ zYHdM`)WTh58GytKX~iE4u<)p_j295bHC|f z#HO!`c_##zwoy3(VAsIjXNX;JgiH~B`vM;Y@d$A>1mCW6f(*}yEGU?6BKYC7Q9Yb? z<_XF9es`vz-RalWW24(o<%c-qty$su`gy~^`$vZ|KQh@+S6Y?e$UFwJt z{F#0g@klIE-A1F{(PnzDZ^;x{m>;v!Gs8N%Xg7(DB^KVF%-6?cZbeAC+0;d9r4eq` zxis%QTUf(D?w;IDpOv3=i67a`4Yo4BS(GQ*sfK=Pi{P;0)0*VcT&o*9PxNH~U>dY1 z#vj!A+y+PswZ72M61ty6O9}ma1daQfuBm;3I?8umdZblq|SJBO`w*qm4{wcQU__f6EB9Kqhev1nZgujcXp`s{F}sW@!dmtLY4r zh1|SO;6j~vY-|U|CvR@_AFb?q@lWsoK*)fqpfF~al=<`?Gd21+ka7tS$>SM~&U45` znh1GA9h=_GO}6ORw6dh$x>S&gk_3aYO*?{Bp@@1tQt|d}_L|iMUCsIpcy&(=u529} zhN~i9;o2gAv7qo`W@RAu-q~R>xDd{?@?96u|LDS)=nvSlUa|P-C+6+9E1V`a6bd~2 zY1C`1bZ$GZNeDeM4)tl@Fb^{y ziqH~TDGLy-L6@$Khj9>$aGdujo;y*``81J>_HqiO7P_nQuAp>sim!f5?n$FL;hXxc z`y4-(u0BCUE39SU)T4T>@Jb`px^vSC3$(bWanv!ZV2YNoWt+O-_??t6cm6ZHi7LN7 zTLyU+_S5t3o$b#4Re^?ZaoPQ_W@92Pis$VLbEaKs+1k_Th8OsC#!E%rlM6wDF}CgD z*Z9{SyN9Bin`9B1SmKV6(_NUFFHn2H>_{x@?u$8x(mBOYQlwu4`SHO^j9r)sGR<6I zL{AjANgSrDqRty&Dp)mK^1F_y!xMn461QnSVmLIOvJ;{!8s1o?8+2Apr_AMU@)_My z*6k`o!&(Uq{Pm@;iic zF_9Lq|6LFwv!X_L3(w)QJ-i7+2Tx9B@BdQ{cdzM6$#roISh5~z7Nbw2)t%EV| z_$lh#h`JLOz~B>Y`v>nr-T~$3nDA3KZ4G38vAMPGh5i{lf%23HGt2LK%zO~#y0g^{ zW>0ZZ@1!OXDlqNIJtgLRJIzcUF5i=ir}xo2GXk~K-H|C#zVwAPPTlblWW+%&;4SZ{ zm|ZeN#vHO!f*;qh|8b?>n5bE2terpc{EtzR3AEti#!Tb&siZUM%a$7mZ1o1lXbltD;xp(%R0*xCxc?e|fB=jpvW{f8Gz2t;i zvhe--h$*t27a^p0rU45U3aC%7v)8h|UT(`T6dN>gQ61vrk%~a*dZXjCgzeNN)+YYMx1P_IQPKyY5;A9*UfY> zZzjnWAwAjS%vwFh#dA-V+axZJy8BvdzL|EyorvvfhdOT}m9lV@x30Q()7$i|6nxgz z^+Y#`mWOr3=pPuL_a%}H?_qK-;7#*N1%{OxQed=t29%wG(7Otryg%(paQJ_k=hP$Z zKHd~MO>VH*?Ka!M7GQn+j8YuG_lQcSn~d(y^LZQIH|9w+F>V`M=Ki*yq+6?h@@{ZO zi7a(pyL}LwZo+oGhz_q82Ub?ehzOOOfb1H7fa4@%TVR3Qr1wp4brnC2pZYzc;ltIv zJjWc{3ERPQ;}=99(5Vv_Ktav$0_KgLwywHBKoN6BtNdZs6zl$cz+C7##l|7GrMCNI z$#qw;YyN{|M(D;hXM|UiPbAg}NDaMa!r;01^h&1UGph`F>d%0E7LX5jw()6v?~vZ%Cs zA&HWC!XC+`|7|l3SG+bvZ|4!N4K0@R$xvuq2B_V0&PUFRvZgAjo-=#Mz zMiMG+z9KhsLdx2`m@9mo}-&;W?NNsHfIW3?!qRKWwSF5<6Ze3aM9%p zoVcV98z*dTTm0%z2J;U_R2F&vws>fxM!n$U89Lv&qPJQ@xU1+qN!KTQT@Z}D+tV{0 zE`w3y`G0t!1qZhqtS%72uyxJ$i>Et#c9DpGBGQ-9*6~Xa0#wouAxT2moz@Hl!=j)f50dt9Hi9LL!ns%^f_5+XE)me%NN@9={yDt;uB^A=$K7 zsa`?saKOd{?M#kX!LP>sw`9Vr2oR8_oR$nZU+`}YveeGOzz7YwpBBrG%Y??(jyTiN zc@}yF%l1Z`qjDp#qZVs|YKtN!TU3%;vIaw~Jc~0HgZP#>8qUk#4IZ<3UdJWF$(xXJ zh2s4TNXv(ZpvdsaN>tsGp618q8f})3ffony?WFp6`zNo5`{b|VZD)?h3n{#r5s=q= z{FE%8b2CifuzY6ucdn6Bh&+zQN7akrBLiU^2Lhvgfz*QpJ>yUMEqAt3%M46|KA}-C6Q27Z#X~Wtvs+uJZHPda0x$wmHJk;;TPL8$16DaGNzm#m~tNnPl6{CNd zWLr)5K#upiO+Ee!TT+x8jwNgRni2khPDYe8+$7c&XQ|LTy!k1e%Xy**nO0qB3us*H zH(U|ToLN429YEFX5?t1#>wL00&akw#^YPU>{%#p_QV~q zz4n^c?zJAr6a&kJaDcF*9OVWxs|D0Cep)!!Iv; zT%xB#6DaSeiU|0=UUfh#_E(P)68XC7eR#~#oH07QXs&=lyoM%+7ST?Eh!7io4Ro|MpB+lXWyM-$k8^K{-#F+Tnk z!?yEzOkx091^~rC<+8%C06;X5jB6SC8wu)o-H=(@8iNYh+n&+tI*>~}Y`lBa%x*H9 z&d{A;$!R<>5yx&nSe3$N>F5!A3hFyc;Wp81^&!pH%6bWXL0>>vKw5bFt?Xc40VwjM z@vvht5sO)EyVZZ^H=c=Qyb^-S{$>7q^h-K9m_k=9R)Zu?uU#e<@-xx6Zuvrpq0iz? zTqM=dx5$zRQ$ydlNZF7ON&P58bHhebN`fH_h}VL^K*5qLFxoHw&?|=YuRj0%_*gI9ApU`faK^Vh zvPX~m#+4j>$ro73Fs#x1tppmUq~c@@t4X;teYmW8H_BS^y{03;=;1lb{8aY#A%i-;eEtkTv^!#D13STw4 z-pYNwd!0n3? zor;*8ICgp!Z`e-7(LtIwIEwrXmEveDq;*YgEv40P5>y~n#k5(a^aoOkd+cF4MO4M| zyc{be0demo_Wl9d!pTZMk~rC<2Bo%&k_OVpviIQpeO*~K*^^_kToE+LME6uU-8APW zhD4uPVlERiXYxBm>dy~_0o!HEu>RY$0o!)RPAIxETEc6gF{IO5h02z;Msc>wZPyFg zD&zC%HZ$#;4ctIVPZ`+|r8vL)2QJi@=xLFG_g^f{uH#4dp9f;{a&zR}PF5dxMH;JW zoG49Dh~7qhkL!lOJi9uYN0W7gq$qB%F1w(P(g}2a8E{C}oR!s7je4iRRMb*&?iISN zO#bsj@gcs7fl;x$?w^DGRw|(GlE9YTzSIK(8kHJ(JPke2E@?Y~OOiZ2^pLu@ z8X;e+R|L1KlI?qvAgI3rD-tHb7?VZt@p^~VX)YzCDb_@698docIE5F!u zHd$Wp+L3;gHINEaJXQh|_oSIU}R>_pTF=n)GDChtgk5>1` zK9Bd*eBfq(BV?SlZjuud5sL0@!o%*`d00cS!4`Mf373?XOt!s-46eqr(Lp5Gz@ETn zDWEjLlFVrIV_S7UDjE=42!&2f&|8SWnlD1A3SqI<=WJ0J;5Is zw%*;Es^-t_{6PE2CVCVP{puAph|MHE@vFSZgw>4eu$^*GtsXA>(tfX@TO6S$Y% z(sbO{v(YT@fU6~@iO)Y}3}@i%&yd6B{V{_8nX#yyFr@8p5`&j}n<19Y-fugrf7v$0 zan#pQDX)nlghzbnY#Lcqdb`4=k8D5CXx3CbQ2yJ%g{SX9)VE8}v?~RWt&9xupLy-x z8gh+giaSWtNqK4*=~$ht-5dAtKOrODb=iuaW@0Oy+M-qF_9Bhn0)|q_XepyPvKqE8 zasjuTzOTNBy)5+g4Qsh;`>GdP-8&=6ur;HbSCSmh`-7fT=z$80o{N34`n+pP)C_#dO{jsYGI&fJk z8sX4Rr5_6-k`e&VXQM5G#e_#ZUz9#JTkvo^yXyDs?|SO3DvP;&p3MEmUu)~FzFW9( zHDbC8a9~BWcSjFz(u`@@2Zv*|9%VmkYi>tZwzWieyxQ{SnjrrU3&mxK%>QrJ<`6;H@Ss_9Sg7BG3A3%LBWsVKgqMl`WPrJ^HcBq z@uu|z4&{95f?a-S-+8-;y`1*B4jn5`wtxNrnTwFwEqVr(G-8SL~>@HdH5KokVoc_lgCuoE9d z*6C5nn$MF2Hr;mYR=3HX(4Fbr9c-6rg)X1uCkNm{fDViPexGHN+)6pSbOo46$Jn#e`4tM(s)DH$rEE5lQ6r|6c9d3*XWpi17u2*+D>XpH%RX*o=k?&bQnoo zq-XAVF@Eu?`z?I{$@8yNeD>@zROi@D`Ck7xe{!sLn)XpIyoFtEK@Fb2A+E$lM=AK6l@P z(;$|3&n6Qzt}DLzr+Xv7s3gQ{G@O3ae40FfLq8=-Ibtm-hZ(aVI?9B@J~WX1vlG(= z;!x4Mg4g8f}pcIk`vIM!FHhLjIxGAkY|%%GEC!GG*QZ5{t3az1M1| zWJ4bkmsXu!zt4wcelL6+jlpxm+3t`<|HL{D$)FDyCc8TeoGO`J1y$qhp zezfhNnjYA?hZE6f* zaj{55LiHJm)n1RJga?4Md(~(gyPOkgJ(|bgjtIbJor=x467(-{Y*_g2R)pP=;q$g< zvOv*SANHHK@jT~iPiVv6VFUwT^yb4L8o!mz35bwBXy7mLUw?tKw4mlY2%4aLPamCl zSe*u)lFNmpU=&EU{Z_e~LW+=b8eeGlJ3!I!8(HDvN#xXeL#H0eP|Jql1$g*5i;I~J z{G!o6d>rF#uQ@VQ!a7gMzewgA1%7Y<)k{#}lNLAIeo=J!GkmEkFC|KfSmHTJq-g%+ zW?Xd`IGH%NT-8p_XB_ie9TyzT{UuH12epG2%y%1zQ*kWlPp~I0nN_x<^%q%$5=({p zw&VJ>Uzn^ynH{g^GWQ=tJQspw-cu0R6M0YXDW>rWXn;}OmoUMoyB0-M2Nu8_oCl?! z%x^vv95^n_E*;N_ruTo>Q3NHLx@3tf@QIko;^GncPfrb8#u7hsSB@O^9Pvb-vnH)3 zvOj9P!+0;hW+|Z3Vd#Ee-=A1Jb>6^HaN6OV#-BpkxVTH;KZfP!CQI5DX^r}!@V2^# zbG)6+`it5-rmohkan>ay-Z$yuNKfWki*?nb9O_ z<0dj4u14QD^X*Z>!cR&1zm(LN1bZ?q$Y{x)sAqY~BGed{Oyw(+i~ve{Saq9QP=FE7 z7w^)J1zF#-$ENoN9W$@adQ_WnFX8u6t&V2f25K`+!ecCc+@!r~SQZ_f^);haX!N zG?FojuzihdK6cihpEe4!rYydn*TbU&g(OBQs=}i%zY<*b zDaF2%6rvhh_l#!?KW|*~V-++#YUzBvNBp2&GO7gVjSD;m9D6EoxD09W2@dp&Qc!8W zcLe|=5| zqIF~%PPWT4Zx_iAHUXQ^~ZPDpH3dIgd1ljJMgX4!``D>mx3iQY#Mf~j4^;?8Ux ze3G7J5FnyS4<>B$#jtQWSTi5eRu9NMjDFkFFjj=UcNie^c?#P@vQ!C0;LD0nyAn=& zswnUHBKE8?F7r_ibw@`w2o%faZ$zPu^j!mO9Ioa)6VX*V+#wBTw7PyT>Mr*QXxe-v zmwYG`B7Sp-o3JIsK!oe})ID7J1LFuuw!wL&PC!3B?}hU}Lsp)tQf1SiVb~!G=RZ}c zi-e5DYFTqg^1hcPmWkjh`0kA4AyxSE^k)I!-lGjz!P=&7Gc94@YXCC6z*R+y+yLT^ zFs6kzNs#1nRb{}L#X+ATqw?qgiWxlLdr2EJ*7$FHPqt{kGkIGF8lta_g%+b2kz@+70o}eNA8D!4 zO$||y880u?DON8kt~mRfidLX~7vDFfrt$6bd`fO_?T^+&hGT1~X^iKot3?ha7#%qp z98o~s5FNdT;|wIUvmZT5x&mp)xEv0ILa7IJIO8vDXm^QKOkA4F5nrO&q6Aob4`*J& zy)ZLFFJoA-FgGkg?Fi2B}NK}-#>_jjvHo%#THkZd@ZSojC;RX^(}JZO33{K+slrXhEZ>C<*k5gUKcSFA zsSk26BY;5WFCjvV0S)}+LjLj~|BE8|A4wixOo8<-&a)J&eh+#Zak5V_CHL$I05}N= zOS{+X14=$$cXlw8p-M_hQh!~Kl@*5ib9tox7w2S%{y&(gL<>7Vk$N}RLNZjvLQ!2W z3mqK;rF-#>p#haPKl?Ad(QdH_p`qwxoZ4bQ3-s*rT7l7#@j~w$_qQWsqlL3$6?JVj zjyooFupT~6oX;=ML{kqB(uBmM5Xa77*)h_t+bqRoX{_EY6LZ&_mcUDh>^Ge+WN}Pj zn7HK>$NTPJa#FHsPEH)`fBL}xKim+M4f}^Ug8S%iCql@&;`ghOj0*>v1zK=n2xqFc z;P`5-wOrR@rYf=F=F;V=Q5?wg{TJi+?GT-CQ^^|WPzhQsD~&%UPtD%klnbBkY-@Tx z`B3d#)Z+~O+pEx;!b-a_2i;zlf7f&Q$sEvtZ_yp?DWLDgg}lxSlvZ+gu)KSf7pYuV z3uVZE>*Ds0>&a+oT_^m&>(V@LxLP|~)nSQ%FRq=l(e93GVIISts*riKu-AJHOa+%& zrHMASy-4KjK*k_AueJed2HN{7=@>F$&bh#G-MWw5c1sJJkI00)AE8G{hxU3~%fZ7~ z9L5SO&U604;8^GBd}=;0*cw+HQ~*>5(?hms6yrOWnuphpg?fiO(;`Vlv`<95gHn1v z&dtbvl1$((=U8BOIPZ73K%t16td0twDsdr7#L(2#5YPw9M5g-RX7Xn?1p%iQROH3t zzV7X611TA1*_1#Hh;sv#vD#QY+-z4f0KSQeqn)qoagoFVs{HN`VTN;ng({CU z@5h@_dm{7XzULaN&t%&5u7RZxTIy64u&zkLwuDSfsjI3(_0e*z@^agAep~KlGRbSI z6Q1e$g~k^F9B~j!eNvcRGAReIGcW|ssR3x^cwnD82$|k9-I&6WW=~Gs4$9N*l58^r z@^K4`wcH-ix}A!|vKn>_-kz@Q48)KzH2Cb1Hv8;ldOuu&!o$P!?an?gSvk+z3z#Qo zwAzQgBj!&BD}Y>S5=`Yds0l=)U!TSmS3Bf?)jf?90y15wZqFMj>~8kpC5=uF{3y_ z%yG_UnbL$-DKT4&Vrqbfc|(<5Fmvv&QuXDO=WG7z=j zGoUn>9gF#uj4rFmWiOK@+DES$mnQR|eW!4@e5FboPv-GD&5gsn`@P0$(CutlGs-8-dUSK>@)2g9Z5A9$DVF6=+E~rlHgD@vhv=aMNN`|KCQBz=1EG9&n@t>(&i06 zI(%+B>fCPQX6oKeJ0O`$nI&HR!O6S%Y2$VBjhY)~(Z7Kn)Q>m430C9sd79DQClZ9W z5kL;XXVt5LwdrveGm0BROHS1Ko}aB^COU4|GL;Q1%*!9{BxwgS3gBIyzZT6$eWz1s zR8r-p7$nb41xH5O3Gmo?FQ^&L!+G8&U#B&Y6InMh)eH{;s8r9KMn-DxaUx(L7ubdZ14?LG@Fb859m%Lil3u;-Cw}P29CRBGhODMGvNe-t+HVWRh7&pugAvUu-5Z2JpXQmoZ z@b6t@_WCpEphreGP!++|!1Q8Fcq^)Ky4acp)rY@kC@g-M*?ApvZ6?*p`4TJMPA=Jh zRR>X!8VRO75a>S0HuHxM2)Uc^I6tVj6&bvv*vbdhV3|KZd|48sgA95TR(oIb-;a+H zaX~~~nhuuRlZR?3h3z)fv^y=+Y%?r5_r$#w2Y75&au)G_NtvZib8b&19%2dd)^%)s zOV7Q`uqE6fSiEXY_@H;%!pnc3(=H9VaI8b!x+N6^r#j)z2U(>LDQ(Gj&yuui939=u zyJrzp;NfC22l#x`@H(&2s4;)0snaEieaQ3)gjtMYUb6=IJ{-Nttgz%71T0BziZ(8v zt6QkxhFoy{JpRzYbkcqY8`OXz3!M=vV0{cZ4xmd{tkHv#`yT7pR8#%k+o|7&Q@?uf zcsvD=rOWjt!0+~>!;~j%D%Be^TNF5Iw4>!Xa)3YEx_b(1!TphCNBo{wXj8MQU;(dH z$~rFc#-dOjk~tv3NQ#i5nF*~zDvL~XhV?g~gytydVi5t@oHxT@Yw+TDe!;cEo*HNB z?a<|gQ9n`S6+&$6YqkCBCoSL@H+fsFR3kBWW@hH=<&1j8I`q>FE$|4V`DE(?H~fD3|;doq=1rS|wD#6O**i63LB z@w6HkS|p`;%yX)Cwh@4iDS+I6b1vp@Im7)*Jn&QAd1!dJvFPaPMM6epI!q=t!E_4P zeHgdRxyN!4q++s{`OM*a;jHMG_)$D>n2{k-ufgf|mx~>DMf`T}wl0IjpOa~VIoAl; zi;h4UQ5m^;a|YW2_(^b?~ok%UaX1C#j3c z25XRIF+4h{CJ#ej^va{Xk^|XC!yB$TtnlSF@B616`Jcl{iHSdPwIy8A_1diS6B3hK z{higo$kYpOvVm4Vb;+HMPF9f3a6aMYmKvAN+4K(LGE7glzLGnC} zqNCf7h%~^0zO!glnEV}cFxE5t*>@4v&aPk}-=bw_liU4)3jw>iVE#Yq(TRmcXqAX! z_k(3fi=)I$G94#XPkk6$9JFf0U<+&Na1MuAbozX3igi7rZayNt`!r`A4~{JSg5MW3 z1p?6|Y#Sn1YyOs#RY0!kR)qbQ4o;2o+~ju1L_&^;3`yXJ4Uv&JF6?nYjiiml#CSk| zn^-t*XuN9I_n@e&VL8)V%*o!svD^I4h?xlz^jBSbk+W^_2#l836~FB)C6Hrd;i`55 zq0dR0zY~-mYKp~?sQF5S#VFzDWYVCaf2{^kG$$mBREf>faD04)K1 zV2G!WN1b^>J$hs|%70^^wvHw!wkb&R`YIEO?}>452cIc03d0;1C9; z5`P;Ju>(CW>AY=^`?rK(gjCo>)S2wx0;0GJYAO@!TtS!tq#`~A!ly_c4@b?zQCLzS zgNC*!5L5)ZUjgtGx=0jL#i0@&5#o z{>uB;H}HSvP%5Y?k+WTCre$|P=0|GwIE>!bWr~W9kG#WvN%>BP`F3uAk8O6@wnH)x%9C`-`mgz{Ea&QbL9cmq8o2m^~G zO{Efu`Rx#{vu$oLv{ng>l=IjW4U8cbDk%TkuV7+UuKH6{3ivrh-`pSIGV*^GN2t)y zhWxu$&O)0;2l;mZ{t*NN0|(pi^f@B72>bv(na%pKU$sXULhPR#TT(g<`WfPOuvbVf~tQ;(GB$v6*7oNUk6MD?Hp)h3V>^Fa%2*yc^0({Q;v-Q4PYGAZA1x=gj! z>8Dlwh)xO_tj5j{EX=xdL5)RCYlJk~t$nmU>88He^Rsn@;=q+JBJ&Nl&FjBHzIaZ1u4n4yiw@Yl?DsA zol+-P8fe*+)+>14peB7U!dGB4>iAy>UbC;m_n9F}#IItjCp2cN-NLZh?KNjb*!Ss< z;??y^ZD}Iu^3XR2>AsD1?abO&~>_XHyI3K1?{YB;bRW-7b`$vWN&Dia`UeoiN(}Gnk z9VTvJP}@`M*Gp$nC5Po#5udwxWPF61l}@4~Zny zE$I7kBJxGiNcbv1j_p92wFJ`kz={7TJyXK=B05>DYR?ugl9L3uY*^Us^tZCl{O1Sg z%D-ub{SCVE&)jg^tWfkwM>!37Py%x?L8ScNb$aL#vALUA=U2y+`>ERekn2M%a)Q)_ zuMamXk5j8F12mEZ1zQ*+;X@4jlZ>**GD|nhZ{)3O>R@ct!I8Hr4xr*6!oCM(SE!`l z{@E?yv1kW07`0_r$(n@@4>9(tsT8fY?y>4ys({s}rZjLl83nXP2VMjJf26*NX9|wV z;-`8>c$c`X+h{g?qhHdt`5cNKvhZ2<3~GXVKCQt>02ydz?a82k{b~9a_JXmCABnJM zo1phouWd72Xv{1lWL`$o6-`c}ir6d}I(Zw|9cSp|YCJy0>lvt02)Lf&tg*n{eGYlg zLkEdSF7MsaU-?jDwdmZD4_L4)j4Tq94N6+zT3@(KmUFCEH~{aInk|h9@q-N7yc2CR zCgMzX{)DL)Y{i<^-IDT_am;>^aE&|mdAe%01a;Woe_gpcY^2AakpV8C4L;oHdl4m= z);S#Akxldqbrm|HAGg&@t8!Bf0_J3DshjiiK2qbsdfsYPT6v3uCmjbbHXkMq5?z2m z8md++{Ap1?j0E{?=Pf9TD{=}EOapI24_-?nc~1H+@tCE|7sbSY{~F?-x}TZMj-?~V zyrsdBgi5Bl&N~F|2@zuyBFQFgzL&G~d*LeJxcd*rP#AELmB#ZG48zZz)A3$g6DOWTQF~*#^yABdoH(=M=Ch>XF~v>NAET5>N14 z*gtsTO^AOQhY2^wuq)V_o=MWqCPC{@l3|_m8)4^3n!Mj~97BwP-u#S~BZEV;Di_)1 zaF7fkyEt2+!Q<-qLd5|t5W=62!PZAX^wT?o)$xk4rh_GXAcG};U~+;^-{`O*i$O<$ z01YQNk`*nZ zu(7}bAi-xoQ&v^22um&Uwp4I>T3TVIXq)G=G`7lhA6%tCf!on);h1Id@9@W*5L==m zqgB5@-&YzK85n+heldAuHE7qQ(jkZQJ{aK>eAC4d1?_E-p`oEk&BxW+Q*wS=W8L=# zP_n@TNWB0|*oHzs*|S)q&E4ae2QFveFDlX2AKMnJ2AdLuF$L`C*|SPKxd2nZk{G(@ z486dXASM1>Io-k>Rzrig2vPU6A|Wy&cJ z00-~a?)|hu3PRojx*aZPMaf5d=rq4sTt9C8*!DW{FBGSj_(L_0 zy_CY^=I18;<7LsBIQoG)Mhw!0rytuX^X`eikFh3nTM%yIG^}_&H*w7{Y`JhqGP=0G zY*HVjpjrh{EHP?Usvi$1XO?&|bR%Ib(N|M${Eg1PK#6`AqjMHAm{*MS8N~x;U74<*T0q z`rJ@pXpD|M*y!o+1V=0V=%<(z}=tvQhgF6#?f#?3?nhVmTMkYvv=5<)1n&OWp5W)}7+E1n8X`)c-RryK)DVuMpjy4W}a-FX9C>mz#vnRz2>r{%7-4zX7r zX?&o!1hKIoqfiZPM(C5T>r)Qeg7N!!e(r)Zc(%Lp68Fw-HEbImN}BTti30sF0v{<( zB!F|AP+~Vt%8>*vj`)=g(F;WOKZ)i2cHdS%V9#M=G1Xs+89{b^xbf8Ji|L1Lls+5s z6Ux;n+pnGvU8pYCS3%a?TG3RW#{P@5HxGoe|M&mzq9~QENOs1SeaXJXgt3N7vQ_qM z*^M<@$bON1i9{u2H`YWL1~;;fB^hHMG4{dyuIaw-^ZA_fJ?H!To!@`MEZ6(mU(d($ z`FdY=>L$L4_*y$I1VWjc>2a`oC5u|cTV zw`yMn$Miz!ET6`o-*>z&6{>K!a=9*;{Q85-6pU|j0NK!OA5zGZC~5(+=CfePQ$|WE z8WVB`e9sI$^-=Oag+l}VHO~G~wRJv$RDTTJ{5{R$TVv=pw zH8i?A!0P{N{Mqu9OG&E7onk3{9eF>Mi;GL>U9WU`8uDx{ySbZj z+Ap=F>@>RhA}J#d4Wl9WiH1CAg`=aB48N%2E_{Q_$$4J}}^ ziFkU*iv*hw<*O^IrdZ_sr8Z%8tJR~}olMDd>n4=2sw4>YyJ75?0)P7WZ+ag5i+NE7 zdGfYK9o)QWS{XM=%J}j!td~q#vy&hcTLhxATOO>6x`r8{-9jF4F@75AuOjZSzWLM1-+v4O;|5PE#6n9LGIeWPN?Txioe$Kla* z(71@Lt)Jb6Z8KU0S(lpy$07pf>8jVe%ntrgV&;_lA;QeW>b(|pLgtze4W&p?hZZ63 z?Nc!krWJQeRI-^}A33I3wtc;KZ&Rj1fzWtZ*MtIWWwJs7YhHO*V>Ytg+SJz-m+SZL zvbrQdps3t^We1U_XEBJwy(Zcx6^W^m+i3saQbw3|e@AXyKr(y!w1HZElhDDAy zoNUSG93I-cv64rKGNzkX_Dug=?@MS;@D0b`J~-B?$WpOv{^C4`K=0qEsi`|mm~W;M zm1HmTNF2=Z;u;_xVdarYTL|JMy9NJgto226-~h& z>^#u)!>G<$DdJh|R%wnqAvf zl1-ITj)XO<;Jfw@DdLt`qMot3` zk$iQ}|FE??cNrHN?%h0#Vzik%zma0onL@9y z=+;IYGOu=y9N+6gZ)Wo4`7>mC5zMM_f@u`m5{HPo{EF7YF&E;K6OC0I?4YoZ5s(+N zjz~G-ejoUS!>5On_SV$F7tP1re7x_Q)>`nVD&`mNU`%G=n{mLgRY*h6HH(BucM&^7b7`5x4tZH6X;FuCh+ z?Y?{%`DPPWPW zQY%6w`cf@ZOk`{CUP@xbqS(;uu^zIw>ne1v!?1=9k<&s!UtyB(og5@q=AW#F(!@

    w=QP$g^_34 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 literal 0 HcmV?d00001 diff --git a/doc/windowspecific/config-win-behavior.png b/doc/windowspecific/config-win-behavior.png new file mode 100644 index 0000000000000000000000000000000000000000..395827918a5b613cff8bade1148fe19a0e17181f GIT binary patch literal 36233 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�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

    hbzXu{H)9jHL=^fy@w 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 k{(E~v!?*hy{yx?44^j~<& + + +]> + + +Window Rules + +&Lauri.Watts; &Lauri.Watts.mail; + + + Parts of this documentation was converted from the KDE UserBase KWin Rules page and updated by the &kde; Documentation team to Plasma 5.8. + + + + + +&FDLNotice; +2016-06-23 + Plasma 5.8 + +Here you can customize window settings specifically only for +some windows. + + +KDE +KControl +window settings +window placement +window size + + + +Window Specific Settings: Quick Start + +Here you can customize window settings specifically only for +some windows. + + +Please note that this configuration will not take effect if you +do not use &kwin; as your window manager. If you do use a different +window manager, please refer to its documentation for how to customize +window behavior. + + +Many of the settings you can configure here are those you can +configure on a global basis in the Window Behavior +&systemsettings; module, however some of them are even more detailed. + +They encompass geometry, placement, whether a window should be +kept above or below others, focus stealing prevention, and translucency +settings. + +You can access this module in two ways: from the titlebar of the +application you wish to configure, or from the &systemsettings;. If you +start it from within &systemsettings; you can use the +New... to create a window profile, and the +Detect Window Properties button on the resulting dialog to +partially fill in the required information for the application +you wish to configure. + +You can also at any time Modify... or +Delete any stored settings profile, and +reorder the list. Reordering the list using the Move Up +and Move Down buttons effects on how they are applied. + + + + + +Overview +&kwin; allows the end-user to define rules to alter an application's window attributes. + +For example, when an application is started, it can be forced to always run on Virtual Desktop 2. Or a defect in an application can be worked-around to force the window above others. + +Step-by-step examples are provided along with detailed information on using the &kwin; Rule Editor to specify Window Matching and Window Attributes. + + +Examples and Application Workaround +To see what's possible, detailed examples are provided which can also be used to model your own rules. + +A special page is to dedicated to address Application Workaround. + + +KWin Rule Editor +Invoking the KWin Rule Editor + + + + + + + + + + + + + +There are several ways to invoke the &kwin; Rule Editor. Below are two: + + +Right-click on the title-bar of any window, choose More ActionsWindow Manager Settings... and in the Configure window, select Window Rules or + + +System SettingsWindow BehaviorWindow Rules + +The main window is used to: + + +Affect rules with New..., Modify... and Delete +Share rules with others via Import and Export +Ensure desired rule evaluation using Move Up and Move Down + +Rule Evaluation +When an application starts (or the rules are modified), &kwin; evaluates the rules from the top of the list to the bottom. For all rules which match a window, the collective set of attributes are applied to the window, then the window is displayed. + +Should two or more matching rules enable the same attribute, the setting in the first rule in the list is used. + +You can tailor children windows for the application by placing the more restrictive rules first - see the Kopete and Kopete Chat Window example. + + + +Rule Editor + + + + + + + + + + + + + +The editor is composed of four tabs: + + +Window matching +Size & Position +Arrangement & Access +Appearance & Fixes + +As the name implies, Window matching is used to specify criteria to match one or more windows. The other three tabs are used to alter the attributes of the matching windows. + +Panels can also be affected. + +Window Matching +Each window rule has user specified Window Matching criteria. &kwin; uses the criteria to determine whether the rule is applicable for an application. + + +Window Attributes +Along with Window Matching criteria, each window rule has a set of Window Attributes. The attributes override the corresponding application's settings and are applied before the window is displayed by &kwin;. + + + + + +Window Matching + + + + + + + + + + + + + +The Window Matching tab is used to specify the criteria &kwin; uses to evaluate whether the rule is applicable for a given window. + +Zero (match any window) or more of the following may be specified: + + +Window class (application) - match the class. +Match whole window class - include matching the secondary class. + + +Window role - restrict the match to the function of the window (⪚ a main window, a chat window, &etc;) +Window types - restrict the match to the type of window: Normal Window, Dialog Window, &etc; +Window title - restrict the match to the title of the window. +Machine (hostname) - restrict the match to the host name associated with the window. + +While it's possible to manually enter the above information, the preferred method is to use the Detect Window Properties button. + +For each field, the following operators can be applied against the field value: + + +Unimportant - ignore the field. +Exact Match +Substring Match + +Both Exact Match and Substring Match implement case insensitive matching. For example, AB matches the string AB, ab, Ab and aB. + + +Regular Expression - Qt's regular expressions are implemented - see pattern matching using regular expressions. + +Detect Window Properties + + + + + + + + + + + + + +The Detect Window Properties function simplifies the process of entering the matching-criteria. + + +For the application you'd like to create a rule, start the application. +Next, in the Window matching tab, set the number of seconds of delay before the Detect Window Properties function starts. The default is zero seconds. +Click on Detect Window Properties and +When the mouse-cursor turns to cross-hairs, place it inside the application window (not the title bar) and left-click. +A new window is presented with information about the selected window. Select the desired fields: +Secondary class name - some applications have a secondary class name. This value can be used to restrict windows by this value. +Window role +Window type +Window title + + + +Click the OK button to back-fill the Window Matching criteria. + +By using a combination of the information, a rule can apply to an entire application (by Class) or a to a specific window Type within the Class - say a Toolbar. + + + +Window Attributes + + + + + + + + + + + + + +The attributes which can be set are grouped by function in three tabs: + + +Size & Position +Arrangement & Access +Appearance & Fixes + +Each attribute has a set of parameters which determines its disposition. + +Parameters +Each attribute, minimally, accepts one of the following parameters. Additional, attribute-specific arguments are listed within each attribute definition. + + + Do Not Affect + + Ensure a subsequent rule, which matches the window, does not affect the attribute. + + + Apply Initially + + Start the window with the attribute and allow it to be changed at run-time. + + + Remember + + Use the attribute setting as defined in the rule and if changed at run-time, save and use the new value instead. + + + Force + + The setting cannot be changed at run-time. + + + Apply Now, Force Temporarily + + Apply/Force the setting once and unset the attribute.The difference between the two is at run-time, Apply Now allows the attribute to be changed and Force Temporarily prohibits it to be altered until all affected windows exit. + + + +For Apply Now, if the rule has no other attributes set, the rule is deleted after evaluation whereas Force Temporarily, the rule is deleted after the last affected window terminates. + + +Attributes +The Detect Window Properties button back-fills attribute-specific values - for more information see Window Matching. For example the height and width values of the Size attribute is set to the height and width of the detected window. + +Yes/No arguments are used to toggle on or off attributes. Leniency with grammar helps one understand how a setting will be processed. For example, the attribute Skip taskbar, when set to No means do not skip the taskbar. In other words, show the window in the taskbar. + +Size & Position + + Position + + Position the window's upper left corner at the specified x,y coordinate. + + + +&kwin;'s origin, (0,0), is the upper left of the desktop. + + + Size + + The width and height of the window. + + + Maximized horizontally, Maximized vertically + + These attributes are used to toggle the maximum horizontal/minimum horizontal window attribute. + + + Desktop, Activity, Screen + + Place the window on the specified (Virtual) Desktop, Activity or Screen. Use All Desktops to place the window on all Virtual Desktops. + + + Fullscreen, Minimized, Shaded + + Toggle the Fullscreen, Minimize and Shading window attribute. For example, a window can be started Minimized or if it is started Minimized, it can be forced to not. + + + +Maximized attribute is emulated by using both Maximized horizontally and Maximized vertically or Initial placement with the Maximizing argument. + + + Initial placement + + Override the global window placement strategy with one of the following: + +Default - use the global window placement strategy. +No Placement - top-left corner. +Minimal Overlapping - place where no other window exists. +Maximized - start the window maximized. +Cascaded - staircase-by-title. +Centered - center of the desktop. +Random +In Top-Left Corner +Under Mouse +On Main Window - restrict placement of a child window to the boundaries of the parent window. + + + + + Ignore requested geometry + + Toggle whether to accept or ignore the window's requested geometry position. To avoid conflicts between the default placement strategy and the window's request, the placement strategy is ignored when the window's request is accepted. + + + Minimum size, Maximum size + + The minimum and maximum size allowed for the window. + + + Obey geometry restrictions + + Toggle whether to adhere to the window's requested aspect ratio or base increment.In order to understand this attribute, some background is required. Briefly, windows must request from the Window Manager, a base increment: the minimum number of height X width pixels per re-size request. Typically, it's 1x1. Other windows though, for example terminal emulators or editors, use fixed-fonts and request their base-increment according to the size of one character. + + + + +Arrangement & Access + + Keep above, Keep below + + Toggle whether to keep the window above/below all others. + + + Autogroup with identical + + Toggle the grouping (commonly known as tabbing) of windows. + + + Autogroup in foreground + + Toggle whether to make the window active when it is added to the current Autogroup. + + + Autogroup by ID + + Create a group via a user-defined ID. More than one rule can share the same ID to allow for seemingly unrelated windows to be grouped. + + + + Skip taskbar + + Toggle whether to display the window in the taskbar. + + + Skip pager + + Toggle whether to display the window in pager. + + + + + + + + + + + + + + + + + + Skip switcher + + Toggle whether to display the window in the ALT+TAB list. + + + Shortcut + + Assign a shortcut to the window. When Edit... is clicked, additional instructions are presented. + + + + +Appearance & Fixes + + No titlebar and frame + + Toggle whether to display the titlebar and frame around the window. + + + Titlebar color scheme + + Select a color scheme for the titlebar of the window. + + + Active/Inactive opacity + + When the window is active/inactive, set its opacity to the percentage specified. + + + +Active/Inactive opacity can only be affected when Desktop Effects are enabled. + + + Focus stealing prevention + + When a window wants focus, control on a scale (from None to Extreme) whether to honor the request and place above all other windows, or ignore its request (potentially leaving the window behind other windows): + +None - Always grant focus to the window. +Low +Normal +High +Extreme - The window's focus request is denied. Focus is only granted by explicitly requesting via the mousing. + + + + + +See Accept focus to make a window read-only - not accept any keyboard input. + + + Accept focus + + Toggle whether the window accepts keyboard input. Make the window read-only. + + + Ignore global shortcuts + + Toggle whether to ignore global shortcuts (as defined by System SettingsShortcuts and GesturesGlobal Shortcuts or by running kcmshell5 keys in konsole) while the window is active. + + + Closeable + + Toggle whether to display the Close button on the title bar. + + + +A terminal window may still be closed by the end user by ending the shell session however using Accept focus to disable keyboard input will make it more difficult to close the window. + + + Window type + + Change the window to another type and inherit the characteristics of that window: + +Normal Window +Dialog Window +Utility Window +Dock (panel) +Toolbar +Torn-Off Menu +Splash Screen +Desktop +Standalone Menubar +On Screen Display + + + + + +Use with care because unwanted results may be introduced. For example, a Splash Screen is a automatically closed by &kwin; when clicked. + + + Block compositing + + Toggle whether to disable compositing while the window exists. If compositing is enabled and the rule specifies to disable compositing, while any matching window exists, compositing will be disabled. Compositing is re-enabled when the last matching window terminates. + + + + + + +Examples +The first example details all the necessary steps to create the rules. In order to keep this page a manageable size, subsequent examples only list steps specific to the example. + +The Pager attribute refers to the Virtual Desktop Manager: + + + + + + + + + + + + +Pin a Window to a Desktop and set other Attributes +Pin &akregator; to Virtual Desktop 2. Additionally, start the application with a preferred size and position. For each attribute, use the Apply Initially parameter so it can be overridden at run-time. + +The &kwin; rule is created as follows: + + +Start &akregator; on desktop two, size and position it to suit: + + + + + + + + + + + +Right-click on the titlebar and select More ActionsWindow Manager Settings...: + + + + + + + + + + + +Select the Window Rules in the left column and click on New...: + + + + + + + + + + + +The Edit Window-Specific Settings window is displayed. Window matching is the default tab: + + + + + + + + + + + +Click Detect Window Properties with 0s delay the cursor immediately turns into cross-hairs. Click (anywhere) inside the &akregator; window (but not the title bar). The window criteria are presented. Match only by primary class name so leave the check boxes unchecked - for additional information see window matching: + + + + + + + + + + + +Clicking OK the previous window back-fills the results in the Window Matching tab. Enter a meaningful text in the Description field (which is displayed in the KWin Rule window): + + + + + + + + + + + +Enable the window attributes: Position, Size and Desktop. The initial values are set by Detect Window Properties and can be overridden: + + + + + + + + + + + +Clicking OK in the previous window returns to the main KWin Rules. The new rule with its description is listed: + + + + + + + + + + + +Click OK to close the window. +Done. + + +Application on all Desktops and Handle One Child Window Uniquely +Except for conversation windows, display &kopete; and its children windows on all desktops and skip the systray and pager. For children conversation windows, treat them as the parent window except show them in systray. + +For each attribute, use the Force parameter so it can not be overridden. + +In order to implement the above, two rules need to be created: + + +A rule for Kopete Chat and +A rule for &kopete; + +The Kopete Chat rule's matching-criteria is more restrictive than the Kopete rule as it needs to match a specific Window Role: the chat window. Due to rule evaluation processing, the Kopete Chat rule must precede the &kopete; rule in the KWin Rule list for Kopete. + +Kopete Chat Rule +Assuming a Kopete Chat window is open: + + +Use Detect Window Properties and select the Kopete Chat window. Check the Window role box to restrict the criteria to chat windows - for additional information see window matching: + + + + + + + + + + + +Clicking OK in the previous window back-fills the results in the Window Matching tab. Enter a meaningful text in the Description box: + + + + + + + + + + + +Enable the following attributes: + + + + + + + + + + + +Click through to complete entry of the rule. + +The Skip taskbar attribute is set to No to display the window in the taskbar which loosely translates to: no do not skip taskbar . + + +Kopete Rule +Assuming &kopete; is open: + + +Use Detect Window Properties and select the &kopete; window. Match only by primary class name so leave the check boxes unchecked - for additional information see window matching: + + + + + + + + + + + +Clicking OK in the previous window back-fills the results in the Window Matching tab. Enter a meaningful text in the Description box: + + + + + + + + + + + +Enable the following attributes: + + + + + + + + + + + +Click through to complete entry of the rule. + + +Kopete KWin Rule List +As mentioned, due to rule evaluation processing, the Kopete Chat rule must precede the &kopete; rule: + + + + + + + + + + + + + + +Suppress a Window from showing on Pager +KNotes currently does not allow for its notes to skip the pager however a rule easily solves this shortcoming. + +Assuming a sticky note' window is available: + + +Use Detect Window Properties and select any sticky note window. Match only by primary class name so leave the check boxes unchecked - for additional information see window matching: + + + + + + + + + + + +Clicking OK in the previous window back-fills the results in the Window Matching tab. Enter a meaningful text in the Description box: + + + + + + + + + + + +Enable the Skip Pager attribute with the Force parameter: + + + + + + + + + + + +Click through to complete entry of the rule. + + +Force a Window to the Top +To pop an active window to the top, set its Focus stealing prevention attribute to None, typically, in conjunction with the Force parameter: + + + + + + + + + + + + + +Multiple Rules per Application +Thunderbird has several different child windows. This example: + + +Pin Thunderbird's main window on Virtual Desktop 1 with a specific size and location on the desktop. +Allow the Thunderbird composer window to reside on any desktop and when activated, force focus and pop it to the top of all windows. +Pop the Thunderbird reminder to the top and do not give it focus so it isn't inadvertently dismissed. + +Each rule's matching criteria is sufficiently restrictive so their order within the main &kwin; window is not important to affect rule evaluation. + +Thunderbird - Main +Assuming the Thunderbird Main window is open, sized and position to suit: + + +Use Detect Window Properties and select the Thunderbird Main window. Check the Window role box to restrict the criteria to the main window - for additional information see window matching: + + + + + + + + + + + +Clicking OK in the previous window back-fills the results in the Window Matching tab. Enter a meaningful text in the Description box: + + + + + + + + + + + +Enable the following attributes: + + + + + + + + + + + +Click through to complete entry of the rule. + + +Thunderbird - Composer +Assuming a Thunderbird Composer window is open: + + +Use Detect Window Properties and select the Thunderbird Compose window. Check the Window role and Window type boxes to restrict the criteria to composition windows - for additional information see window matching: + + + + + + + + + + + +Clicking OK in the previous window back-fills the results in the Window Matching tab. Enter a meaningful text in the Description box: + + + + + + + + + + + +Enable the following attributes: + + + + + + + + + + + +Click through to complete entry of the rule. + + +Thunderbird - Reminder +Assuming a Thunderbird Reminder window is open: + + +Use Detect Window Properties and select the Thunderbird Reminder window. Check the Secondary class name and Window Type boxes to restrict the criteria to reminder windows - for additional information see window matching: + + + + + + + + + + + +Clicking OK in the previous window back-fills the results in the Window Matching tab. Enter a meaningful text in the Description box: + + + + + + + + + + + +Enable the following attributes: + + + + + + + + + + + +Click through to complete entry of the rule. + + + + + +Application Workarounds +Below are Workarounds for misbehaving applications. + +If you are unfamiliar with creating &kwin; Rules, see this detailed example to base your new rule. + +Full-screen Re-size Error +&Emacs; and gVim, when maximized (full-screen mode) and under certain conditions may encounter window re-sizing issues - see Emacs window resizes ... A &kwin; Rule will work-around the issue. + +Assuming an &Emacs; window is open: + + +Use Detect Window Properties and select the &Emacs; window. Match only by primary class name so leave the check boxes unchecked - for additional information see window matching + + + + + + + + + + + +Clicking OK in the previous window back-fills the results in the Window Matching tab. Enter a meaningful text in the Description text box: + + + + + + + + + + + +Ignore &Emacs;'s full-screen request by enabling the Obey geometry restrictions attribute, toggling it to off (No) to ignore and selecting the Force parameter: + + + + + + + + + + + +Click through to complete entry of the rule. + + + + + + +Credits and License + +Documentation Copyright see the UserBase + KWin Rules page history + +&underFDL; + +&documentation.index; + diff --git a/doc/windowspecific/knotes-attribute.png b/doc/windowspecific/knotes-attribute.png new file mode 100644 index 0000000000000000000000000000000000000000..eeaf3ae931da1663b5e7e1c1545118ab8a003f94 GIT binary patch literal 35453 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 literal 0 HcmV?d00001 diff --git a/doc/windowspecific/knotes-info.png b/doc/windowspecific/knotes-info.png new file mode 100644 index 0000000000000000000000000000000000000000..4769a1d9b243eefea0427e107a5dcdcc1bba8021 GIT binary patch literal 21435 zcmb4qWmH_v(k>btCO`-TcM=%fHMn~cU~qSLcMI`u9Qkz z4y~4H)|whvL{>$SBrrVk%W(2v*Lj;-DI!M!fl zk{1>L96P&5+WP=&Rn(XD+>vfU`|J?<)iGQXw#|3g(DB3FA4S!TqjX(JV9Hh+5Qcrp z8c_Xzlvd$SZkOanC0dtebju9W+i)>4v3c?{3Ae4C?P+^IUwe-;bD_~z3j*ehl5fz$ zGLln^>)hsPsnEhY;W%c!XIXNLNOY+*~AThKf?Fn_2FI~E9y`=%84$zn53xf#|UN~k>w1oDiKMxmFe zqe3VgR1+lzuBvi{rX;6q<`$TnNj@+5@aXJs^!fOHDj5n=FATl7b$<>W?1 zvgCc<>|#v{Oif8X$as9}Ntc%Jf3xsFbyXP}+c(6}{dIpkVLI`KJ}cS;dl(VMxM%CZtzc`fBgL3JyW}=*yi5+BtP}>+;BeaDo*288`>%nUqBGI&K_lLK3-*m?} zXx=!l)tjhI9cz{10~nbbizfGLYHJ~jqViF*r_fmUuT(?bTj3R<{XQYeB_ZYmb<9S) zCkUR~JDOcc?Cd7uzdus|+zLX6)=R|>H}IdT^4#we@P{o4X;M+((Ba)4-ryu9q744@ zF1P=BwD}g`RDO8NT9W4qfYqg9R&>n0<@-vE4(9n3{pUJzHJ3O#s|S<_<+*1(T)u}SUF2gls=tkE~GXlnm`Q4p!Ll*i7}SwSAO5@loyVq;F2wSN2#bYX?Tqwx{DP5j88| zOskiix!Kzr3>ZY#2-`-_S0=69r}_5hf7F$#7wyM#&r(mX1J^GCk-^a$y`bJG0wAsV zVNksvhRi<-8BQUjKQcNrLT$WLQMR;U_XTspE<29-!iGK)nIi_N%nApP6wBhTADPP#c+8Tok?UnqhbNkMU6_SK4DX<0jz^ zzmBynbelU`F0XWAEcEjLKD@Ic!_s&T5UzU&ghKr4tNeW?(#r6+OpTL+ZbSN4^xSP~ zbteBjEb-=u*0RMdt&{+ylE}AyB#2~mH|dqySC}~VT058!bGQL{4wigV18i}6|C-`H6W zu6$MlKbephdK-j?;WQbwR#jCsG&HodluhpE?}pbu-Yg+dl`CPu2?7EQzfyixA(6vg zRh{#mni(8@huVYv*DgPa<1=}7dVnSND{E`1w~_V4(rND?THK2+2!K1U4&+-Ii*;@B zYZqrS9>4If#8YwYqUNm&@^fsYn?2lD|__MxV`u@jJ$)R5?}OOAE@ z$R_>J;}a0B5snQ-d;R&3mv~5M)HYFcy`D_6KKP&x1(!;bivk-jjv@%?5;&UXKx<=! zf?_l%(9rVa1~^bU-@H>}MqmM$L4O896~RMQL-~N96i}d8W-Ch;0Z;&Qt`T#@KOO&m zU<&4+2mU^rjRVy;4fFr}ALMJke@8Qeh76pY7~EWS7O8iC^W1u6zr>gR`79l-b`1yd z@7IK-7W>2a$gEb6XF*wyho|k+*W$@+Q`)1hbpgi@ zFk&8S==N9x1KK^iu*e+L0e5Nzh67jgPXhRxRC3oUTIDWy(-ZDjDXfk?{vG1+UBMl> zyMN$fu^Z><6cJP@y4&0n@UA4Y&Utj68O$`v`R zm+zq`Fb_T(dU`4?qQ~>ndwsayULW*9i@%KJe!-BKPa#o19z>zsWDxpTsxGNUJ9uGYUi$1a|eXSr`g?($E(tO9{BUB^RRm6GnQN0 zZu0|dbDzu2w~}q8RZ5HqhD{n?vvuXOTC>xU@A%Cc)_L=$bn`0DTAPS?d8_tpD=vMo zehl^TIn-c9k3r8 zl-j01h@T}yXbwXxC_g006q6e*sU+3=MN2M=&H1?pIBT^Oex;FVCDIrzvC`tM-6%LY zV#-&3FTJ|b{JNJ9A94^*#1XT?+^kdF?>((+8am_6VV)x@$8SI4x62?^5`7&yV!He> zz0s!sXEnarjQP-(k5>J!o2i^dr-CKbsf^LE6q7pGadhwN2eb}rmDy@z?QkEkf^XEj z?(P+^Bf@E!Mpb0%9DC^ca?#qrd~9a*hLU6!7i<3_|vQZ8BbYF!WiX3t%s z0S>Eqs#6k?TX#p+M<{+@`|5PXyG2N{kZc;{Udzt73~R2O^*2P zCw|>?cjia4)~mXkOBL;CD`8oD#z9*H2nUdy4u!m$3scztE+n!7n@`>5V!SoKHN2*;^OWhfPOuOt|<88Lg4c8)Xa1yAuiK zxI<4%_nm}i1YTQnNT@quR!E-+a-?3RVZUc$D@X8?W5`66hh_2CI@YxzY~7ROvfU1+ zHF?ewr4=tV*uU3*E-Za&_Pph%#I3oY4v=s#B^~h@Mx5FZ(lwn=$nfWL+##??W-{hy za6TGw66%=4!iS+`HO|vKYLoD|X%a;hb)C&#VgSBaxzKm{zUJ~`5~Rf81kKI0Erli3 zdA)w+F(r+Ep9hADBQo-xS={ta7g=P4Vay6^IxulWCm8D08QG1&8C~abGSol&sLEEz zuWL$LCaQbW0=xHpnU*S5<8I}-KAu6XVr|!^lr#)rW`)*H1f!K9={2-mrdmJ7YtwX~X-37h20DFer;)5U* z3muTa+*SxhxPG1pX46Qe?AMF+7w}H*vH@8It_Dh}@(5}uUKIR;%f|o-5>wL8?E*!j zyP<=<^uRlfjt@H@=xBVY3(6-Z{CJ!cuLZ*^$&G+_=wX+D5@U(O$P4FA2H*(wNJ?v8 z0;6AzPM9PL;8oC$PUkB~Yu&U={kK=s7DGn9sXHy`73~{3ConRWDw-)C=~RAl3Y<(D z!`%=+)9k#aCHl)hdQMqAJ+4MoK^IM6$=4)UrOC|_rXRVL>#7`VL#HB6xYA&M3Qbvi zy3{Ea%a!P?ACDt%KW=qVfdxM|6m0<=^+O03SK*?!f3gXiENA=_q!*8)<0go-lA~Mo zjd#aDz_|zvUgNQ0;zZ9>>v(su_HBzrhwc({lA5TavXp0FHUc-lK-f$LIrIpDNThHn zKM(<}P9Q+z%z>`xF0FHPLNc9@b||gJLOd=V9PntZ(d>mKy6g59H#43*eLL~?z5)?L z@afk#&lz_rnM|TOl}v74VbQy=7R3=RwG>3>HhWI<7Y#aYaa^;9BUfEhX!~1@LfUo3 z&uQ{Hy)?jL!FRHDXaK*Lm?fs!*Oo{ggu) zfJda(rj4I^OX^WN_M7y438?ak`bW4FzIuk(H*V$F?o$N@i;vtrdZ^cp?Z}3<>e+zgp^{5V8<&L-!uaN4_WiO&?+jq6hU@FJ11Mxt(;t{ zezf8$-OHLCNlV+h#D$EZGZ_>K&1tUE-akPGr$kS8@k(@2-N6Ik)d|lvK0Z?;j)~bFXL7Oph#`=>2yx>hsf;PmY;riO zQhz*wO^87!Wjm$hzWWW~n%T=rDF>T&vzKtD>ji=OHpI6x4%Irz9E(wjFiYF@Ge`~g zO}lI_pjF;#wYm;o;PFWGG?^Yo{yY|bS~Lu?*&JQO{WDoIB(Qh!x5WHCmErQa8p!9I zA!wURmORdJL5X&VNlyx@cw~OT$tl+GN?S^Myi|jvxPADNA;3c(o4%0oO-2{z<(^?& z4bJ5jr%kF2F3TN_9d(Maf`IUcbpQZ8mA7Rn;OBwx*Xl5f97DZQ_+L zGGk7YCH)RX(Pgz7f7sZj#SMskQL7>%&AuY?+;i&wHH&@m4HBGbFn_`f5n1>CRfBPE zJ&U>4>UD{u{`c2ep?XEIDO15~K4*pj*TA~d{BGHAXWI3y_nEP$=t3d#1_XQ#T_)<3 zWILBp9~|+1=nE^Qp?jzEM#Lm}V{WvBNBUOgaTRbib~^EuNhOMd~5;F$Y8RcBO%k0(kD5cs1|A>r0DFP zH@tB;iCQOoG+U}2+9H=~k7!AZBhP~7K!;-Cq^s7TIh8{;E4i%8C<6KYD;~-=f7m*Z zyU%wmriR_`d%V(bkGTtU0k9Mu{_fjC0HLzyhX?`N7D%k>_3+ePkCIg9^sOn_*yt_Q zcZ(#6w_yZd7lZF=!-CYHWFgaWpVbakBkhKPgL%ewm%ZqG%46D&u&!~Yhltwg)bTNX zEe97K>rtz?^1xtG^Vd>fLFnkbjO`Y$<{WW>{9!MzAFXhs;hI+Ud(;+$PY8CM- zTRJ}53C_c8+PZ^*-KE~K*NUjDbUEA9ZL}rodnO~p`lsX~-7^}ew$fW-))KQvz^>C< zqDJGx(pJ(q@$xKPZ;w^`T_-ShF zDm=p|A!ba`6=KKouvLv17w^JE>hpSKDQqe9Se!Pnl~&&52OiT&Vs#`cZ)F=83roE$ zw&?yfd=fB{mi-!GXw463>CB$&Ja|>XwljDAu)&ex@i<28N7Zs(8mLM#mEM&2Cqc~8 zFFzsT<_BiXNp7}lW4>KTJff(M7=jTZ{oaw^B{>M$^m8BQx9cePNk3jOL9FqU0IRuL zm6vi9KOU~m{%1ylqb|8hb!y;`k#ymRkOZ#ETf#eQW1K%Gne;8nK3|`dpDequ5s>2d z^$-YrwWmo{?=-hOsDRG4E?{k zj{k5NfBnb59Y!ZhmDBP3dm4>qtIa;9O-7GDvr%AZ$0NIGaa`72couJ|W$+hv9V3JfFYIP3sTwH);O=~O!nsew+QE&zd`lIyjrJgC)H_k< zGi)Imk{|Jh6mj`=eI%8QWzx82k01f0%>I?TK~jyyq9oQc z0@@qj3KKSF|$} zQl#9cIv+$K>%x%3PS>LzhQK#AvqS^RV02=`%yw27#6@0mO>CjN0N)eR3ND0;AuolyA_z)7>9J$k$CNGq zv0Is7UYmk z%H6{ukBd|(MT{Iq)Cj)8x=Lc*FX*Dt<0vlJS_za`kO{ha{OL@?m?l;R7Jow}u9cScFOL(XV-|hAX z*wBqfc2jMfWA9XNSDyKd%c%aWl1Q`Jlx0^3|K{GJ^NHfd42Z0E=avZjn`DFJj3fFE zu`R|HmN7=E$WYk78q{RB{D9qQPkXF^OFWblL~4w643Rs%9%Y9Iy`G||CJRqB5FSqH(iBC9k+wLBDibnu*)6^m1)FF%0o6T}DI1?Cq(kj0Qn zp-k5Bflxt#X!+5oHDl=P{6VwrOX~C*q$pN!HGaTmXL9ZfwP`-GII zsv>j*g6Tirw;-{a#xJ{Rlo2)zXQD&$HHnK>0^C-#7-o%p`JF?ljcrQHoB;p<@kGfR zS;^0)?GcqK$n*)rFmrq*&aZ-2QMxysKe8-Lx4hKPoQg(X|#F_&u)&_<0@G`diAxD z6&jtreXBR^NIJrW*IRMJj=0=7)sPsf1tZ?(cCIYwcx}!zWzZnY8@bgQM)pGiY@zPc zRWQAp*c)~t!!$(>1sivX;PsKqw~dckdx$xFUFiX9!g#e8my!f{cp_v4-C1hPndRR6 zEbnUP`8&NWdX9!P$vjtc3uu>ux{l7f_i#Jg-^6e_FD}mcvKX2~nRoA}xx*6xJMa4l zPx*pfkW+LSPeX64>hwJ#VBx)=(`^#vaDxOjDiDL~s4D)1gz3gTT0)3V@eawk)>QIdS&a@7v->BGQC z|49~{V}G5n6+Am`0fi!DZ~MtkQSzFH8XHLpgdn7RITN>XFM|(DszBghMj6656AbVl z;&*+Zv!W4;>c8HIUp9y=VH`rTQSFKsi^S{YJ15xqxK$QwK4C9Wr|%T<3QBIoPUoKd zRNuVc?BA3!WZj`Dnxdg4hoGpC&K%0qd94XEETtc@aXX%XUaYyS8o+>=^?@PuLXGq> z0My`HK>Mw#k-Z;r?jGA#v(*+Qa0!`RwA<&i)5!Y*!y|+N5+97j!j;ey@*b51OCWElokJ9&ODLwGOIgxiM z(cms%RlY!x&`|IhVsoV^ISz&Dtl@HqAjhx6M7VAeBxLuO#w66>{^QdrKRAudm(9|h zA?+=k#d_G2p^}EGwRNs8mgbX&aH-r?!dE;JmPAWJ&A zA7i?ksrV3ZK75qk6xfwJQ}aDe6%zHedj=O!#;Y-}j3HpgsOfZ(h?2)q-^Ol7_l08T zxiqCsQs}9b@YNZH_K99F-Z@K~V1$Q-g6jt0bR!9P{zKe4xeKOsUD&F`rl-7hrx;}U z+<o4ihly^7Uf9Wy&v^#09VC1o=FKhG0*NN}qOGP1xAY3q7Nf{_eeo6n>aW(!a;-MkHy3OdAIc#fMpc*yaTV39cd* zlLFJJ(=*~6sak${uy)PR^(Fp&sn?kaDdvYTx+rfWNd)>>E6$L_I^#CP*tH(kCJAWZ%8cNh?ENyp=Q$H zM8)aGC#pPQxYPVaQIMb?5{Lh{=6_-;y+v+KH0&U|Z{M8%g#8^gK2Mm7Hx72?zp(?Y zI*SIRQR>nYAYRam&{Wr7{l*;Gf>lU&y8r^yx*$m|NH}fGLv;gPg?R(5-%528WjE15 z-$8@+s9f=T3KF|nJ?ef^h$}6a!yU*ETn?E}OI%KuI3LvV^(E{tYCHLw{vFvO$K+jG zR+SQDz)@=8Rt{P4Nliv$X#jzgh0BmOHPAxI6e)X7EZU*Irh*p;m;2mgRIW=M-7o_Nzuv^n9_Y*KK00@O-eFXuKHWVqENr*(HPzT5h z3}-VvhZvL9Nd)%bqTFL1qkO))lx}wv&>H3O!OeF}V3w_os(lOqY#1ltgwJ2%$fmN# zSrDt&97E7zWoS0HsQmYed-Dmq!;$5)lJ3OwXUI1I18QoTe}PUjLSRW5R=(ang%T(v z;k*-DnuhByc@Xcj?KUGVA+8GgHXn{|mJLWh0e_|FVpb{NMfRt8%aMIs}=0F&P8DyK&wfoPCwDTCI1K>V=@&3+A_X z@)h$RsoCVg)?QEmiB=$J zy$fROks#nSHqm>kXt4Dbf`d!8AlZhda8VEhdg0}P$GTosKp)toJ6_(|Opq;rPnGx} z$}A}@)jI{cPWuxPNim*X06{-EP5b+YAz0DhRJ6HgnB@(Iaix$hgx-lyMr==Y#Qz9M zBT$%kY(Y3e^C`=KSxzmRf&2nUk`15T+I-@ob^$Hb2$ksO>OQO-43@E~b?82TWX-6n zk8Q481b4wF*ei{%(a3)^gD2k!jeyx?j>fq!MxQ!;Gn3Qs&9eCdvnwRw2dxq*h+#ae zE|H`&0Zx(7hve=Y>LyQ@%T}AV>$lGOpEJUpgi8c6gZRB3cnu$pf`&oUC9}y?$Z{EI zNpM|hBQ~^meRnjE7?nInt{Hs zNuvU%!>@m*1V#F7rh)tIpbZ^mwH zC-rKBer;>qI{wDPrYX(tXCfyE5tW{T(qwlmR!2c#R{{a%$bw-`!F;<>=llS2(H@$t zfAVe$Y%LV0E8buhmyy%`iC?qU3d~yQcro*Tl5Gbcfsp-`oPD(D9sPO+fo2*lZiN1} zlx3se^yqz#SN<cOuLUzpZ=8zVRG^Vy-zQ`Cxv$*X#+?}5BxTrCj3q_ z`K;fXAJN|!S~1Z95zu!oW5*9U2v;Gr%g}rEBU(COxMhX!ybs!|trPApgI13r3+oL* zrNR9tt+K_sc`n(f-{PPnO+S}8hz~&da;af=x0NXuWGxYnwr+1cloIu$Fd!60vCu~H z4qf1r8>Z_&>#()6txV)&`u*!=Iz;gZ8!Icgknxi#zN$kE&V=6UR(MNjkZdZqf07)? zqG_qw`IWkRG#a5P&7PRf@oXkgTBUsb=st`-QBU=(i;r|C3CHhrXJ5=9a+^}nBa^Oe z?Vdg@#syuM&nIb~3n6{7>r4VA$iDaq?cV~5HIi1a!VSR&MdHc^eq=EUmhPD7&=Xrj ze`M(LCSEEDGmM2$=G;sEPJd^S_l^76nT)amnH|KM{j36)4_U#ELg)ukDQT@2Mdacd zr_LQg8D%oD!jekWwqTcsvLk{gDp)i1*X#gBzQN@R>9A5tRo3uwFH=|evfyi1txEK# zo8?E7U`FfmYMhz$R)4Sy1DA)LJawz5u%3y@;|O1gSE*L5t&0&(APNoxN>UdY@CD_3 z6``@;qYj5wy~+zqIRbprteN*mz%LLwLz>4!EBp-rSXzy!I9w1o7dQ-SLlZO60fu7m zS>1X|0ya46`Z(eVscIOIqT|nqMr?_~%p@U`5|cRAu0FWT9*2Nz{Un^3XtiDA^`z`3 zK1p~HQx{tECxfFi(h|&ZO7wP4hb`RgRY6cw5&?hxk2pXJWD>vF9H82DQ}{%|mTngY zO6x`Aj0Sc&5U+_!;#H~6xaovhyRDW`H$q+fL2S3!}Cmv^sL_2#uNDz)`J-gDw>TcDD9Ihv9#>gbZ1vZZ>Fee7O?L?^a!iF1(h%eN`N zfN9lt`_bTN%FM0s(-j0^gsrIoT;6a3?eNs)WOVN?aFD~H0o_Rda9%#R1X6zyDhqlM zgYj0XRB7@MB|T$ic%|u`u^Nm2I6nNkNPjR5sjE?{i$k}YOgxqYp%e4T8D4;E<@Q~U zM10>d$85C5W7>H*o@P|o^xMtyO{_Ufo~~;JJ>u+t?xd#1+JxZk(!Z{6wscM zu=uOK^4lH3neLj;m@j#cmA`hbsGSYObVA(;5VEjbCZznw-{boep8CpM+a_fz6^7XB z_#|0^#Zc0lqmlMb@w>sUtk*jPN$!n^N2oUz%Po^qopMnVqrVCu6+1MOcyDxb%<&jq zaAD_AoP3=H%)Oz#ZPXPfX+{4d=ECm0`4a;ErOPlIi2F78Ac;1x6`*#+~a&b1e^{5 z?<&xX@Ti3OANFfoe+;MR!Kp6@ZR*8zVi#`p46Dm5RhVP=TSk#N&)N1H*%X*npH!^*LwESkJ-ZCU#?bWx*U5kg+<}U#q=cnnmu-q_AWEe(X2MoL`(1VvVLS7R{dNP z6_wrY2yE%Xb}*GENr8=KnEO!xM~`cB(jn@QBmXMc`l|UR-N~_W_GYTsy#1KzjTY|- zGiHbt@zt7$BSIJ|We?7qWsZLSNXOP`@uYG6 zxkjt2^m+DZQPJLI6MUGd#9a{gIH%na=jJ%^eyuKpkC&fFV$d5cx8FT5nt`bO`GUX~ z8Dq7-@2pSN#%OxrAgbq%&Fhq#%qElQbtJbOBwNt@x~3a))rCLY{fqF6smmnrd2|m% z*Qz?hvHGZbKB^7wat7XGP8RS*G9!>^m&;2dX_yctAM)@DMQJXdRP#CdS+;K!tiIgg z(_!z>$NM-x)wR={cOxQAVCm6JXT<3D2@##ckx#vlEy)&dPkskQQ?&IheLsqfLA)Z=5 zCG@yDN+I=>F&i{o&;x4J92IK{>s6@o57SEy2jCD|o+u)x*9P`EAvf`lVcho^^QGH@H5p4q9JuS-j6G8 zuUH8ymG6EL&Ti-_AST^#Rw9t7l*@}FY3Lkws-rNYs@&czWOBFnct%;q|HPxGvozDl?ZdO#>tmu~CX(w9p3Bsj-H6VxhR)lleiSW{9Zzo0$+#{Q z-|uC-KAI$6S3t_P)I?Di3q%bFAAjjsoV8v9h+&GAUyp8pItuuFn^Y^EXqmpV^{ZbW$&FKq#*yg-mHH-_kYZv14 z+r@yy8SAO=r4I`{h6^$~Ze@Y_&>&etw$8u;*$8QDt^}oUn0|!Jb<)}%oyE9AP;T-i zFq=W^F#z7LK*>_rg@;c{zvA-~fH>zThV$DC2 zjU7bhPf5X?H92nXlxoQ@F!tV+z7H0FUO{|)24SU;JQux82R7O#s59E8SgD^NG@i+im#<>tcvft3OW(L znfI5hCf~qWg*|L={qHlv53*k=UG6`-1smt~eT=DH=W4!s$aN0w~3d_1_oi^WU?IJ!>E-`97D#|fwso|2mNu*QUVyFnoqzK+z z-Te}Y!J|zekin3pG=I6~WAT(zbshK;3}Ys@6L$IAT-fuOGwU@!!_nTCCWV%tin)3H z4s}iAV>fniqDJw;-W51yD8a5El^y?BtL$lpApFOZ`(zG$nv4O;=KPa4G04V@FtYdF zHc|+c&y?c>=u?DK@eD74Ae|p>Hf}?^?W7C0yg}HfQ7)Q7PB7TLQznOOG;R5XDaF-A z_7p>WUvhWQt*Pa+`bI;4YKkt&U4I$hz5T`(p~FBiVDSx2*kn7Q<|ZarHcA zE)gakHxe=rAt9Jn(h+$NCnU10h#MtvLAcOmrR)RdUHxJ&DeRKXJs;7 z(&^)q4$(5KBuNxBOI{4S)xX8;npF+6C{^INeDIJdy3R%NUYXD~1=B6Xx73y4?~>#T zG6gI(5+6&VXwnzC@9kVDSUWnR!Y1#DUc6AkGow~)2;3MW zqhiejQdE;RL@(QzZ4*7GkDRZuxO_KCJC3CFE}fR#9_*lqF;q&6NjuJDL6fgGwup%v zg%i`^Dm@{2^>S|`DkAIz7_rtXf!$!UP=coQx-AcEViV-q;Wo|4V)di7h`H5E_SV;x z7mN^UXkeV(YzQ{gXatMS5-YX#a7iwvLCdoizH;iADki!VTH(HAKrA|boGB^1D1mAl z<%f+1t91Y9H{AfcQ!g+z(4jv2BqwcSN zc#pI%Zuqg+^!n~Fr&|e#EezkQG~=m<)cNqK&|ZN6#A#!|?XkLZW=bD4KbFsog9>a? zB9>*vta=OkjTY12IDeFoCG#q}edrxeuUmy{s!a`wI2COI+C7t3GEMvZrKD+g3Ss>x zq!Mf35_AOuK_7u`NXmzShh~f=aH!T7qNUg(t#+wyiwY5msd%1tN&YueMkW|-zM?qH;f-IKu@2?$!>1Yy~Io*Fnb^ogtw6~ym!)}C+Oez0GPj`F`^6xq5V#>T>GFt*sUbJAH3hd{UzS-pf5AzC2Me<#s zo&wgc8ZWwD$gUpaq+sWUzF;XFXS9jgdf`>K<1!}#DcbiOgHx|5>fz?}9LP=;g1>uz zEdY=EM{eoA6RfU=#kzAaOjUTPNC0Al;=8p2^k39F-H(YC&XSR zQLrkYkG{}uQbawh`kCr=LKaia)zBaIwKD?X_XSb-!>x7%ziim+O%Tdw5X`3(29(`F zwl7p|Ywy=yyT>~2#vN`j?}+u#B}uwS5?5)z^_MtIh`dR?SoQt85m9{v9yUDx)5ydh zEk`}%*C(+!I9rz`j_Y(BIGHeUEaar(LsVH|W~-z)VUy|^!~NEWLJ~Is`bx??Xr?E0 zmSBpT+{vPmlVVwcrPk_06Cl(>yRmws5yKEvPHYp|j9&)X~0s#K&{!j1V zjA)ue3bXdt=Q|D@;*XDkP$7vS-UykGEv>cXf{G_jPPg0ATa1 z77A2ufh*+6xXG?xw^>p0a^}kzSBIlvwZy?rPZAW$kkf5i8e08J#S?iyuh&1VPJ-_+ zv|C?~=3nlwrW^MZ(LBmw^BN@IjNA3BsL@EqZtC2nBWvX-?q}6btoZw*|Vv)C{Rj#TC{UAG&0*CH*SOEsIT+|7bBY=(5Ipxq{s2H zH)9~A={bDMVUvDr5)N4);I81OI?9R@Z_=Ok+ocJz8P^}~GHoS+9^vg-YBt=GfV z*Ay;euQYT`17!VMKGy|0_B96WX402XEHr;xtREdpPts_=#xp$;%2b=3+Sb-RM2Dt| zCIP43w=d+x^I|0$RhfRd_O~kyZj_v+p;Q+YR-HygpC-w4d%VI3X7f#1@Y&2oQ541s zVMF&TK<4u2+@MOCA~|VN1*KsjlwQK(x|}H>C(-?uSc<}WwHiUXr>L7fz9t-H z$VC1VFtDB+G4GDSyP!S6oFiIa5k>#uV0h8{=~BCDhaP|rnu=woM;_!r&9LMWa{mpNP4B|5kdvJ+F85A}z4cJ3ntuWABPT@Rb1PF5M z2k--Dkb^D*OHrDJow-Yw;hP+!<~EiY{~fnj5Bf`XK3)^ zL~Bz*R!NcpXPd5!#iZ5D`ZpfvAbxC|z$w4mAKjq=v?yAh>|z@MuuzFQ#3*5ti8^$R zGgfmVxaEz;?R`|H!w<5;;^8^6ZrnVE-*YGrhkNBSD?Z0PM`Tt@q*N0nHQuhhl?Gvx z)9S=P?@B6-%*8I=za(8eZ0*J9_X5pyk*Q{1x905!o+6Z`yA&3f>BlOKHFW1&rb=c* zs#`^Y+zf+dfj9B<-CB)a$_Gr$!o-nb9gsyeR*#(N{j=@eMT;n{%nloh%a=KCNwFH! z`Nm4n=P?v*{w$#n*t+1r(Sb-ebRhOS9LKiBM!;80B)7ScF^|g|KJoVA(9IZqD#m@- zNSlV+6NZl;7Y^f0>G1E1xbWbQ``O4fjH4msCd(<Uz%1%^Gl=|0TD z*5T6Ou&5bmT!PJ%Gq@XYQ+mnV(A9HR>4(vw+Vr?4)4tul768I~Uu6{1pFHNaQaIwg zzv9rUO}a3NE&?z!OKm$edd$KQR5)EI^BjOU= zj#PRTD1=nw8Zaj3vs=zb+UiZ|;Y+Faz=LkU*gGOg2)}R#ezG0L2#KKg3s19jAlO4`P&t%5rz`g}{9i(c$hN%#ydjNUUM%xo%DzAE* z>|zSpff^ap#TW$6>Ud-mD$pdN5HLVNiHo(9)-7LC(B)6bAhgzBSYrQSgJ zy!LNHDc_Dr-aFVq6wLrG%r-Anh6R?wPg{ zZL6hEGg^1e7mVpLD*N)ziIYGMX!jHKYx&!LOx7E`-gO&@^&~(K?p&YRu;GMUD2H|a zfA__}z2NJ;k9+z*ja&y%Q(d=>ARq$LG_=qW@RNXa0tAH6K@d?&Xi5O-O=<)I3B7~T zrMJ-41duL#fC4Ip(2F7>y@;TI2=DOy^WOVs-kW)ECX<_-N$$y<+`aEQd#$yj*i{v@ zGV`u{K0VXPB8Kw^-Ba5I&lm(aZ^`Y>`o*=QMGTnAYrne7?R2spd@q;`78GVeS5Lq6 zXTA-gJovNC(cE^|T~J=}>{fxq;x&(hw=b9A=%yv)cAEy0*ztEtIZDHlJGFFkez<8_ zDXq7eS@r;DqMxin358o1@zPECua5>4KFaHKa^5Caycxf=^80=v)I*w)oHnBW3(ycNaigz{r}2xM ze`r)Q@cZMPeTy*h4gCSe+5#$QN{Xeph(>2{iCrC`Mb*Q2UJ{5@~nVM_t%xGh&x7^ zfYz1#bEgtbw@0$cGLl}Eu<6j_AhJnV7TG>`7W{zmJ9%Ywxwzf1r@V5u&g2(Rwzz2H zbhOXZub(2M0CXcsMDQ-tU&f%o9m0?cy*}aFd9uy?bd1;>55Nei-HwQE{kb`AnF}&H zxRs{Ht`3)JyOc&+SJE;!A^@j2uF3by58yAdxnQ-<2>+8pG!?7b5R!c$w`og{L#Z&r zfp<_+>d<_K!jY}#-0MrIJ(k@>r$`*&DCXfhT|^Ca(s%66Zcv3s`*sucjq^-{e$qMQ z9rL@I9_-9xg=gQf%&1pB#oiVbkunzIcJKmFfwLscMBn2*x@xQ{bae>9?><2_$8!q0 z{9{f*mjQnwS|_JHa4+ZYH$#XY)4t0Bb6qw*I|K_i5kSeKZeJVH;{o3D$g=%RPAJ>o zBv8ks0k&zg1m8qO29CjiE!CY=c<+Gh+7(g7fGzG#!y2rwsME-q{KBp|sM*7l`I5Mn z;6+Z^FEY7}9{mz!L!W?B#BH*1WHo8rM=4+vxo>Wspi~SuyO{0ACD&tx=UP2Xw7ht- zSRWh$JYUXH+@B2_BT5&9Z{$u^T3mIJwu$$7nc)t2pPtBT64oS-AVA8kG{JmnY2-1v z0Im%vzW}ybt%Aq0FXCjlDQJa>S)2Dv;t@~6G3F}Jrm)#wna4ICO(m*3ylYd2tE@!~ zgjlv=gHx$G_I36yedOTC{H!SwqNKmCHV4Yw2NpH$pGAGb-=8O%mL#==Q_ZXvyO2$( zKulmqJ{p^$+82NwufDTv3)eg&C=>Ft?Kx{XjttCFYAffa7fLSjC}WQ{j2ymG4%4q;XJQ6`!v2qkOX>#Od;YyZ5xP9GTa6;q zp;yHVuLX%UC7GrclUJw0ALyLeUd@|<+co9cC~vHboZDR0Q?NM_U+w(;3dS}N2p@Xi z$R|7cThoQM6&KTVPYZQ1hh5P1mcHogV(19c%|$X=%8P!m-wQKM;m$;baXP;%=FW;OgFBdnX8@ty-I=TS_EX~1jdUiN==^Xgt@f=>-C|B5KxgveVn zM3~9oW(dt^ulG$BTRIGM2M^f9pD!zT?K|nfLA;G^@0SH%%Dy;3uD-4khmt-ikEGeC zw`#o*KimW;s+0kseVeSG>3GQdmT73K9J(t~$y7Yi@gO-4P4uT^6kG0>WECEKpFkXH z)-}0eJ{y~IyW6taJ_u|!RCL)@osYgcJ7=`{E^?Y(v3*7T?yF5h#QHfZ^Vk~!eXh?G z{GyOB_ZJn=4HmUNxpfjBI^(%m9&>Nf0J}vxTAdaOE)C|JgV_(*hWVwSMd14I2R`(F zUlJ|Ci_1dn65Sb}&nFQLo}rQxb46p$rK1TJB+GNeTgd~W{W(l&NiV{z1ZuvK)3{vt z1!H*|yypR~yN5#lH?Hf>DoZ?{dmENX$rYlGg5?k-=xAmY*1E&r4NNiQdU5;M)u>De z)mn4{O|hhNTxuBuvajmCb(GY)SD8wl0Rlxz23~xD;b!mI6ZGtJ`ljp_Q@iH}`){S8 zruV{DewG0{i`e9Djb?zp`NCX;Hb=c55Z{%yW;DbL2tYjq=ijIQ{p6tfJ6@2h0*ZQk z1K#)$(wGcr(I-+->NFTJ`u>KHp+*oJa*u5!i*4HKk>cL8Z~N#>LQnLAoFPDEn}nx) zhT-9oPu0>0zDbzm^+t^9A^$3YxvJ|Z4p5AZjAc96DG(vI=ATxL^S|-?^F}J2AhGCH z8Tpi|;#>nW>kvqBhwN4T$2(nGz@c=UmS5bilHJFNOP&waAI`U?>ZYX~eOknXVc~!7#--Dg(O?wI!Bwur54RL3;3$oGdt1 zvW-0o-Xi5Nkg{}ZoBEkj*7`)rb^{H~=LGRhamH%ClxJ~{+52yfxWvmpD<39tDjEC5 zkOjjO-9I#T6$XDemav9mdxt$W(4C5 z#$~XS0|}i-<+>G*5Sig2{C|UbrXRNtk}4?imX?|Lg53-Y;y9OAdMCRROydI^9acqnl5iwBWho zHr?oc`moA<3Hy-kx^P*!Q{>r6&954~=Z>xvht;j8`k}2=758r##*d9pT^;t{NInvr zY8h22^0Nfqm)^s_rnb&FEgl~CXv449v}@0%>^|kSpL;TYQXsWc(Z$lSXcUgWYS@&? zJIYp@xptVtxEc>Z*DvSppncP!N6!fDHCy@4>DpX+hdhFz#j&55emfo-mE3mX-+8+0 zcwBG>FkPo47er@}5%SNA|9npe2_t}EI>;#<2AHtZ1B65Y;e-Fz;q-g||Bv6aF?J3J zQZKgSrHSTJk07?Ta|X7v-ZHgG&C6hhabVu#oEqM)_%q}Yj$n{=9$R%itg&}20wpdc z9@|t_Hf_B(b{Wxg?;B2Nf^<8x08Bp2QJo}Ko`^<`b%72E=D*4cW1OT#G$=p3A)Md{ zkg%Vq1n%aM#kym9)*WTh{b=y(VY;r*N~QUMCQktVi2~xx4?pGh{_R+H5(dX&xhti_%WZ&^j9(Wt1g_dT<$gGo+<_DC@z& zD#O}$Aj|vB2zJgtkqab>FoK3gFJW_d@6C6UIktKg2Zy!hmA!T$IK!)7+upfJ))H17 z<>751C-YP9roq-e@JOc86z${l6lw)zGaN#^TlY6h~5-l{hIw_v;YyhqPKMhwL2|+Ot=VrYLFwhHvExQ*dyi)A6#*G&1>u7LDi+b~ z9-MZc@A}x)t4bme(#{UxdwoZhqV?kSN(MY8JEn(S;Sim8xU6+_Z(k_M7(e__Pj9e> zpd7eM#@gsZWl@YU@#IEwoW9;4UhfuU?Nq==?ISwD+HeX!IDRFYQ~ia^3@tg0a8C=4)6W`%7+2LC72>H_W!y~i0PeO2E3Y~bl8sE5( zBl0BrgLZ%4+PWppjgz90f()FHWi7#8G0)Eoj@8X|3=iP2&Aekc+BW91X}94D$1+`F zt!Q*{)QR_okOZ1G=gioBgUOM<+8%ck=v)1}cvOPQd&>MD5%5I6rTo5;VjMk(q*kWQ z`SbBAA@4~Ctw}F2B8JoLCn0|2~=jp(>rzLj<(Uqwk?LiHL+-~+0=&nUmHo^NnJYRlEw78spdv44cYmE8LM zgl8tv=#&;GodyMTCQNen%rG82Z=4hzn8%+99c$XvqdDNY_>+D}) zI7;NTJI64Vk&*2j02&Lluof%HFSz3sBjxX_%S>e9zKR&-yKrcuAxDIkWxi^YKyC~A zqSr2O#_FrcmTc|X728vMQaCV=>t_(?+doqOkCb(q=>BJhJ9`@p@5dg~Xa&*K&80ZY Rfu@`gT}?xcI`zAc{{ya-C1d~q literal 0 HcmV?d00001 diff --git a/doc/windowspecific/kopete-attribute-2.png b/doc/windowspecific/kopete-attribute-2.png new file mode 100644 index 0000000000000000000000000000000000000000..50f8ff76e52a5303220662ec8c113049c1f64434 GIT binary patch literal 49387 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 us*kC{Jwa$5!rkB>nbeg_-PNlnU@9L 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

    };Qy)|~Jaozs$Ee2{pZfaq2$pY^s0_5)1- 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 literal 0 HcmV?d00001 diff --git a/doc/windowspecific/kwin-window-matching.png b/doc/windowspecific/kwin-window-matching.png new file mode 100644 index 0000000000000000000000000000000000000000..50162f9805d98583a6de57481ab3f624cd4d1b4e GIT binary patch literal 53362 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

    p6rZF!w#AA#uAJPyM$C`}(mS_H?`4nhV9057 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 z zf9eGuu*f_%XjqVjx>vsr9YgCe z9>AJ$0010@7=QT^7I+uqX&RKUK*x|62LJ%SvKY@Ms4dblB*q&|jPs#C006)_GBUIE zHiNJrBh#xG*QF(}z-o*qEG5PP0B|B=yzC_`@FvE0?k2`9(lNC537o5p7+(~yK{o&Z z030FRjPWv9knUBCrxMf_=@?px@tAmG8~^|~jNT@&w5$vk=ooqxT)&Z1 z^<8VS_sZp~*YqRkQ42bDnc4evoGr#UANm6T030MYFV76}w2VyD!U1FKbhb7xKfl^> z)|gD$wFkA3nYGtF&K6^wHRAvPz&V~id#-__GmaWNH3S_u9X}mM#@YUHwtr?E00000 zZ{P#~0DyB4<4x>}aR2}SfEc&oKH~rY006dZv*km700000tlIX^8spq&8~^|SsBfPo zx6l}8%{Tx608rniCk>P)i&>@u0002gyOD|G?TB#z0002}5+~ac;{X5v00wD{FA3bh zVAur!006}JgZxJf2mk;8P)~}V-xXk`Tw7E~suD~4CtSXTng8%Tr9Hsm8 zoaRvy+}!x_DxmvA$_Xwfq(V_{l^qKT%_9OzHWx6zmpZGWWN1aqXYeBHXaRC0zF@J*NT@AG9!;`$BbpjYL!;+{L`E zdtY!%d;~bSyTrW z){g|W#IW43!ti|^SXl$h4NF}=pD&t9(kCvT8{6L)7V9~#bHBII$~v~f2bMe7G7Eh9 z@7Xe!r4F{l2NruT-WZ>_pPEYS`lb>SXGk(^bJ<=SO+MJxL^f{wb6KF^S`4Wa0!Fk~m5FCr*)p=^--62=T!uKb8Ln XV0!?B$+Z*R00000NkvXXu0mjfXgkS-JI}8%s-GaLW_u%gC5W#iuV1Ymg!QCZj26uOc;O;iFL%#35 z{lDEkXZyh1p1##}tE;Q4yMA>iQcXn`<0bJ+004j?FDLa50C)z5|3AEV4sXHvz;F%# zG_%M{z18$VI9TfSyO(*5?9!&hku~%w!~BK2*$dg*w2X`lQw=MtKUx`!W7R{4xLG49 z7kbr`42^ap8`r)!3;5qIXIEisqaDybwhn06^fXUtY3at|mSsQRD(mLHb9=9jNwB2U zMXo6Hf=l@B!rkX4(%MVvPHy$EuUNDdm*rN{hK*QSNB&vW1h?N5v)`rg-2lOOQLLNL zx7lRmORc#a+9dDGPdmKkJ+V$naf?QpD0x4GePNxewZwN#L9xPGTEFU8_jv*PYKO2$-f~aZc`$)d7Mi8Iyw zL6umXZ(1Hc&}VvCr2MNw^B|wy|Hbv?Kpr6Y&ETRH&k`7nMlVlM0xNj zXtE^}pA==Rd=M)$VhqQu(lX$oKF{Y`<^6#~tmQ)FmL?Rer|1J@dpBR;kkm|fXm`c^ z2K$GOt?W0Ny0;qj%4RKtfhR(Ikz+wli~2n44+MBeXEj{wlWaY5H??PtuFpjzSI(Tj zXXJ?6Pctu+5R9vvcZ0|vDRrKP{)1%c0#~om3kf14-~}@|w1=8{+?HGG`?z)+Kyif9 z#QmI%P>JOgHoTD87$v^p)*0#q88Cmx%8vO3=?ayRf){S~s*r^V4qZ2_GjX>SN@{q? z|FU7Kdlq6GY&P=G_kk`1XL?^Z-YGRZQxhe&>`#&N=9WW7G*86DazpD&isfB3Y;93n z)1D6xgU%8Y@8~=ytaQwEAlF@>+1ASprS$dMHa27beGx;StxKw^t|yVO=jpQ}u|#jt z&lopfO~@=9`2=JWgwr0sJ<3c+o%SC{xupX6W~1dy9osr0JY4 z9>c1=@fE!Ge9){jk5~HDUq*N$N>h!dFi$r^qwzRn^Nx~&_YUWFX%=Wea6IJ z2aEGwxn#z993zZ=gg*CqteZk#fp5P4w0ie51FV{kb)*7KSg*Na0m!#|dQJ}i8Q&f?4ziOuA;-?!Fr5P7}h2rX`gFZX1Bis3dD6(T=d_0VNYN`!G{D2{2~zi=J8MC9~|%91&p%?y9Esn zTe_*^?dU$0`p~rw3@mI})f_qp+6}pyP#X)l9y$WCKrmivVFu4D;o4A0k~Ldn`;Uf> zv-F$OehNoQq737m+;22I$4CiO8y^Qw!1TO1F&{WjN7WO*_zh-ajD+mV9wt~oRih0rC7Yr#&?W}C`GOXFj78RrN&!V8+r1{ z1D7BzmC0_8WIcGg)@oDR^?W4oPBV6KYRSykhxxdrbo6VQ>ALGn(oy3^rPDlDTeGF9 z`5Wx3lAQ3k?AJ-w!!YWZpZ;2Pq(h~rhQ)|E0;QNXP=xxijI`FtKLC((kX80gvJp4_ z9?xSfQlvY6#ZIn6Q#paQlG`xK58yFi@k64#Ip0kXcrNg3wGR7`b0xoW%axAbo55$G z@Nne9xFZzagn)+uvRQ)5tE@&!<5nMRK=NX9bK&*l!xm2wKxqk%c8Qh#+OZK=T@ggb=%~S^?dXN(mhL&_&kFQY+N& z3I(1xqTIaFfnip=dzTwy<4JU*r<2=|Urt=DOP}zoQQwGiavYIWyW}KAydUBiytik=|Xek)awf>o!NZohEJ)yfT}Rm8FlQp;syCmN1gUC8?Fa zPZLI}Sga)@S`yAf+avi(5Mn5sHsv2b(NYZ^F|Ldet*OftE;}Rxpc{x(rKTdVz7QHK z$T-)8HPm@C4odk12;NDI?nJe#Z*8Z==`;sETo9RRj0T3s4KzD(J^CF$^zgj5jq9q% zj*=;J>vzxa2ik3I?9?3>X|Rl2uE_F@7+GjrUZ9aXs(0?Jt#9^V36cr=NjNODFXE5- z6dBIE-=FJV{w$iwz*4#sB}_`W1N(F#oN2stM)isQUwQ+o%T(3uR5&(4yg#7eH{^<~s=1L+7H6TX4o*Fgq|B< zw)sopPjX*hwdJ<4ZD?0EwY*`6CrX0D1RgtME&z-D&+A2dfqP7{#INRQ8`eW6Lypl% z1$u_3gZ3Qd&Rux9db%>?y9O=djuI!>k-K(=&U4L4mBPJ@uW0Iz78KK)O$87EKWme- zs6#|3^$O!-l+@OI@~ickPO!$bNytnAm@^({)f_ABI>_vFO3PD!($h1X?KN}*elEP9 zby|=is`t3)4jU=MTBl{2t+LgiGv&3M4-4F&s6L!RT}w0hei11YRb4MjYn>mA8c^-% z7s`7)M}viQujG^J(ZTP%8?_-T)i>#M!NM2IV5_d=T1xMSTmVUpsVTI?(_(n+DcXML z-1#cVp3%|92L15^xe^qFs)5j6-;Of2e|N3~zInqa4CL0|sz?|9SQ&*9LvI1$pi>H3 zkiJ9S731?hb?cq{-c$PQ=55itdCb1ec2jg|FqNfH+BDvU1kBGLLvs;EYfLEEDn`_T^!jBojMIY~Y1LlT!spF?FpnH` zuyX9{)XcOwA{Yx|5d=L;LA`FheM6aDt1a->q|J{uFljZ4BIpJIW??}!NmG@e+8;jo zJV8YCI7yW^*P;*iG|}yw-`znIl0+5VaSxF@0+l(8cZ_+%*_CUa@Pp!$u0~)&VIliK5?2IL^-!5eVCjaCjQ{Ij&ZmU{DP@25!t5l z`)GPYdMpV!d#n~o4rkLBE1JG*Y?wo-L0df<#rx810OLTk%1HGdiZ>4Ka~Jc%XvNX) z2Es=-GN%dG%(;4d3SoRv(F-S*!S4hw5=wJJt=dt*Cx(fLlxxco1W>>U0E0OizT{+t zS-EvJ*QrngK)YxUeO0d(?mi15R+m=0Zt1qUmXvp}kgYX1Tt4>!(3`_GgS9%zT1az? zvnGv-so}2pB3N^SI4uo`APx`_A;2%wjg}@OhZ#CiyS$-hF3YIX?b|i!YTN5AgNR2HFrV)t+SkC?{^%TEG{|Q+4hPc zliRbnIhwzSZcw&nwZ}8ZD5+T4K1pde(LhmcQx=%AeN4>`-8}g1>cqX~>RlVl)z?mL z9b&OkSos|hq~R9jcK#3d0Od&{agrY7V`V8Spg?C zORTRkzYxJkmU?f`#uC0xEzBRj1Z}VSz8Ucjqz`(yB|ro%3bwo!heGdRC4#d9~g@ z!ZJ6oIL`*hP<*-b3{VN<6R{|qN@q_Jl8h-aK%VK{D2~3XN0-gZId2r7 zk#s)0qlux-;?>(P7R3XGLK8T~cb+v6JPNb&gy5AZiDTpzPF?mtvsRqlQuj6vtNo5E z+p}z4dNNP0b<-Ocmf2!h<9Ir^Icuw@DD~n61t0Q^ zb;6(@!sst{DjFANT!KsxnWnXhJ)WSoquCMk@-YwfnslO=?{e1MY6WF8f+Y7bJnl+x zWXD8gL?uUn?QJ!vByy4Q$z$|jgmIUs*hZ&rO%~BCC0-b_rz6cgt30#cLZM7rBw^{V zTl#Q@nqy)1mZGsmIvaj;KW;egNP5`LGym{TQ8tZ9F308Ya|AzB%A*Ym4uQlSku!8);N;m-@)FEPa&k0e|V|2X<@^+xI=#2 zZKT$-Hh!r7vzbuk*NxwQw|{}>X4rY0_TCXLOTDXPOQ~xx5&j3yq@YRji*Gn6_;+#k zGF>q&;;&gTY6w_rw)%UYclccF6c98(y(f~q?#E3%qK<^8az~LkuMG!1_c}-#j@+V= zj`=Cd9_@`ts-u}+$7?lxHOu!TKM_9Ce(U+CR(rRqPsUi(Rxn(}*Cvua&M#BoW9~(t zqtH4AnPw`a%qpvFv32=NTy8Cqb-zl^FKfM`bvlV`JtoJ;ZvH9zNY&i56UiBK*96pn z!cfT9_bg8OE;=5AX(qfmtvMD)OWrP>Y)4exih9q1Fx%fdU#FH<=Ghe8(ztD57n7

    WkR(+H;Q?oR-3Y3U45S8H{`pGJaAJ+_F7w-%*KGIc z*7Gk;A3vH@I^danU-QlJWR6xwh4rWG^I^|yQR3#x1Q;kkK=jTV>B8u9z|jXG9(#yl zf`aNF3j1|~5{twcI1xAtG`yLG9IQf0B0hPPpCDAqkh9I4QWJ%wv-`UE?i~4cp2r=6 z85z?=t8QB>k=5x_f~BOg#<2j-nwr!>@i(FG!p284(9(LYMDDkyeUhE%*)lrKFC}tA zo-JVTSMMTs;XO+z?Iz_0fKOzUJW3IAm_XxX?KiQk^**qvGs@@h@zfRsD{=Q`T z<9gLnh>-2h*AN3h*UWZ`fZS;2IDLpDRkl#sTLl#i>|KaG`H7WjusB5(>vDSYCPZVQ zaGz$M6tm)vDEdAj!KUk@{1l?mnDJdw`!_M>K$Bm;q9w7v#`E71u6ivy?ZsqbwQ8{) zJsy|Ri*t(BK6!%dvZI?ScePEgk9S4eC89mVTkp!vMFd{BXcXMf@&02iW7xtCWJ^P8 znMe+gIqPpCT}jm^c5Fl z>q6kxVr6|zv*+TkDMMB%+1jg!cHjom)b)9r#h(}BAqwGjoTckyR6x7h``G@XG^QZ? zB@0xWM3m9Xl7-~(Fx|=GOw4mSPH}<+#?1;wrm;L4dL=(MZK_lXaT?2Fz8#pbMU2XJ zbK1Tkvy337ufyC)oD^BY=L!b5VpW+S+mN6?gKj>jsIz#?7CV|nh-Lu70HY6n=3s?U zjnR^{{F`cV4R@xm$n(5y;vp7GS{{UG0+f2;#aL^YA>smtt9pwe(uofM%mvkK1di6W z(({uoG)$$b#5(JS+RDtcKdjF~5F7+_U&#t?0=!>z)h3Rr3(inXP!i>cj+>AHASdq# zALU!*P+U&SoE9WtCw0ghD4D#9Z4-1J6238o3F#L0P3C1&6(~w%|G7bboJYZcjopj7(g-q(|1cH#X24x^=~z&d7}32=+tQl z;`EztX2FrS8`JhAax(awMlb9MJ4{$_;qGLC%{{~|pvw|Tag{)m{t)NmbYcAH-6sQN zlCzVOxwk3%Nvfn*Cwj0j&rTN7aKk^kxew` zRCCWV_}a}({pCm3vEVxrm_E1t*#zbMJ(p-~Ohi)ZpxG%wxzd(|p{OhIjGFCRrHA~z zruVxFP!=Ii5AZ&2E>?_^lB~|avRQX3nXV;D8)Qm`ZU|@Y%dL#| zbs z-M`}ebaucIkj4x2=_^v({TR5lpxo5yEjcj!&F7$i?R$`PHEZMZAI`e;mOegx^Z|LU zyK9q%J8^8^rRc4LdN>2mz(oJLK3 z+V!ujU8BkUr@j(xCAPi-0OVpw)1}TvO*)uZ;faPsVUdD%*o~eS0sz1+O#bX>zu_2< z000P)%OQS;MIpl40w*F|@RpDxlpq9vI1mxe0G`K4ME{4NSrbUXuX`!+L_*TwP~t}g?*Jha@=h~?vu8GGpWuae zRhu3XaF>Sjnlc1AxT668Kbn-!?uHG7HsCiq<-E+aD>Gtv@o8X3&LlKF>*4E z?{y`CALV3#G=}V@RG_y&8W1)s;mj*AB?ue*#4W<&6*wC!k=d}bG7L_ZR&9@IpLz&* z9aLEDxLE(s-|%{y<2{1*HTZq7$-EW{QEBYlE#lP&$fsnFBr)I2oiU6FwIDB)DBZ&J|c5$T){61-osYQ6>)6WZ=)Cfp`UaFRqsNw|3$A(kFy zkEY|LCQC|5;OB}4n+{>dt5P!1#bQy2nb}(t^wSp!BZ8e*+q&m^Am-wn8eRv_z%=Gb zm7Z=N3rcZ%3h=&~+e9sd(OF3xQV{!2y=}}Z#v^VHO91JFwpZQ8uEQC;71xZa6>%j( z-XTn4btDBGX`HG3=)zrp; zhvUC`5u*o78eR&qbK^wWE$A-JZD7i^3&d;23zwfp%jF~%mf=>F{4DD~=)w^a=fuPN z4Efki^Fs0W#yP?B2va9VENLE=3oJ}7E6>>B%4&DP%`_d#rlvYFb=S!FXW#>`<*vCU zVscT0^e@@{Lrc_qnfe^Uyr&i)E1TmG&R!~JEbHs2u9DE>lAsXG@FApE4-iMl4T$_v zK??==;$g%ntJDZJCWzWNn0_3KNQV!>atpt6kZnid9-JXieM3x4)_=Aq=z%eukZs~& z-l)iApP;4rF*FkS=RR>0Hpuxm%`{Dny@k7-XJFE}BAW~YP(xj=U1DHQFg+`WY8BSu zhv7;jovf`%#lQ@5-8`iD^kHKHjdE^$8gVt`m6?@=6*7wmO%L7owb@*;nANB~{U5fw zk}~Re*c_)WUSeoqkw8QXcB}bJ{RK0xpwQE6=StJ1x_WOA9-qBV<|XWWmQ4=)#@yY1 zo6X<5j#eNJ>HST1YDPFbxzPmA6Q&b=@qbEmWK@}SmW&Fi%O2J%>eUH&rVt2j)uO*+ zAU$Pz?d3RLbm+BE*6cJ*+*zT#MlH}qkNYY|`>=f!VLGnmP-{8M>fW^#Ds*MCcpyjq z#>~6v@lq0q)hhF}uxxc0RmA2~FJ$QZ)g!;WfWbF&2;1+d z$B$G=isH$><{B9cdY)*mDqKg*XKOc_Kq_5eHC~-kKbbCSG^5nUiX{N(!n zrmIGaxMR7bq{5(1Zh5{!c{`K+j5AWvrDy$$>8#yK7w!5K$X&QY@ZiU0 zmd5_{%;NF8?5sYmK#E_|Q>%|jDl0i2_6C6zKPSVIa9)jMbnx#EXzxO;1&j^$T^H=` zvr8P4pn~`p2~=&+e(gn_8jY7~qJMC|E2*lN(flL>i3$dT&)lallr@*aOLs-vTnwc~ ze!FiZ6G3(sLJ(Xq>T+YYS#>2;9q0{Q-r22Q*2NuH4(9OuaBAGe?^;7BIGqUOSRWnroiv&Hj`<-Qv`)FKCe6gtQS?ur?kckP#LLw-2*b;L{Si}t5t2gd z8g^rI=h-t+Kf2$x@qw77bgG$no?xupUaZ$!{EUx;;%8scDVv1unkWTG7SUgQJnPGJ z))Q2iy{Wphoupgb%?oJ8y8!epUqi8~J8rKQiLaa#W z=^DWv(Acyy-?d$R7@1uSsPS7bOhahl<=1YJ5=DHUl;awgYXT_Yo-%Ow5CX+33 zHZfT7LzNOLhmmvl=eh*7)glS{ieO z>7P@0qK`O+oguPNA|W+@0;^%=IE-iCuDi|oOM;6r7Cry&bcS5`+E{_nlFzy06;av( z(NbpqhXpQwD78vvl2V{PM$V*fyilrAjDL-@JPHLL=2~Of@!hjV0NVDKzHO%w67f$o zUO|9VQP;lwl|bY)c@1kiS^ja}TMV%9!h6y~5 zp)wkEY?k|fTfN|WZ0~pOFSLIc=8-h{z4#L`e8_hH{uMA2;(7p4DyO4dht%~~OwLe$?oCiNZ67rn#$qT(&(=uhNit%s-F@YQD+tS%%I4)mi_ z%jZg=rI8v#1EG1fST3w&NK-SNKEJX1`B|4YH}*#L`)vbj(Rtzmac$?|uR z8vgEg4cb;C-uA#x^cpq4XhBV-tHRFnWPTqZ$etw0DGu(}657>XAwNkjidV`I6$%;* zFA8tlNv*;ndQm5sc+spZvm<>@TFs3aGT^KZCM{Rx-$K7)3L>~p zyL+)#_7ds6jKEgRDcy^;;^STXLeUAH#FYn9bDtg#`cIg)?#9mfsb3wX)n&i*7IdEH ziX0;Z8%YCarAjXx@cRl!y~?tZ987RQ-J)m~b6ClTOaoQca8KgaggyWONizp$qpKI_ zBk@0@Q#2tkcGL6Q>GS-K&TaCb!Nx;#7BA4=x}ub+LH@I2@5}Js7LJOXi{qjR`w)WQ zRm*Wl1@^P(8 zpdvc?!tEiwg`io(QqJ>kp2Kf~!aGd-NnYm`Tt( zY_p>fc4MQkZ*M1=cXUc?Vle|urIQsimGzq!Ua1jRCJAY{3`9nWMxI6fkZzGK%}gx& zL4RzeInW*gS$#Nl&Ns?KTbo=RxguPYqf+Y(-5hMjV|O*G>1dO11I@mbOuQ99!%r${ z*W*Z_8}o|WH1GOenMP;HAt0aB*xiOcCtTqkTpUk*W37^ImRpOmi+q(t)r26<-k)=& zJgX2`U_XI&<_@S7S4^!=nnkoxAIXnvM9D}8OsI~!#n>*UGl{D$sO)fcULU074o@`3 zv>DllT5wivm|e17;r+)>(8A8nG%C`uAVicgM<1THUh|2mc?khbex)*oOJu2g=J%YJ zk_bumr|j!-(?kn6SiAxZpXbp)It|Nv*wM0|;V|nAB9sd%pqIlhe!lXKHJ==SJollC z6twEMP1Xqte*E?(lM|R`NsnUSWMk~?dqeOM@>}FPnqz{}^936H%q`Y1Tt@ualz8F} zoiaV=;z@&b@%KH0Anf@e_n&pKn>K>)274EZv4-3Ah{-TP2;SXq!IQ%@;E?^aPZro7 zBZe1TQQPlcmzH24#3JxjU10c`(TXETQ1b74^8wv^+@xw+SA;>#xngO7I{pI*hBIX$ zkxp_sQ>fsDHKJ5+;(H|Ioo}M`L3oHry%XM|m~^Qg?D^@#58FqNTcUriMK8B^-k%q0 z%)Uq4J>BsvyM`~Fv8-%aEM*zV@GkibTTe`gw}5)M9#W{tSF{9S(s& zjIHhq#ck5suU>*hspuK;W+&*+j)_336nyGYAI^>V3N@5RspdKBh2^g?A5W-P7$}>* z8d%C*;e12&-A+SnRr|w4wfDO7ZND^33Tbfy^o27{G(7H$IAM~OJzwMZswC&-y5Kp{ z0r~o7a}$W+ar1W2<&|g2OrN6u*|{{HvaU>pecLt1S-`H?d{p~Ki4w9r$xb{Qd!djz zebapXDeTZjRM=lP+=b&uG;AJM`dYbJVswRHczo#g>G%fb(mjd?Y>T%0YJJ?$y zEh2GFe#;mL#rC~`l0Z+rEF!pbR%~?6FWjirBQL-?5oQ>-j+T(8BNQ)wZx8yc5 zP3I9m*%aBusapd>E!eIjjX!#xbXO|4zo@*-wg^l%<6t7laNKKf;mPtNKjaQx%?<%d_B=*0ZnbnH#n6vTt^iMjpkEtuv`A824>(MBvL zxUZTbBhw7kLV&c-8aeK0!XQ4~LVdpsj>f%JFkZ~FH9LSCwt7XRqvH8C>$)xS4)^it zEb}W<*%1i)1@Pg??}H%@9s#yy85A_dpqk^&FFvN*Y%@)a7E z$G5&>n?$;* zBg~npY!>{5IlBwnNDHa6YcY)Yd+jWqm}CB) zx*x8GmZaZMrQMvo>Q7FmvEh(~VtC^L&3!2Ce8cdB?xrj!wE(#@uZ*jqJMk(`ytAxz ze3Ky@*hxgEZc?HINpsH4z5*vl`~^QMEB*`ocxwCy{5Y_Gx<270S3Gds1d?dp7u83% zQ?yRAfzks2Bu_njM(Ym!7|zHB+Wbfm3vcO4WijY(MMjf$6NLjoZEyYqGm3iteA2!u}TUEx#7}*12@m%AQhakIFJAfj#g2^0USj52>({2QQ#QX(^wGTc$EPBw6ElI zo_(#dcnF80A}kV>IK81C##)v8$EuCC3+zXO)|)e(VrTj8%=H?5zBC~SgheZbxs-8wti)lb+Y;L*|PA@ev`>Dm=C$B~!?NBZ!=)B-F95rgbA z)_W6MOyy0Y!dDQk^^-&z+xjNi+|b?C^R@NvJmv6m)7z+&`SiHW-G_^en#r@+jGFCF zl9!8RhIN86)=>~%vX;+-_T>uWt^T7q&uIi!U~saDEtbXlLQ1G*&kqmZrBCvvV?<36 zVp~_IUd#9BFQU_!l}$w)`-aAf4UQ}Q4i}c{jr=dMGB8Lcx3-vc3b(5~@2@<3XR?}X zV{0-j;1dnk`u4$m3!C&_{@w4TN>>e=@p_H9I)87chrIrrA!!KFwlN{O-$kTa0|&D8 zNFbIdPEOEKuaBrn4CJP+#*~bQ5G*+&2rD>EGx6sdu_C|&pFY&EwFXSwkiml=>9U+D#-(y7E zMgIG{sT#I|jCuHr*&@=OURSr@8ZYwK<>G1vk!fx>2JKadVHf9Yn+NXdiH81{#n+Dy ziWOlk$Nuasj=bqL8|P6qt8hz}s+My~mkZj+*zTSuFvJ<{C)%imdc#5ykvnGE?@ndO zgBj6F+60YN-rilD!vg38SeF@Ig9D#q4pXSsOf;vyZGMt_fbd zy46|zgvM5;uuZR$^TjG{n?*OhZ?=52hn7TI)@|v}pmV#T?6PiifVBRqu-nBPcl-PC zjT^#A{Vk8xqEh)BvST49f>~fuV1?wY@#!rdT+IT#YXo2bw<~-Kg;d@#%?Ztmh|Rrp_rJ zw1|p>{A4OejTgIg*jWyi(`S3=TRbjY8+HnYNydkbh}?ypvo@FhI(b|#GMNbU+FPN| z@;==%?Si~Hz00S*ZJ`K?l3iwq|7P$6aaSj1%~0Iy+QD^{L<8C^t^MB)M-u!0bU07G zNBYSP!yS&{?N_@AD7)RH>uM|D>!&d1JNqBmwI)EFmvg}opC1{uVo)zMK(ZV<1W`6V z?JZF3qEe1k2LMQy{||T_{s2Jo{{Y;dS|T7(PdGj~w@-xJ2nxJbUe+c7BzX3B!9D@@ z_8=xaxbOmS6#u_~dQ$rTg7YB*PlV;zV9Td0!4r<(~6Z>3QLi@Wg`D8Tby1kO6d#0B>PyJ&{Pvg6AO;4K)myKuoy*#~A*y`g z{msVAjh|>p1Ikm6CE`;L(0aWgnps@(L?*t{%Lunr)GTM|0co$N)ZV` zg*(n~c!(RKEuu-cX<`LZ4x1-6cu(8cQ&K_&Ez!_Og9g5VeT4g6O3-+eAY(98JXyf!v{UkxIqQ;Elz?w( zQ43e0;S^{UZMR-c&)q`zrQDK2<(?-Bm%mSFJ-zbJlU`KGvuqj3)4FHP8x6@@UqPWj z-RJa_mL2{+Y|#CCd^ZSc|Fr*noElXZjcuU6StxEy^>3uEiJEkDS=*Hih6OQ0ruVB`UnZvs^=3!M8x|z^AUmVEO*lWthY*p{oErl0%)9! z9|T4o+gBTBg6`dC%Jfr}7b!6tOmir5wu%L(i&U}&9mVSGb<}Ihd7M@zt}M47%4h`` zy2tkuEPnVTK;p6mslaN#D8V@W5zKn^6Mh#*71g+7s}j^>!DH<7;oe7oYy&lUA8O}i z;xfLvH8r{&HuE?p8k#K+>Qt0@ee9#668%$=$?G0B9qnvqPOo$|`xvxaZ6OHsP#sv# z)qo{hqztNFoU#R-O@WuZdG_WaHHzw3>Bi>x+BiT&pDQ zx_oEkUtOv#C;qc{x<*Dh$zd;MK6lAj*dN+uHIi{$pZ2PLe{P!9xZO@U>D}w$8NV{L z{mHL+G?He*vnEa}oYUH6%kM97zsN(Dnp5m&4DLskwX~&p$Kn=@+MGTXT6)wL%s8?% zDGNnbngnnDaonFOOPelT9Q)FsFg=F^)El2fkPK5Cs#66wC7o`w8SBRiVjK=Nov+kK z{oXh0HZg8CtLtmh++6)`gkk{Or0W>n`_^}sxmV}8z7cOCQIqNv&~mwFKkK}5CMM=E zrE4VKcnP*8I?}DdB0bxmUL}GN1(WsP$WMBNLgMFtRN*?#5AbN0IJL$S^QmtZN3_Ms z=O!|T$2$I^z$#t+n&;$z^QGb9p3UoErQ}r8gmzI-Hna@~)7<{zc;(g^9D~RJ~jDT|Dodk)) zV*~>#VO+GnkA1`VtF@n*#25pZe%Jwl7r9t@d!6#n7zyP*Cmw7!XBy zzsMucEC4z{HQcYyC=WR7W1+9PYwg)TxJ7dc92G^BaI2=y2P)pYuDXn?vfTIRFm_bU z;_R-=2!>&S?ZiQsda4X)(NSf`oq4<-5JH{V78s=gDY0V8DD14HN0R`noTY4kp{Ais z?|PHIzvIL;@o=uLztG6A*=_o`CVyYS(dXpHm=XGrQRSTc`Qrz%sUSHUo&9H$mL?$D zlIydhmaW{;EO!E$hT|poK{y1jQ7~Oc51w@$&T05f!j&yVmbNhKYDXepO(4(e>LBC| z`$eaeLx(WiDTV^q-uJo3r?DOA)+g+}ASSi>KdJA6sI| z1yTsQX%g^AlDTXIyOQk<^m&@xc4TAXr5-b3~+XNsZPE?QrsC#CArrL?Bq}2xO zCZ7WtqAw4kiOeg>-2nQw4iwHCCrXP%6uAMwi9o$Ax!M<~Y2?%zkat}ro_=zwiHnog zqb&?O(PZ5a{h6dE6q84~~6?AFMn20%;h+6p{Jq?^Enwy(vnWV zYt!=3+ozVV&M%`?s3yRhAt7z`Yx^^lWWhF3s)qnp>Af;oW&YA+8K^>xBVK9m#Ce4WVCM9|Im#_)LWrB{r41FQq57xb_ zC^tKQ>DQM;)3$9nthoR!Ocf>R5N%2)=*Jm(uG0x%6ID zt&KYd@Z`lz?3e4?Rub}1j$bdQ%=v*11}ok%ws`G3BBD2y&bNi)~WSS1Bp1Cg0=TX~!En_!+Z@rDH9= z)2jm-B#+IP22%T`f;jj>{wI0YyX^WD_BYK!UK`0j7R6YD%#ywjPdNt3S3xFMM^DB# z4TZptoV5=eIF4J#dyieAmmprYghzDQLgyud}QcNdz?q@6#-^nd2+pdke<4c-~0uM~kV|TsHh&txO2QgrcpSb25oV*~i7jo4-dYq+AYHePK_9+V^ ztj5ixbYSbzN3&tjyQMj~1T0cP(gcgVa=>w;Ms=?J*K1+?>zeM)t45e?~F# zrKPg^%EbN3`P#ZzHiq094WI2il}REQ(b5fs0KEq-A;>=$2QfVZ`XYb~WUKr2NEbNH8l)~s0q}Q|fP45U+2TO*iL`_GY1g!*0jm{{ zWyLarJB(UwdV5t9<~q{*dwcH`KNGt@xwW78@EEOTpU-)>q7_I{L&&+<4J96^Zi<<`NI4${>9eSPhW3;-?V zOXEBV&F~Vqu4bWfhVW|8CdaNFrwu7Y_cPqgp9)+`c!8gfpDyqjcq;$g62E;54>I8e zzI8nnxcu;9h6WUep~QhAofz?CFb~8wyb1sRHM7nE1C-MBxAE40ww9 z0U>k9@*`59Z+}VUIRBAA&dncq3lD_h1r+m-G@z_dU@-Ahi#S|^|6IBLKYUeGox_2_ zFP?}g!7K*#5+E^nxC|cy1inutK(PJ}2q+*biXab#NBd8#4S;o0|4miDyDJAr3E*W7 z&xMFc?kxQ87tj*szoMi3y*^FV(~W;d7h#$J@%UFg6#rcR9jWd!c@-6vxBtz)ww4=s zEBTk@zb5rmPk|fhKW$Iy`JcT1ReNXXU!8~-!WlYt`-A>Y7kq}>C1rsE{|=Wd0rHm% zd{Sipibr|%|C~Vrpa8CX+}|mCGS#O(PZI_i{YO(4K%%wj{aLTQ3q2tZh>a#dB}dQ^ z{vu_y_wj15n{hGJ$++F9!6Il?MU1t6BRJ8Ai(y3&dD(aNJyX}w}F<}O`Tw5A6z)zlGj1rij z2LZU=Lx3Ik=c*-IY%3~RcLFCxaJ*Y@)@sy%aL#Kj0h>4Q%1}o%%YP_GI4}w*YkG2` zUT=4J_c8?nKXK~MKoPf`-1p;*Lbq1gjG)xp2Z1&tmL3OodIFZm<|Dtlu`L;zlv~F5 z4^RzqlP6}p4#DpP7=5qN4&Y%lV?R3l9R9s3e*VggbeITudk!}|co+ry)%{E&0J8JH z2zwKFD8I+;zfwsHk)^Dqne4Plb}bV__APr0S%-`*WNQ<$-3&#@zVFLquM`d0&B$JM z!% jNv(>&*%Gmf3N@lc^v^<~)17(CwFg2=!&IU(+OcBGrmug)~ii8SZh7Qy=0DJIX6}y@jKkWVl~7GKH<1 zm>)|&_JJ_ltu?rJFM$*n*WGQ!FJqr4ai^{i@y2DOdV*j9JX1rVZcBW^^M^<+W!X@n`#|U_&N-@U7{sr4@#91)w6#exOS}{i4ZrL% zS2fpNn=(=zKST7PoVwd)T2QZLhHTIK9GhA(i` zWQ!qDl4|ck1s)>-v)TT#_BTR~opV^{rBZYFUZejX^g?Y+=QkW-v#~xsDf& z;nVM`^u5hytw{RhZrBEXUIv~@%SkFBBSq}1fSWNMtwRNq=ahfE4Ci(%)axCoJ9){! zto{jku-K))G+%7~dg{H2n`WgxNtqcoHD!xgll=0ma*Rn9wS_|u)z@i$(hdpMV*LeP z4+w&??KEFt)*3CNiGW6!)vsqcZ-a1dfS*h8&sw%?55i&MixHvDCQma_wq@H;q66TQ zN2sL%>=>%n{i}mxT%YMm56WxyTepgUQSs2vW|>?MGZ{%>N)0W(Q0^zCw`iHFCwgvT zZ+q;c&zq@eb#(2WC~G7}u-0<(x%E$MSiIA>SKVi>-O2aRqSR0p($OIhud(tcrog!3 zkq`D`24O2-b(OzE?P)^V@2n#euOirHG|HZvlLswx_kma8_8E5140|s<#swub7xNwY z9oAxsb((URm$A!f-RrO9)-g_$Mtk= z6;VyviH6L`KCxOrtHh+t%gnp4B3|LMnnj&<2D9~y>N;-@#9Qmq4wI<`vZWH@?hopu z^c%4h#hoy8vf(z2+87ASxAJ+T(T4b{?LN%i!Ui`P>aeQlli=dyFsr}S zKFk*v9LevPRLjsi5$wPB8fmcH%T6Jsb_(8RY`@d+9MFW3X6=NBV8;qK5}EI+0hiVG zc0;b^P*MwU)Pw2Qu`CvW&-wasDV?(lHOs&@!|_2%cFJ;Nrk0Ja<&{oJMd&XS(R^Xm z6V|0WZtzpPBkYR|gYnaIy3aMn%ViuJ!=Std+of%`saKGK&sK(tFB{3N(fquzGbB8> zJb##90SLgsRz**RsKL}u;fo^G7$NEDFyED_`ewgI&Uj)~$-5iDXS4K%t9_+PQiQ3HfvDigocMPl#FO<( zWw048_8-cPg=d(Il3o!~^pIt(mu9}R^l3bXH&_NJZP!`r^vp^u;aZJgMpJs_J0DY* z)yp!j>J-@DNvYsT4%>7uK?>PELzBZTjO|LYUr~Py)k%dL3#wV|ta6X3ck8TT zA11vFlEal!QrY7nijb*>gV%b?)E8e}HKNBTps~iA;YwVyO}~aGL>VK_>iJ@hh1;ye z6~1WguG)D5gcj^nl(qI|Y*WsYpEp#|l^ro>n4YJ#nMRz(LD}CR@#xs&-X4)*q5Gn( zk-t-i(}UE?maXe~jS;!f19~a^F0?bBAkR@@v(h_zb;nH;f`Xld-Al>gG4-iHwvs zE$U9LR&16s)c6fr2zNQJSN!f_JLYbHphnrEftpj}8`#>SjGEWUeeq^zl)r~Q5luMxxN#BS2Q=H&`>P*1G1iD24zgZE z#`0^p@m^%yRT1ti0Y8Kb`F*l;h{mKrasS&&hhE7j=l6`ot5H8eS-66?86dHAM;9^d%iUJfsdEnnXleFR!#9!ph33{6_*I{u$ZiiN?OzjuQiDbQNcGP!1Kr0` zoBSv%OXgzdwRu66<&1X@w2!K}kqI&4bGG?^oaGX_TS6$*14R5Kf%a*58P+4!91LyD^&2wZ5g0!Cb8!NZl!HDFXpy3^1|zIB9fBUN-> zAtLE~<|@QHn!H`uV;z43$YF5!OlBF)t6Sg*OY*sA0BvZ0CWkQT|Np$);^ zY7C39Ms_^)uAc(-+@1|;EN&=FHMf?ajxQ*mJ4pE+y$(p$%d_ zMV{#d)o5#)Yjgucuv#AZ+;OsuL%?%>ihkb9<(C>)J3}WO&GA{zzkI!ps7v6WZiyk! z#}D#q_a#TE!sKV7zDNrCRKA57&eUC-_|jF!W4pGzL?wgkzTe-FqEl1>lP$2UZm`^Z z7WZPYViv_Uy8O#r2)A9<+GbQ#8e8?2TRuEUFJxnVb~V;i+a z*Q2SoQIh=mF&1N+lg*CVhMEiATasI*7r z=>msg}4=YvXfH~*Ua z(>NfxXP%Qm<>sD;yyudK2SC(#w1I!^V_cDWZ3Im9XZsK~u5u@^mHuYYxxkki7}jZo za8BzjzfpgKo0o=jZ&4ql@OWK#2(V9xmk)O8J*0tthzIAv9+>x^G7cj!+tqKKKK66E za44>;E;Bswvh-=3m{D=%w1j@4R#S7_4^eyszw#WR^NnI>LoTOnCC}w5U_|1Q+`1wq zHLCLai(EW-uDC`UAeVt~FB6bKm4b{=yvXwGJR!eW5&Z0e0H8kg1!#aodtUw2;J*@Q z7H8L=GonIu73?o{)aDT&mQykta(0%sw|aNB7VAwG)7~4^Ki1H6aS_s@@#^je!`l5* ze6=;6rrFr|jJT7E#hk`tT-)+qOSu7ZR{|1~>#0L|mGe!K%$K$1{kL#~4?WA`a_8cJ zF`e%Wk`G()I)WHUDWq)G zD4Glqi+Qjw!%v_{FT+iXYdN^JcZQGh$=;d=2n<}1r?0?G#)YFtHmML}OBflD0w=)EQ9V$D9*1s1VaW=v`|4z2RcybN`IRct#_wBkszxTxTDq)l zw)4yn*am7Q$ek?+0BEj!$q}!g_cRXf2_dyxVNcm4ttPiOP{)R9{IboAl;QSN7GCtw z_LJ#?7+>5VRfm?_Yy0l*SdE(yh!d22rw2#klP2O=X82?7KlG)(ZH{$vIBbK$zs!Z~)kjzcg#0hrTf8p{&?k3w# zt2@9VJAIjTEh<|2c%};23++kxFFCI5tHd;GK+Req;}RSh{sC+q zVNvVatE_6}XWYAdzpQ)F==aZ{aDHVGy_y%3s(ReI5T;kWJ1iDVN0s7NP$SM3z7!+bKx?Du zo)s>C!&n1Wd#>=fzI;nTW$Sf2?MYa&sBeyBMZ2gm|AmKj?e8ur-%-9_(QZ+4_4a;` zPhE09ybvArhr9!WZm9m~;cymMTp^}xZj4W9SQrTR}OtSd8p!b6M$s2vtm(F(1eI@c9K*JFz7JaDy6aVT_ z`!g5{L|NmKtaVtg;k0U*Ent2iHbCdSdI&?t6&!$lk6Dd8rQ}dx{R8p5c6C;Q$zcB- zT?;be=~88?(g7v03r6k+Hh!KXFT;a22Ua-qXpsn=7Ln-KZ$85J3+#%C3{E4ZZI!gy zKM(q)$lCU7jLdvYB;V?AUimW+jIl?t&PVEg@wduWe&rNdAtYtqk#LM4CRp4#KaR-R z;eLRKuB`8I$ol>UmED=FFRB^xfL83_8hTU}W8EIjUr(*}e!oHww2K1rdavFF<~iJW zY5N)3h($TCs4px_0`k~9j+9o(jGcVl_yUY^r*WcUo7u0@pKAxy!EQtFh}IMdP=IJN zV^%A3?)61^FAKXyZ2q)lM;axDUetJ8Z$75Pa8Ru9Iy%dII`A6;vc{G4s6CCIW;=@u zeJU1UG?l}?^+)A;UIm--lJUiy(%uWC_Q4${q>=>$o4=3Hy@yDz_d%2@BIEW4BN8}H z5o}>?rg5)*x@|LGfIQo9{xW|nKH?;+kpnIp*cCsEqry|4^ZS()kd&?${m*f|upWUi zzOHqL@B3O9kMBoD9HXPrk%Pvodfs(x)xZL?mw7c_W0OQ^fOQ5)qlOZp)WPPk0`(Gh zM4y0k^dVq3Fp>G5>@)+*aso1n!RqwoXY05 zwsOMP7%m!xpBeuwKGalf&8%$?Mhm*MBMBzNK-du^X4LsT`VDMmL`p~Q*@^at5@u!q zyZM~F?J;`8J=B00BvUyT5}l!OF>m-j&g3oJq{2Bd*K?nXXVjayLhekAb*ldyiC0~s zTViK;LP*fhl4VFFujmLPs#w8J@pVqk`K*pNg^UDP#9mj*{?gab0^+6gxk2nGXKDi@ z2sOTnpbIOGOesbtotHhGRKxS0QYRDjc)Xx)<`7ym#4zu>ZC1)Nto8?*)v;}DAVVEw0i5VfKXvkX}7Aot^4a@=~dV_uf@?yIxYUR zs~?aOW?$(6L*Viq4pt-LP5W+2z>CuidyXTu&e4U%o;}yTA)QsVc1iU9UnI^`Z%bkS zQv&f)cO?*X`|pE5muK*60(eUBLDwPR1Ly<#2V`!u=P`M+GD*&lz|1Gh%@G)rtu@r7 z8kx3@P{5D1w_yR2{y+rBYC!X6P3WYu$Qy21rSj184waCs|sbPv5rO&JHNyplza|J$p8_ z{v^Jj4`86PQca2bUE04vF89% zQeu}~Ptn=6KK`%yI|Urf#-VXWo9k{_Tj03;gRQ~q2L~6#Jk_ubaw0bKz#e48QM$TC zFEEJMpYog@-ss%kG9JTMBx3K+D`(biQ7Mrwc|#z?Z@7~v8&C>c8jVMd_>gt0L-)w4 zp{;j;PiorZwV4#7e9O9|t7NyI+9Cl~$HZx+YsvNcBT6J-?R(D&p-s#Ccyc+OJhDz(1`r#x+>3$b-U^h1d2|()3 z&wG2%D4|zB;2rz=*kXp(=SPFBYd|y1b89m9=S=kE;kd~kwm-XA<%-^*iUqIwY{kAZ zvU)+6{|8~!h_H;jbGQ=wc~6bD*;%Q`#LIY2aZ94$ckJRY*q+g%+F^{4Gm-Y)RLd^T zT1YF&a|-mof?>Wv9`pEq)2uAB-VFB2IqVvk=y;?(pT;ZzorEF}9y5|CYLLPek2O@v zu5g)LUx{;01mpMsn9_@iY*Y4DJ;8Hh_-?31l-B&wOsa6)0Kpu+vIVcY_c`BR`1&a9 z!T_6DW(^sneAXm+FYE`OaC2G$Svju|=nPO5I(G8nu*LbyT23}RGA`tyu_`yLwBvwj z^m-1oMI>V==wbHkCk*8^}$pRMT?Xv8?tPDK^d+48LageO~BtdFhFBLlbF2+uI3M zY6Jv^Ck04j)Zlkn3Yf=?YW<%&`gS(ZoQE>SjLgb22#Z?|wcy1c+$`}KIVi&zV~X{d zK!dmlk8qHhmi1Z4$gHr>4rntF+O<-zz}0Yp{hFb5zeY-wIr3ca>yvzJMU~D;U#c8C zl|L%fb7WtjXz&|ZV{_sZZM<O^4mc$D{ zpeNcFAe?1!T?t*Nma>3~$?E7T8>#|`f14LLAFjlcd0|(ob`h-hKM3}n@KRfch%Qt~ z0*W!R@y|M_0o z@AXs1B4?f2ds3qS)E4X`505B`OV|7Ta826Uc%3L9T5Qs&tH(F{jG_{Y{AO=Sr-)@uZ7WhDP_=`Y}jsyv#5JwNU}e=^{q7OsAE#bz|h!N z4PygihunJCm8!u*tG}8^fa_-}-E=NShobHGo7i=H3H=)voyDDZZFNsnKxKu&BHo@K zDAZpyjQ-5XB2c~h3K%l*v#XDA=JV$4Ygtu_*ou0t_~>xsX<-FI|g-XbYK^M zOtt>eG!sVW=Q!sCh#3-wq}ee>hhGWCvcgT!g*K_36uzsll9AFALU{i-u$0E%`F>>7 zF}~p79tC!UL_SPR)J#zB<-Z8@mMpG_jzGf^@BM##E9G7)*-XT~s}L1ka2ZPX3P4QP z+hpESQ?IwdkZq$FNMe=OM7Tq@2?f39b%bOIN=0LJhU42e9g!sFoe%FGOC3h~1xqT5 zm+N~uCJrD|j;#KAwCIXqXkpE*Z++QZ=G{iPK4mx50{weDPV4H$8g6_Hj+peB=g7rG ziPRL(0ldpSYow8Z?Akh0hvt4q+qyu>M^~W3ME>fO(cqvlN1Aff##E*DJOFD zgMEJE)^FWZ$-A-7ib{=4?Aa~rB|YoZ&>_?lxpRE`^u(?ahI_uo#YtIJbDqNyjbTXU zDr4Dh9=Yec-W|{u$UG!!_K8(~vacHHE93nSi!CYU{J&pyPbir|9H~a^y0Y1=my(qn zomC(%Nt5ZcFi05T4+y5-QOic}31FZFN{mp0b3sy#ev03+g7*V^X6S%4(kh$uX(>`< zLn-p+Tt1O0e)Ai0aY9c#St($#Un9Bx-6W`pA^7?iwAIj_9oO;=i=#E%yv+>?wr4-U zUdRb>oWp^F&UL*wscXg!AKDoG|14vO7Dc~jU1JJnar(Hj6Vz)>w{8umV6~IDF!%^P zgVjA*tqBaEtSnLw3&v&#?YW-Zay7S}JG7b;!a>!(LgU&LL^Quse2Ly>MA;V!?9TuhG9Z zjs)GsD8??IK{DqX7Zt~Ezqd)1XYd(%fi0*wh|I+nESF&%#WPV^_yV5d;NP*Ef9poi zqSb4=M)t48L5wU%&Ms=twzF%pnTep9GGm56cnnQJEqE7p?iKd>)azV~K}tDRqhc&#&mr+Lth!>4^^4~-8)5Mm zH0oj|A6)1Y`gWaOsL6@_+ijpffFzX&WB3;z-a;-0g0g`M#B$nS=iEQEPBeA5^3W=X z^Ql$M-7EZm;CrBa1HL1#>fbAB59r$Trg#wqhF={HoK^=_pt$&y9h|p^j1;}D@MN#- zp1|?(`?ZN)DgV|HG8un|nL0Zn*16GZ4v(q&?T@n$)Z%?8of%Ctuy)&H0gfa~eQ5%F0}M!q5d!E!lcPDcCOlx3B?$(sHFf_AqIN zac4q`anH{kTH_9*O~!}ykbfVP`t(=W$qD5=pi7(Lf3pCMonHlO0uAT_|Ld20L5I^8 z`v>nOO#(blw1xR!01Ju#97zc0Io{W~T|;Mya5|i%6p!nQvzUC)Gj{Zr-2jp&>ztHEwoI}ihDEMY_+BY>!pLd2_bEJlP?wY zUU5spxy;kJ3v`1`7tc7Hh#K1F{{N$sfOxyIyfmcTkn0Tgo^taz+x(gZN!dpSf0eg2 z`3S0aGRTlBK2s= zR6ZeR;iMe;H9p*hm|@WdN`>7HefG0!2=hkowuDM2lvRLL8(RY0gI1j8@Y@+Ij1in-%TZr!+9>=uPD4zPbiG}0YWp? zDK@xlk^j@F5vfOQ{m#P`E_q)E(pw@&K+V_f-Ca&a?{SzfD2H#>N#WSgct_t%bb)!f z7J6>d+d}Osd~8sQN?#OE=`>=5ULZ)ARS#|++SWmv151W9TYQ&yZU!MIFvyX(V z=%k@PnO7szzVsA}J^pC?c^;Afy*wS#hcK3ve8ORzXkQ`e|gP+#iZX;U+=6vc7QG>u^Xo6R>$8 z&N*}_V;RD*JegH1!dga~GL_I{U}4+}=dCzv1ja2g<@Y>47smlX9Q5->wJJlj_0%C> zmtmb))8(Qo7?&-eeuOfB#Ha6Ty zxp(HRWd>UI8sz5E7|ZK;)LfRjeAes(IV$PHj^sIbwc$gr`3vq(`92sq1v@KL;sv~ zC$O~{nq-43NV`SS3{72>bolY!J54;wk+{fJZL&$G*^^#z>ImFdZ%ILdDStbuRhlZKlZML0dmxPyQucYqe;pU ztU`c(gdi@I_a)4^)-2mU&&tuQqk3xbM)Od~oW_>t&>BCHAdSR5qAe&o z!n#6(kIL{_ZYhDV&XG4LrozEIOI`Kw^c~so;@11k2}z4vF3N5iZID8EZFAMdkjQ59 zmNVBPgg+$+BuFty3=ypmiyE<$7Bfp5UY8sQE>0E)Uss*j(5gbMgya)0rsvnb%GWI} zRThBbto*DrycpdrFALp3tya?Fj~EKvGJkT(tY-dBh{s^OWO84&i$fotx3@a-YIeO+ zuCiQzLZ|6cQs)_c3o|+Xc!HbwlIGm>ThJW^vwXC*-B@+)vZo>QiEksmUCF z%bzV4{NhO15&lJnn~-KX+}U(dy|iHeZ8PcT{OeTW5O*trP$twtd?wh;8fm-wA%vc9 zRXQim6^pG;1Pwyp4@S&b;m4g;s`z{^1#g~aGH#@yVja?QYWRQj4qd)%M2ycc<=V6P zI*sHL^K$;Of-tDN5q({n&)nMCk{?juK(NfwD+RJH&(v{-7-J<;X=HU8gp+s@7rXFl0s5?nGK}8^=L$zExdsZ$@h#huX@MBwd*pxDSk&F zax2R<-#sm3yUK0|Iiufl%4jKRZNl4SWckMdM-SH2&KJlsNoV!}%w7Hl@-R_Gqe@nJ z(al4I&v$NL2PSgF7iFex!EzJwSi8ZNN6YXMxa-%_Vc>W2*|~P?EhMTgEMw2vhZ5K? z6%kd`>&b}%@|`2$SF`cTg=QrifkO`miYD0X)@WVJIkNfUxfaIq?kjJp{F&~v_D!}o zlWYUT{*GjOa9F|btytz5|I7ylPf`RmmWRVliiWc^1Q3l%an`htVxRNNf?)=%(#4kD zRv}t%#M`MNW;WV!?1roHa2f8%1r3%;-djn}IaAd}q;%oc{jWp_0b5SEDr?KMiL_8K zJ)Es})f+6DQ9+r31#U(LlrGoE$-=@CTA^TFhv%+G+oomqBrG+C-eshA+{V zzVv=g)6+DF`#GPcZnISMl5)x>nTDbd@!+zETS=?DmYOpgbN)s${*<>2-Ms? z!MQW5QD!Z<5`0F~clMJIvOs80^7#O9+&vLqfr_(xnZ88`x}$2z@h3$N3*ih`rSf1> z!|1LA7`B`o9Dv!sF%PGNRq>%)u6hq-sX@F1FTB|2*(m|-&7bAh{bFELvPQt+zWKOX z>&_UZ@RZF|6vu`^hDo<7LY?Dn(Ra2Cx10w$U(9tqmgrw z{2hL2BK)!2HIzvE=i>YkVcnfuiMDFXR{QAO)`4d3@1h8WI$|3lEw z@;GA2YL19Mi!2)q7nb6F_DP>}Kaqnk_M|tP!lUtFmHM=%Q68DgRiTgaG!f@3wH};z zBS~89f9zd>;p2-DY#a49-mWyxzI>qGrdb|4TGy4dKPmC!$V)An>dTPyvflqOBZo6JV~N*v$!COXuCZe%l84V?L1`O0%5fG)l6MELrr|wEPLiDY z7|{^N1^q8NBF9?CoZkE}72&eF4aF~f!!sC-GCUrP|B`0_iN~JJXRM1oQDBqGYczGl zx4W(r%zU3i}uQqSFG{dJ}fkBU9qMAteq6x4i`W+e`!h~eaJjXW%liLzTHaW8mQsb>dZN)zw>vm>Rz*pP#Ndk&3$f zFm#Z(w-K5K%j>@A+jH_!nT=P+1gQQK59dC&;HA~o^&^T2@w$+eNIRmM=woLx?B&SpqG5pTprbo{Nasix z!HWJTgq{=8{VDyn zn{3eaJy_15yca|o|G2%~Vg6$eDA+MZjN&|gZalE0W%)YpoSj1aLMU=9bQy5(=sNmN|f3tH38o4J7CI}_gN#` z`uWpRj~L}&)5oMf?dCO2bdzkRGVS_NH0EH`WJM8eA!UqEQrwVlzmsP<#0{@3Wi5{z zDy@^*zLFSWRmx|(wAByu1~P*JhKIesEWCbmQCnp2sHq11?Hc`nt)UQ7nMDif3(c$Q zZrk3%9SAzwJ=1O_iaUbkFFT91x@phwx94;PL`Yyq3z+v-8oB7n`t&8UB){f_%qr|8 zMR8%0w)Tm%qXCAbLX$>Ul~;_K2`;Y35>1wLd$&By0vhgF8Hbe*%S%;?H$=g1Y|LnG zz)e=(d#2xPt64v<&hyt{wFtq$yJ4jcDKeXjRS`YLQ5})gVf5OW3Dl2OxIgW~fq8MI z3-}PnCt(I=rEAt9&=bL(q+>}{-wNsKsggt(^R)T~1h(~u{#Up>x8+N&xLc0HqV->L zJmUAJqkd0rY?o2w?dP)9ptR^<_vQs_z|U z6l{!t3i(p>opW>ujNyqrl9TPT^NnVZasV+Lb-89ab}F&ZzkHGn?)~Mv4P9VPwqz>5 zHv3=+33^k~KjkUAPvr8|3f)TQGbz7D=n&QS00XX8*k73uo3zN z{FdI%_N8V&)N+}op*hqv)BcZKVJejremBXSQu+lS{;qOZj?7~(bYwfW#9{ceAg<=p z>1xaRodQDsv7}xVn75{#yhVWsT$N-uv^2r}qhrO3=AGVYCoeHSoJTv5sC<@#@!u;l zYBK1=jOne;Yri1go2+-Hvf_ksqEcgSf{$v=bycK#jD=EPEC(}m52``${N_@JBA=VG zPINHbswUT~Y8qAy;(mL%k}=ABy3maCcfA2FQgK6ZW?9;h!%&+CIV}cA83{aN&sEQ) z#%vF*1TYdgplVjTePpz5WQkXB&Lv@<{>-2bC3q;>=kJU4ADtbypX*5XAKB8B4PDO@ zM|#Wz{Gps>vnf0gKwnamQWifq) zpL`0%bRop`wkjY{?>PdS05h70{R$lx9l2)D!}doFTEKa~A8k`66#9VLF)kM7OU5hZ zDJQQPrSv17Mjg_4h3CT7JI}2NC=U_%uNM0m^ZgN_)pa+;h3=nUf04EwRC=fEY`>|E zr`LRT;UNE;8=LPWePY>GM37pfVKaLfQL6CQ^r4@w$*}pSa2;ivubd=f8~Wn9oU+{& zZtKfXL4UTNael+|%b<7it(DX#;Vz+JcUG-T@P)vweee=UK-&ne8uVnk37f3?8Wy(q z#I&%U$UVcG12KlU*e~D0Bvs=k7a%Z!*&tp-j{0#t4tpvDOnKzl_D~Q3QHrMyw(D)p zmPfd?;T%n~Wt4;mEnd(s$K0_BOhD1ubasq}`rF_7g6>6^<1Y0tqPr)b^{3d?cWA$X zX}pvcQfjpV3LW39+U=a_lS2;`c5lZmAWU)YY%+hS#*&gs8P_>nXav!-h8XM6WxKli z3KG8-tFzQ-p0j1Hcr@GWcf%bD4_E=@wE*96zbOB>^{F-xQO!a(*ap8qLyYNxBwrQ} zNZN5^5wwK)!uTh7uG%MWiiqfM7f9i*w<*7Vmo{uqHunBjQ@Dh8R zVxNkNmyy4vm40+p@>IF%9!)%fj-Kh@o5=jmXC91q?n4*T762E5{VvQy*qzRue>+8 z>ea{10UnC_^H}VbEF|716;d*4kcu@E7P0IE3P@+>)opmQ|C+Fw>$X*0q`3pYZQH1<8u9>2i1F{ zU@-x&0nq#H6-!@E`MyWJ%(+_ZX?{*JzmI-V4u1!=jJg*ln{HM>0c{F;!S@lE_ov>g zhmwCL^;}rZdP>T6Il~2(?19oN-BHljPSW?Oq00eT*#q}4SlEe@6 z$Fari2l1%++kq*in)nBh?u_zKs~%eLT=dYrI}|~?_2VOQdwT>>OS#9jIwm;cjfd|J&Iv+s7)raU>;Yx`;^6 zxUzRH2h8w=!)xVn2yx^^Al!`c-r>7k_7ZY0a@2o1PWm<=+J07TL}~lEwvyKTGVNgG z^61#%1^V*In6UBrb|3S*~hs9rJNJ`!879 zni@^d8hrJ(%^c=Dvr+3j5*O3IYJ!;9M>Vvd%MqZ=;|P_-(#{x3oH1B52$Xvnf77{| z27H8z^9fE=$&v$p&1U@OwmgpW5aYdT(%3D2usy2KGPx)YQA8r%SWIcY*eCO}}w6zLl~RX)rm zjnuWx%Bs12Y1HVJ=raONeeqpI>w>edp!duQUL3|PwAS8?Wli0gSUxl(v z+c(24Xrc8&G`Y;V906yg%jy*&8dn@f*H^C1=%MpB-f#4HeLpI*v{l7bgslmW8Qxj| zII(hWxFu#1-9G=*t5C1qare}$CNcnsQEU8k0*^6*Du?SdgH@=2T~;(auszsw6{aJw zWJMC*H$Cz&?t`#LI+*1kzCyQsT>wE~015*mQpKo*i?UxElWzxJ5UX(aGq2;@ZankI zW1{tBaJ2rax79hAWL`f7x`d3%(1b1k*#@y&CBrPZrI61FfN!6J1vD9K4{4M&8+7Yw z|GXT@iIV5dvGV;{7w3_hVS{^nqCDn6fGL{F&rM}HTg+yP7lU705qY%jac>&&z=JD& z?1Ra+Z9iY#U7+axl}y%+S}7|tO^QrzkCa{~l(#~IFNil*F06HEXv}r>zU;g+PK;Yr zAWna*>XjMsw{`iE->D9@DHb=uWe?6s@0tqGq>r+u{S9$Pu+gSOWjAG&&TUHd>hy6%C zH5IdX)z^&G75S3q8BiJ=BP*Fm?4X4^3z9iehV5s!o3S@pt|iC^p<}$HJTD>UlYgkV zRUrL{czepnjn10&uHux^huMNPqQR+$R>4ZPUlL++d~aBwIhN^X&rL5b;O9?z6zs@m zm0E6>1Pj(dljzR*N5}};KbIGS0TYsdA_0hXBt9t)MLs>u1)Sl~uazsLewGh0Y4nNH z@1$SzZDW{@-c(r6FSAaP72dISXtpN2jy%J|3iqC^_1TJjVP*Gjk<+2oe<(1K`sqdgWds%Oozf^Rrl6V+B zHxsTb1=e*DHNfr{*K{OHC=*}{ZM*dimUwF>bXZkVtYoT9k}XQ$RkM$5YjVXwL7b+U z_l(j}L<^&~$#G6y&Xxtn_>zp3-&lRvloUJ!&gr|{Uny$y5Eh!uIX>CA0ujVhoO`;VL z>~Kv;l6>Lx2{u{4?0iGDh(YH@&~(K}4viz;yQFxC5+ok(!J;-ZA77aZs*!qSkGOM9a>2==}HFqYAyy6Nue+hSkVFEjc3od5+}oj}{=!87#UDZB0av zvA-b1tOmU*;}_QJ(})(8P*rg`uM?5gdhfb|*8_^JM%m3wAWR_-M9=CKhLCGNhK|CN z2hSBcDld^3uCv@iK94QWwb4Gmk0s9#mDZUkC4md|bdUtY^v$OQ3ZpbFqjzzJK=!SdPEhJq7UgJg497RYsL=M}z?f2c?I7 z_4v3|z14qo17gZO(Kx{UldrL)%I)_C`;euQqp>gF2hXlDk2m@>{%@tQTipDYJpA9} z!=61;adi3d57YRMy7`MY=zEa-LqO;P=fCdUX8Ug?^v@I67LGj-;q%Eex1UwAt~-|4 z0DDP2G|ysDVtOnAtB~|d2kE)%_Lyw{y8El;u_2Ahby-yb;YlGtr1 zX3(QjmWR@80NeOc-)~gz{{T70IHa;jir0z&zGu31AYNeLb7AlTKqo~FD}lOK!1DGn zOX!4YCC5FFCR_2+b0VieKNm-;{X4n*pu6@Hb#Y`!Br{krTnKeC} zN#8lbD(8RG|1JS$Ygtp|x_08v0lXSm(G84*s=apFonwaB%<#8NX(v z)3b*n1mC{Ck5BM3ct$=|pS8Vl9)GUgL~es9FCXwmW&VwJtAMhf+F0lBb<)tJUMKF1 zKBuW^e}nTA?#tSxbNOEDWRlO(qg3Vqv-0Es*O`W3K3|i_dnY_BhqqPRP38ppi&oZF zcLJNWS~8dHf7a&E?BBU(tIjYZDZ6Lm`Vg`#>boa3k~iW_^>>^D5FA_*yx2$eU+_C; z41I3hH&qQOH^Z(BkCdpPi&^zoA1oqx7+xpWs-xLH@JTTB`d1db@~ll}xbgX7``E*g zPe-ai9xJA8(qN+ebMKrUYzqi%XA{=Wb2!r0=9MJsow3qO_@e#dFk?;jYf`#-Fh+g727ehXq(!`RLt z`4n~5=>5)0u6VvCkVuiJ9}dbL3}|o|l^F6fZ%f^-L#NdNqp$q5Fszzn>s;N*l3ex? zm~)WY*WbGBsXLL}bZ&D#eo^jWZj!G?m9$o7a?D_Hd`H>?{EJr&kzjQIUq8SUMC_nf zTHW21z4-ZYeQHkwW--HU!s4`Jih7eC%=BWR4{QKzN&_}+aS_2W2z6lgd$Xn6S*Sp( zXXg>W*f)A21DLSh@cpeMjJRs6n^Ei?!4v0jR4MsfHa2d=Cj;gi%A2i!wp42NK~>av z1uBthDk`>|jl!6Y_lueS@cRB5Y(e0sWAzlhhfU4mi<%KU6bN|GzeK;2vUqH77Gi!n zftu?cCdVXkU2j|C$A*D7pm{;4+M~P=Kox*8ou6^l$(bmqWrJG`WOCb*LpDw~gDDuU zj_&*wL(jlTwI4R)d|MDBx{ZnJI^CN8yHz@vDVyWcJ<*(taiqV11V$kiK`*uQ3>Gu=&Pk_$dJ3 zF!2SQKGDcSX>J!l28nztSP6b{?~eF1Y=W|=Z$Y~(7ubpJCT3qomB$6TSs6UH@Pzmj z)zgy#a<>ETGP)9rYaX!0^%ls(MX_@|sw=YRz4GwR!a=WvaijjW1q1d2@%%OZ0-e6| zNVNS-hR|}*h29Id;wEdrI+;l;o5)LTX;|RgW<6gJINZhs>#IAdEIEx@* zhk=`Kb}LT`y3~yL3z^rHX~aq5iqc}Cu~`jz-f&J!6QXFPcMsNs)37pbk)rIaz`8VP_+B!hw7 zroXv?fTy&snU|e4W|`S9*tq#Ac2bzE!U|^!T;?P*g01V&&{A{C9`w(T0>CGwEd#Jq#Y1p z1smL4W8dZDLmf2oz)4o9?_4~oOCp|hzaacc68zs%d4&^w&mM2qMfg2f{Avdkkl2Gb zTWy)u(0#I}EFdzi_l@tEu_F7)b2w8%B2%}e?gSPnv0 z1!r*}NAUeDGl;*yAVix)ui6$25RnP+>+~kD{dqwu{Oh^rD#Gw8Et^%1RJ>aoXp{k znw)jJSOc4HKRGu-b#$s^X*U`9w4noe+)ML}tw~$TO&d%$m=`pe$n^tbxyQelPYwP0 zz1gad_W!Ewyu+H>wzZE93n~goFK!HiQ~^OjiiijSqzTfA5(EY50@8w_NRt+tN(T{; zP^E?@(hNuurS}qgjf7slxq|Go_t~EB-upcF<_{iCR#sM@V~#Q2-)pzvT|C&lV;)Dd zFHGWvJjJgH5(I=J>l@QrQZI};C!fVli-%9sK6%3OtKSc_SstS1$C0UtaDXcd3_pL* z@cbFR{WHVUKnapLUrvJckTN{r4C3w3V~)pk>&{Jo~9><4|n2ivVxJ4$xirn7y~=df|6FRsRftPT}YwUUbJj< zDcD}f>L(zm;8Mi8iqk54>*iL)dcoG}dIt_nqr@G%fR95V6ns4&yC7~lZ+PfRuv0{Y`cjGEOswe$pmFEl|g;e$N3&w zS(cy_rdi4br0P(}2373b7S^db>!3T{xODt_d1#03v}4J-2p~?kKx1TER=-DkIp~?X z+B^_I51#{_aG*1)$!@+hFE7(QV0rteW!>^F(47IqjxfUwiN`+~PEU3M zH8&|Uo`9 z*eG(vXrsqU+P=Z#wd$)kh20wB%ekPc#tSCLQ~S{~$#&Of{yaUpaFCEIB)mG%ql}o9 zN5>qejJ|T29?p7kJv3xgvZ8;bFno#IQ1<@rzut9uFGtuM2{%z>3S8UC>RB60`X#b z!Ki%Pq`FW)741SSC)61L94jnk^FWLz%)a(NtewITK&-sldtSZGl zO752g>4U85vu18TDCVuD{v!s%$y6;TfG=9wk!G1h@OaafW7RrVuReT`l zl5SKOBJv>Cm^$*2CQW-=qh3Xw3rJ8x4Vs{6I}4I`z3VQw^t#{OkHP>}eS47>R^%2g z*iF9I8q8A^laA&Lo?=IGoT*3fLR}Z4RU5vq&?5! zeNCZ4KDggG2Kui&+|*qDR+Kj_c#2fChgg;{+~F)~TRYH*hVsp^dm6*sq*vGz2~v`b z`h|at@PVc?P}H1M%GKZu?027y0FymakbF&JzPXH-SJ5ntDc3bteDUr{O@FABGcM2o z)M`bu0}i?kwacD9hof?p3m|5Nx;h!us9>C8E0}^u)$Q--YLsN6EerXRWt)Rv2`mEK z*2%Y*2IloaXAcIXA@?5en=vq5s_5747qS~)iCo3pzp0&Nbm8>9GzRo&G1JFQd|$40Z!XYirzn_{k=WJkR;t-+bU?x7xOVn+0U%m>7gy3Lrqj)SHYaMA`00M z7q#wSJ?pPqMslMu?h*3nGUO!4Ti5d+LVjQ5VN=RoPQFAzL@2Zntq18*P(e{{6>1y6BHe5u<1yRpo%Up$10qh*Q(65p&}2 zqm45tLQUkN+>0rPS)M13aVSC)A>y6DPiEvQZ$W<#=_bFX^%dT};SF-H)BT<~ph=E3 z_jY{^UHE8CPte3HtxKmW<>w2Won?#cnfHX{komWYIdnmv-Jl&NC9_j65f&6XZHI0{ z@Rgt*ZLQr*gXywoc^`qw@DU(ARsfRTdn0!02kSz$(N!?xqEf+kai19WSIljxat1%( zzN$^^9_tS+y_YprFMzAmZiGJo(b>`LR-v#cOh@g743nw)jAXP)!eK^Gm@7|loU^QY zFR(XEKdmS_Tna2i{=?&KHSyQIz56UAJrGIv`tBdn|Nru))}TMVECe9k`vBstlHvxa zNJLr{Bwz-gLS-XpAyS5#gaF-5_3XtCP)q`L9$XlEoA$SbUJ6j|wzvE*t)CR|{HJY% z+!X$8NAQQihW;1Z_-jD;9~_LmA40H#{r*2#KhTP=u6ys(taDlkB6?S6Cq`;JKLc@6^Twi3A5{sJ@|uUONNAU zd(tMHsm2AL9X~_US+e4B^U&EH)U%i??zOCEXi;`XeT@|mhG=HCrx1StdKry%6Si zQLMHK*-7SlzVA+Cke2J$_?h6RL^xfYr3`>eYkLtD>6Rf~ukmO&0VvVQ_mt>D)e_Vx zP1!&_?7lo6$%}aS{AGY<@#iAMf_Izh15*K`GVUCyUjQK>nJ*D2XpRnEC`J8T7C^3) zKLT2tpn9!?UwlvO{rtVgbvC{vWXC70oRW6u<+kA&z*%G4yf}~xbz1@SVLsrY7DN?V z)#br<92e1s#g2Ulu)2)_jROez8&i|AO_#px2fHwSSeX4*?mTmo{T;<~^R0ua>B09@aruKG4AxJML#dGvhdXfSZDe!U3Ee-QjkqVPPR z8qxt)77(onOPoq~+-zo*cCLporXuE-vjYuXxeY;(>o-*Z2#^4Cnfo4ofgiiI-7MX= z4(X9>%j{QTsk;EYXi>W7qmzezzGGRvl(1>DeaJ%h2}b?vA7KqmV)Vg-4~XVTdlGft zf0w9t_j|3=-$EnGP`9bjCi&C_QWrQH9q0PT>!S*1v!%8U_hhRhGqlXHB5>sb!4b|L zKsMZ3cLZP~LSv5;<}WN#0^kT-A1A)ZZ=At6I-qF6i7RcX578M0A0aZ}M_^+yH^8Hv zwL$dLED`jvp3=~&ReAj=$Uk;lNu{j!WdPtoca>R>FUsn8Zz`X_K7boG+Fn!soAR^s5|HO^ci}!HegaB1bz>e~pv*bsA+gZYLxsMK zi>%i#oDU2BFe7aG*#v{S4UJ}5yV;lP3#n`}nnT@NEBtFNI=@ywd-+-4#It>imIlTn{4QmbF_CXicnNIy+ zDOp)>sF&GU-_UbhBxvQQ2riua#qhNL&kRpR?)v0YfZ<8aLmEWEw5bXx-DcCREmqTL z4^c)h020E_Gq)VlXtu~tv|)YFnAFS&$we(auSkzx>-ZhrguD~*T*QFWVDPmqASUN4 ze@LO-j|FuOAg_1V#NJeZ8R?JDNpoSROn~L!QG@|#eC~L{b*3uG%I&tCdIJ~En+DJ} z4Z|MwmGeAu_a7ClhdbgSa}P=hHR(sAI#DHeZFRO9^;Pshy{T~aTS*1DPnz`an7?@U zL;sGh2f0{Fxdu~MV#0)Vqxgvf4rt`8ETwHkp7b|8jgl&9e$IaX>*Hxa$pi=xCE$*G z*L-N*=E_P@{j(9D7NcZYWLN2Ziv(byBVuL@hY}-DXNy|-lxK|+2gkO!oVlTkY}`IS zeh)=7M z7inSw&sM5W|D-r(aOvm$)#>4K5+{@E(o3aZpiHmYtpb3Y&79*4qlMwqhkv3ngGaBqhM$2@nWVm9&;ld`f2nIv z$2EUPEMr?7e~`!XL-dd<7!5ga9ujU30QfomFgh%hcTFSeS+ohBWghbGvF|O8Oz|Fu z)u3GYG+Hsh_@Z9FJ-0yJD7}?`$vJ~U*0~MnuLI9@Hct=%Z_D-qt!kX~O5kn63_?Qr zwFKYPYg0h6Bxc{uXer`m-!4kkse5;L3FbxBd@OWyRFpxyP^G2rR`f988KTrJ*(H)k z!lf4mAQzgsyg41t{zcN(akt+$YXI4grvjZWcS-38@DB+yLRa;Cuh4os^Ewm?06{Cz z8~8af^=;d`WvvWLwMWCHLG2Z$kjRrZ`y(Tz^|BPI#EZR7F-5Hj!`eboDrOA4tX!D9 zUWlA+sx%lo3u0u;$$(>i^oKVX%8UpgN!i2q!@4YHq*s7WlQTiPyr84b2Qt9ul)I57 z8v1UEJyB-l29uO|bYDZoJU|HN8d70$y)~p@xO>EUBC(Y%j~Cc}r4$;jHYi#Ihq*Lx z3+NZuYIK`&kN4u;5alk7StGMBEM z(Ey0LfbYTI#pKD%(w+aSVQ7EoS2pT>sb`lAhx7TUq<~gA73q@sQ4}^?-N=>^*Q&5`t5KkZ_NOWK7~AY0b=&g3`19;vpqT zs~cMVn)L5ZY)Ge&^auL?X#LSo&t!}5ub(9Q3#A`AMV_nSM_ZzQ3-Al>4Y~Mj{wSV=G#b}C~{e1rJfNl~9^ZEcz2~n0T`b2Mk zd*mbmLTbWFzA;e`QY!CHUk`o~eSGP5T{JKg0LUW84RL_)nFbik%jX$;G=hA)yUOhg zd8{e6(kbGcJhmBbp=B*gFB}bs>IO~-G;etTR?{Be*yE@ZxBc9*Gc~83`^PvC(u$gH znz*h_lVCzxoVjSXpvumAv5 z@?{XX(m2du&$hTEhyu|yJhxR7)?(ECJh2gi%H_6nu5WcNd^d%g#K)n{4Z|ir0=|qL z(uOE~frXJLMu3=n2`S`MX$+wU3K1+x^>HRUpq?7=jCHKlPJNNu>D5NAy<(V&^kem?Xo8>%#$k?q7rz8dudH1GfGI*2nXS07!eKyX0Wbm|IaT9|m)B1+ zn*&r8@Go!{$SHT+dN0ZSc%vnn@@i(Nblf+C$N!3VFU|c8fSslE<&EuTYaP&OBuOqib&a?G?>HS8T#+s+`=1tBG*wk;ur6j`rll2hf9}Ml{ z@Q;NR{*jrfnLjjKfjedX>4_@u9z=<)lDki@5yu&P>yg|)$Hnw$7|-Wa02hacn*z7$ zmjLcKY7e*qRHAZ*9IbwOSaCT5IOGv`BqGg?S^EqP*;UW!=T6@tYAa9J&iMV|elowOYFH4U{@u9e#2n za&!Wj_$9Y`8B_vVrNf#|>u?TXXtkT0Vk;H^f#?FCKg_x}uMN?DD7tANnMa%BGdE8F zfOq!D{~g{`ti@maC%ow({A8ZC?qA?dgaIuy2S+^*;D<+mZvVt&OHGuH4i|2*Mpdi| zH0Z;)n<4`jPd5_Xv4*aJxJ~-Rvi6O=)z39ow4Q zu&%gZ6Uk_ubfa^T)|*ACodBxx{eTPly04iRU@Jzw3J$|7wK((}NG$-0z2-cB@Xeqo z6FJpb#R!pp7cSx9?NWD@H&ROX^GEoPxGpoq=Q4f&Ymoiu*Ffy2m|Lw~I2VpqON8<0 zrQI(pzV4ZHB$*Qx`xJmdnEklgJDAwRDmH4s=}0}?7B>H3i%a#%$XLb)inD7!sx1B1 zBkrI%D$(#{LoRn2>l?wR(yaib3eC<^vaMB)5g3FJ%(Zf-pU8zJDdw^s{JshwCcZx^ zH}7CeC`L0|D(4B{Z@AVaTqXYoN_8>z3dVG&%es}(>xPZk_ITUG6Ko<)bNGQ3Ag=Dp z-1;z+aCrQ;@pwy->!0!Xlw0xUPmqsUBd#R~&H+mlFspH-SmFNk+jzWj9E!%rFUwh{ zw$#YPQ%*$|&g3&CNM@wB0!mx?UKSv}P)Q*W^m^M(63*a?`V}-HnlI=vH(Vc+vqh$= z#*0yJ?9Hj4Tcz;uefB@UGpqJapEBmca26i08;xkd1Ali~!w`S{%dw-)Kj{RJ5l=XA zb;hZx!2pK~WgD~|;N)Mr5u$MNKc7QDv^0!%i&yqM_{-i2%1NlTap;QmB_E1s-Yce) z((I)8PHbOiq~TxJke&xSz%1sX?9s35VRH@wwin9>>66^n+WJb}c(`2%heiSS+~0rJ zs(;IG(-LeZoczhcL`Z0?dzYB{z7_*@nl<%%L!9GnU>xMA_d|tKk_DfgzN%&WD}$_J z3X||Vu85L4`gF{9OvxFQ4f4Z#`QY7D@hE}BVEVUwvJjLq{%@h*fAjr?osaq*%T_mJ z=;a$6j`DvJkvuQRb4gfuB-1k|(|*XX$C`Mx^5J0A(d}K!^=L4U0Iugxry!^DQ=k%Y z7KG5(z)Xkqt{dcb{x3`N-B06DA#V7o{BAk0p+5ik7BFiEmh!*(mdiii1&-*uTLIGQ zq)+Xn{<#q(|MoVjf7`TOS9j-2Z>mAMhKCItM^-76#C+q!E%vo7?;ycw<=aEr$H>TT zj=-lH8E@ES?C$68*M75kd%OJG1+oUpg8J{$j|a5VPAq#b4h~g4*%=xk#0-=M+)StT zu1nD$OzbksvG)R2^TGSqx_*wUH<{`WPP&y?d7J{cNIk55QeQv`C2RG#0HW|y*#_ls zVw=j~%dIh#fhsjtv3d8p*XHGg$Ub$&Dewz}&j4N9PeIC8 zHLk+stmSLRZBFn@KBI}e!rAsDKlqSdpF}ROU~dBORVO1e8^3_|KOP;E6*%f15^cGI zxCv&lXpb{;45BX72*%=I@l5=A`)l8PHuJzW*h%BNnuJbT0w zR;&qgX{es6l!U?0!?%0DTW61;N0agQjfpns(MKVJjbBl>88#{Gfn&lU)q%5}nHDbw z>9d9yYd3{WEELg4!SbGG{ru$6rFol5Z=$HL%OZsXcNAbD8ZcQm&=Kiix@ZYvDUPELC}MCISLGk;qG8#h<@wZda>U;R9JitQA$pU}7;VFP2ZaN_tOK6mQQOta(svE`kr zTZiFxQg9)eQwL=>w}N3wXXR_pUo$X1cEsy?@}hiT+_ps)#U!O)IPDPj4FpO7dqSZ}{?RLIVFD#vSkvJ^WayjP{^`&>lY8P`7U**&j z`ik5s^G+xaRgR=wym;k!lEl8Ay7Ff-1|3)W%G{OFoaa-D`ag=IOU>q`PF5a-HYOMr z`Du6ZWF9fA9*?Qr#BH_WU}@N+aQp zq0lzsv_qN zSu-a}>al`A700sD+_!xlt?hxWUr}!sj~>y@hna=IsG;plxNz(T7j9g@@L7L3>VizI zpNWRvc{7=ttP(E!12c_;I>^brU)wAm@;n0q z^y})ZjI3JRIHx)3>exq3&2v+8UPS#o4w#sIhm)pcjb5aF0yF zG@PGTC0*F0Rcp6Bt(MR={5eIIqN?5*8fh(_kaS19esYg9;1_EoaT*rGu_lYrMA$%iU2U6)L-wt@Q*l|DPSuhI_VC(R#6bK- zOkVw&`K$$7We*+V=#y8#CNB; z(+&AF75TfTPlkpj5hjotZYE$P2v=&jAw!!f-aLflto&LZxD{8&dH!kLcvXPD)4iz7 z)5{$GBPazzUpNHc)(x6nT`kh<&x!{$ZVxqe^x~-B)4?NGPy~#!`c0YIk)ey^$Z&j* z$0~h#wY2-TA5oA(3Pm-mdtjST+0JfEr4uWFsJpvTF3Sxke4{o4h`S?Y?gx}zrWxNZ zh%bwMq+I6yV6HQlg&a}Sx?N%p-;RgF&qI$^Pna|6p3JPK|L6l1uFW*&&ctV1`GJ|S z+0G@b4MVydZ#H;$|MhOYLZpgJSq{(Hz-e0?LA`< zJ+f%2qh3=lkU1oxQmmGj)mM68xoNJGwNfR)#&PA@av=Z2)MO2k&?)cOzRbTcW=Lxh zdTJo*Z7~IhE<2l|P;Abt5miL_=V#~YbS)b1u*gb8s4&;ri50t!6o2KK)G@4`#>_Kt zI6rb5_L%9*N71O0)qZoQicXOcZylMN(+IiziN$>QO#r9$(-NiUnLp^PN{!9TR^?P} z6ARZr>~t$nNe;gR1J`RR_^VOHaAve-NUypREU0F-@#@O(#*2}|ONp!YoBEpw31RE% z6CgynC>PSp!X19BtLB984Iy^tn+M233^Fxy+1+7vW~h(IDX_M)7aZ%-Z;f8lz_8#N zi_g6oOY;M=-lic1{_$&OZxyU8o9TS3Po!{z&?uz$OqO^Ei*1z{9S(f+6fW7$^j+ZS zlUsu`ud!e_pHMc2McTy+r?KE}fi=C~IgOicx5hZ1B8xvad8|SynR!Bi8pr1hK8kP) zDYa{xQoG{1Y|!|ejLiMO^C5i0<)M{%4f4__MD8WZ<6gj2V#j{`T?waz+a|349{+)1 z$C|nRoEYJ*?0ZSz=USbAF3;L8~MPa6fqm2veV8tAh3csX^*28jEvWZ+>l5GzZz$0z!>edHb2(mG~-j4EJYW{CViUU zsGQ2E+)ZTuB?#G^=KCoR6C(KZt70Wxck=ARm8`3-Wm7H+3JVJ#qVYl&XdxN4$JPp^ z@~LZuwmKQ&mR}q;9qMYW7mv8peo?s42lqxyS-IKx-or!D;|uFVrZp!7jhvuS-aR)v zM!yfD;m+kj-xlGW1$_8oFAf^N!}2wzfaAiRVg69}zE*u_(wZ z)eE(rpUc4(?CWqyenfBj{mJhYl3 z9+BQoQsW&aPIpKj58E*tqcyP_=p<|6X=YS=tivlh1^lasAO05M?pZE0DF zcU|2o#G%~W zl%T`=O)T=UYk}NPPPXKq=2r)HdflJ6d8SV@xveg!8=cio*K*r`hV6tlXV^Y&Xzl}- MQ@N3O{jTT#0|kKHWdHyG literal 0 HcmV?d00001 diff --git a/doc/windowspecific/window-matching-knotes.png b/doc/windowspecific/window-matching-knotes.png new file mode 100644 index 0000000000000000000000000000000000000000..7fc4f8e75013211c89ea5b1d7a626a1b6c0dadb1 GIT binary patch literal 37378 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 AZ2$lO literal 0 HcmV?d00001 diff --git a/doc/windowspecific/window-matching-kopete-chat.png b/doc/windowspecific/window-matching-kopete-chat.png new file mode 100644 index 0000000000000000000000000000000000000000..d4e6cd84424d4765223be78398154d8dfa7fdb38 GIT binary patch literal 52380 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 literal 0 HcmV?d00001 diff --git a/doc/windowspecific/window-matching-kopete.png b/doc/windowspecific/window-matching-kopete.png new file mode 100644 index 0000000000000000000000000000000000000000..4c34ee67536df12184770ed2c81fc02c25312ef4 GIT binary patch literal 50418 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 BDDD6N literal 0 HcmV?d00001 diff --git a/doc/windowspecific/window-matching-ready-akregator.png b/doc/windowspecific/window-matching-ready-akregator.png new file mode 100644 index 0000000000000000000000000000000000000000..35ccf297f431270c8e7c0ba2b913e56caae23632 GIT binary patch literal 50090 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 R4!|!_6l7IpN~BDK{|`HmFgE}I literal 0 HcmV?d00001 diff --git a/doc/windowspecific/window-matching-tbird-main.png b/doc/windowspecific/window-matching-tbird-main.png new file mode 100644 index 0000000000000000000000000000000000000000..98a0df802fafd541f99eca9801c47bd8766be4ea GIT binary patch literal 55370 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 T-LcQq00000NkvXXu0mjfXPdXi literal 0 HcmV?d00001 diff --git a/doc/windowspecific/window-matching-tbird-reminder.png b/doc/windowspecific/window-matching-tbird-reminder.png new file mode 100644 index 0000000000000000000000000000000000000000..c32a280720c55bc9e54a8374b5b835adea60751e GIT binary patch literal 55508 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 +{ + chomp; + s/placement=NoPlacement/placement=0/; + s/placement=Default/placement=1/; + s/placement=Random/placement=3/; + s/placement=Smart/placement=4/; + s/placement=Cascade/placement=5/; + s/placement=Centered/placement=6/; + s/placement=ZeroCornered/placement=7/; + s/placement=UnderMouse/placement=8/; + s/placement=OnMainWindow/placement=9/; + s/placement=Maximizing/placement=10/; + print "$_\n"; +} diff --git a/kconf_update/kwinrules-5.23-virtual-desktop-ids.py b/kconf_update/kwinrules-5.23-virtual-desktop-ids.py new file mode 100644 index 0000000..a0a21ad --- /dev/null +++ b/kconf_update/kwinrules-5.23-virtual-desktop-ids.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 + +import fileinput +import configparser +import os.path +import subprocess + +# Get the config standard locations +config_locations = subprocess.check_output(['qtpaths', '--paths', 'ConfigLocation']).decode('utf-8').strip().split(':') +config_paths = [os.path.join(folder, 'kwinrc') for folder in config_locations] + +# Get the desktops information from `kwinrc` config file +kwinrc = configparser.ConfigParser(strict=False, allow_no_value=True) +kwinrc.read(config_paths) + +num_desktops = int(kwinrc.get('Desktops', 'Number', fallback='')) + +# Generete the map from x11ids (ennumeration) to UUIDs +desktopUUIDs = { -1: "" } # NET::OnAllDesktops -> Empty string +for i in range(1, num_desktops + 1): + uuid = kwinrc.get('Desktops', f'Id_{i}', fallback='') + desktopUUIDs[i] = str(uuid) + +# Apply the conversion to `kwinrulesrc` +for line in fileinput.input(): + # Rename key `desktoprule` to `desktopsrule` + if line.startswith("desktoprule="): + value = line[len("desktoprule="):].strip() + print("desktopsrule=" + value) + print("# DELETE desktoprule") + continue + + if not line.startswith("desktop="): + print(line.strip()) + continue + + # Convert key `desktop` (x11id) to `desktops` (list of UUIDs) + try: + value = line[len("desktop="):].strip() + x11id = int(value) + uuid = desktopUUIDs[x11id] + except: + print(line.strip()) # If there is some error, print back the line + continue + + print("desktops=" + uuid) + print("# DELETE desktop") diff --git a/kconf_update/kwinrules.upd b/kconf_update/kwinrules.upd new file mode 100644 index 0000000..cc381bf --- /dev/null +++ b/kconf_update/kwinrules.upd @@ -0,0 +1,11 @@ +Version=5 + +# Replace `placement` string policy to its enum value equivalent +Id=replace-placement-string-to-enum +File=kwinrulesrc +Script=kwinrules-5.19-placement.pl,perl + +# Replace the `desktop` x11id number to `desktops` list of desktop UUIDs +Id=use-virtual-desktop-ids +File=kwinrulesrc +Script=kwinrules-5.23-virtual-desktop-ids.py,python3 diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..95b3fa8cd8bfde4fadab1d8ecce48fa1db211249 GIT binary patch literal 4200 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 jZG?4q`@bm6JB, 2005. +msgid "" +msgstr "" +"Project-Id-Version: kcmkwindecoration stable\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2022-10-26 02:35+0000\n" +"PO-Revision-Date: 2005-06-18 11:28+0200\n" +"Last-Translator: JUANITA FRANZ \n" +"Language-Team: AFRIKAANS \n" +"Language: af\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#, kde-format +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "WEB-Vertaler (http://kde.af.org.za),Juanita Franz" + +#, kde-format +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "frix@expertron.co.za,juanita.franz@vr-web.de" + +#: declarative-plugin/buttonsmodel.cpp:53 +#, kde-format +msgid "More actions for this window" +msgstr "" + +#: declarative-plugin/buttonsmodel.cpp:55 +#, kde-format +msgid "Application menu" +msgstr "" + +#: declarative-plugin/buttonsmodel.cpp:57 +#, fuzzy, kde-format +#| msgid "On All Desktops" +msgid "On all desktops" +msgstr "Op alle werkskerms" + +#: declarative-plugin/buttonsmodel.cpp:59 +#, kde-format +msgid "Minimize" +msgstr "Minimeer" + +#: declarative-plugin/buttonsmodel.cpp:61 +#, kde-format +msgid "Maximize" +msgstr "Maksimeer" + +#: declarative-plugin/buttonsmodel.cpp:63 +#, kde-format +msgid "Close" +msgstr "" + +#: declarative-plugin/buttonsmodel.cpp:65 +#, kde-format +msgid "Context help" +msgstr "" + +#: declarative-plugin/buttonsmodel.cpp:67 +#, kde-format +msgid "Shade" +msgstr "Verskadu" + +#: declarative-plugin/buttonsmodel.cpp:69 +#, kde-format +msgid "Keep below other windows" +msgstr "" + +#: declarative-plugin/buttonsmodel.cpp:71 +#, kde-format +msgid "Keep above other windows" +msgstr "" + +#: kcm.cpp:49 +#, fuzzy, kde-format +#| msgid "&Window Decoration" +msgid "Window Decorations" +msgstr "Venster Versiering" + +#: kcm.cpp:53 +#, kde-format +msgid "Valerio Pilo" +msgstr "" + +#: kcm.cpp:54 +#, kde-format +msgid "Author" +msgstr "" + +#: kcm.cpp:183 +#, kde-format +msgctxt "%1 is the name of a border size" +msgid "Theme's default (%1)" +msgstr "" + +#: kwin-applywindowdecoration.cpp:32 +#, kde-format +msgid "" +"This tool allows you to set the window decoration theme for the currently " +"active session, without accidentally setting it to one that is either not " +"available, or which is already set." +msgstr "" + +#: kwin-applywindowdecoration.cpp:33 +#, kde-format +msgid "" +"The name of the window decoration theme you wish to set for KWin. Passing a " +"full path will attempt to find a theme in that directory, and then apply " +"that if one can be deduced." +msgstr "" + +#: kwin-applywindowdecoration.cpp:34 +#, kde-format +msgid "" +"Show all the themes available on the system (and which is the current theme)" +msgstr "" + +#: kwin-applywindowdecoration.cpp:65 +#, kde-format +msgid "" +"Resolved %1 to the KWin Aurorae theme \"%2\", and will attempt to set that " +"as your current theme." +msgstr "" + +#: kwin-applywindowdecoration.cpp:71 +#, kde-format +msgid "" +"You attempted to pass a file path, but this could not be resolved to a " +"theme, and we will have to abort, due to having no theme to set" +msgstr "" + +#: kwin-applywindowdecoration.cpp:77 +#, kde-format +msgid "" +"The requested theme \"%1\" is already set as the window decoration theme." +msgstr "" + +#: kwin-applywindowdecoration.cpp:99 +#, kde-format +msgid "Successfully applied the cursor theme %1 to your current Plasma session" +msgstr "" + +#: kwin-applywindowdecoration.cpp:103 +#, kde-format +msgid "" +"Failed to save your theme settings - the reason is unknown, but this is an " +"unrecoverable error. You may find that simply trying again will work." +msgstr "" + +#: kwin-applywindowdecoration.cpp:107 +#, kde-format +msgid "" +"Could not find theme \"%1\". The theme should be one of the following " +"options: %2" +msgstr "" + +#: kwin-applywindowdecoration.cpp:112 +#, kde-format +msgid "You have the following KWin window decoration themes on your system:" +msgstr "" + +#: package/contents/ui/Buttons.qml:85 +#, kde-format +msgid "Titlebar" +msgstr "" + +#: package/contents/ui/Buttons.qml:245 +#, kde-format +msgid "Drop button here to remove it" +msgstr "" + +#: package/contents/ui/Buttons.qml:261 +#, kde-format +msgid "Drag buttons between here and the titlebar" +msgstr "" + +#: package/contents/ui/main.qml:18 +#, kde-format +msgid "This module lets you configure the window decorations." +msgstr "" + +#: package/contents/ui/main.qml:52 +#, kde-format +msgctxt "tab label" +msgid "Theme" +msgstr "" + +#: package/contents/ui/main.qml:56 +#, fuzzy, kde-format +#| msgid "Buttons" +msgctxt "tab label" +msgid "Titlebar Buttons" +msgstr "Knoppies" + +#: package/contents/ui/main.qml:92 +#, fuzzy, kde-format +#| msgid "B&order size:" +msgctxt "Selector label" +msgid "Window border size:" +msgstr "Rant grootte:" + +#: package/contents/ui/main.qml:110 +#, fuzzy, kde-format +#| msgid "&Window Decoration" +msgctxt "button text" +msgid "Get New Window Decorations..." +msgstr "Venster Versiering" + +#: package/contents/ui/main.qml:139 +#, kde-format +msgctxt "checkbox label" +msgid "Close windows by double clicking the menu button" +msgstr "" + +#: package/contents/ui/main.qml:156 +#, kde-format +msgctxt "popup tip" +msgid "Click and hold on the menu button to show the menu." +msgstr "" + +#: package/contents/ui/main.qml:163 +#, fuzzy, kde-format +#| msgid "&Show window button tooltips" +msgctxt "checkbox label" +msgid "Show titlebar button tooltips" +msgstr "Vertoon venster knoppie sleutel-leidraad" + +#: package/contents/ui/Themes.qml:92 +#, kde-format +msgid "Edit %1 Theme" +msgstr "" + +#: utils.cpp:25 +#, fuzzy, kde-format +#| msgid "B&order size:" +msgid "No Borders" +msgstr "Rant grootte:" + +#: utils.cpp:26 +#, fuzzy, kde-format +#| msgid "B&order size:" +msgid "No Side Borders" +msgstr "Rant grootte:" + +#: utils.cpp:27 +#, fuzzy, kde-format +#| msgid "Tiny" +msgid "Tiny" +msgstr "Klein" + +#: utils.cpp:28 +#, fuzzy, kde-format +#| msgid "Normal" +msgid "Normal" +msgstr "Normaal" + +#: utils.cpp:29 +#, fuzzy, kde-format +#| msgid "Large" +msgid "Large" +msgstr "Groot" + +#: utils.cpp:30 +#, fuzzy, kde-format +#| msgid "Very Large" +msgid "Very Large" +msgstr "Baie Groot" + +#: utils.cpp:31 +#, fuzzy, kde-format +#| msgid "Huge" +msgid "Huge" +msgstr "Groot" + +#: utils.cpp:32 +#, fuzzy, kde-format +#| msgid "Very Huge" +msgid "Very Huge" +msgstr "Baie groot" + +#: utils.cpp:33 +#, fuzzy, kde-format +#| msgid "Oversized" +msgid "Oversized" +msgstr "Oorgrote" \ No newline at end of file diff --git a/po/af/kcm_kwinrules.po b/po/af/kcm_kwinrules.po new file mode 100644 index 0000000..cf69acc --- /dev/null +++ b/po/af/kcm_kwinrules.po @@ -0,0 +1,946 @@ +# UTF-8 test:äëïöü +msgid "" +msgstr "" +"Project-Id-Version: kcmkwinrules stable\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2022-08-19 00:47+0000\n" +"PO-Revision-Date: 2005-11-26 16:33+0200\n" +"Last-Translator: Ilze Thirion \n" +"Language-Team: AFRIKAANS \n" +"Language: af\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#, kde-format +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Frikkie Thirion" + +#, kde-format +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "frix@expertron.co.za" + +#: kcmrules.cpp:28 +#, fuzzy, kde-format +#| msgid "Window &role:" +msgid "Window Rules" +msgstr "Venster rol:" + +#: kcmrules.cpp:32 +#, kde-format +msgid "Ismael Asensio" +msgstr "" + +#: kcmrules.cpp:33 +#, kde-format +msgid "Author" +msgstr "" + +#: kcmrules.cpp:37 +#, fuzzy, kde-format +#| msgid "" +#| "

    Window-specific Settings

    Here you can customize window settings " +#| "specifically only for some windows.

    Please note that this " +#| "configuration will not take effect if you do not use KWin as your window " +#| "manager. If you do use a different window manager, please refer to its " +#| "documentation for how to customize window behavior." +msgid "" +"

    Window-specific Settings

    Here you can customize window settings " +"specifically only for some windows.

    Please note that this " +"configuration will not take effect if you do not use KWin as your window " +"manager. If you do use a different window manager, please refer to its " +"documentation for how to customize window behavior.

    " +msgstr "" +"Venster-spesifieke Opset

    ;(=9WxI#+>Ytao^Sz!w; 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—!CHLPY)Kzu5_h6cSyzr8@>?4}H{DvmUig}qm3A9m;#NLO z7BB3Z8TZ_LnY;<+#+79kG*dbD4uC5+QvUqKG`uqpksXE$m*aqFiMX6RsZ_c0|T&zb0z}M0RUH>1pn*TjZ?ifGIob*tZA#L z8eGItT_-iqOR_L`1iXDT*>>@};l7cPohl~5%0bx(=PmPnmKjNCJ6+{eDYp3MbLI4b zj-W6E(75Lpz6Gqg5tmr4ltf{{$Wf;rROK^lQUfFDMm-XsE5iu_K4D#KT>? zPC;qKl0_&j$wzIK3j6(hwQCfrRxMvbeb%h`Y~6&P_wCiGEulvoq!?q2>2Hu~ZBmOi z$72^ZAC1HA@MQcDng}N^e{9(k0CSi1So2dP_MEt)S@Q|Hgei_2k)C;5RNd*uMrc%5 z>CDl1hOv=$Ob>6P?jXt-O1rZPE7EX>ffdNCU8 z>`>)sgXrNyp%j8klHo{PoHUm1kZnQHrlb~RaIm-Y^`Qf z0H8Uk#0DuzHO~p5D0YCii6^+I5ZGWZgu!Z~PPX}|&88T%I`2z%)sF+DjQ@yiF-9qnv!HaZgbRB|+` z@6nZd)U{uv+d29~DmN>w+uHLS>>)4A(T+b*M=TCfj4{UaHb~J!Sh8j7ePQb6iPipb z*m^7tKF4okrQa!hYvqc>iVUC2j3#ckrATj;|jcH!HhaTQ^B^P2&Yre_) zW@<8x#S$B&B-Kn=y$bO{IEoweIqr-fhEgF!)qXVyCxs9t4-L}nQJ3UFZafK@gFi>o z$U$^Z&ppYIX?|+&QtUZYSKLKW-fisIwpqJwZ8;0^v1d_Rr4-h(u)*7ldemLNx}6I= zd=SePFVx&ysVsq??=J0HSV$mNE?tbgJ84i?tB{>}lR|CRI%08Z z=YPFS^Zl9FASJnOeq=a82Y-Q#Zw;UnLX>j!VB{JOL+LkvMbm^4XddxIT6Wtqb zKNzHF?NM9rds1DJkBh5T;+V-a93J&Gjv9_Z@N^TJqqV;3NB;;&()-RqiZRBRekhk0 zA^rMQq^2a}WydqLXoG`(YHqB>s~694?Mf1zo6i5yo^-r;Dg=N(+7lJ&Y2SeFdr#UQ zws4U8Z+myw(l`)>aXg<{@M3QS1^1>`z7!?9ve06ut)*Js#aeKEPt&w9u}BsDFh@_o z!4-wMS(MCw;Kz{Mod*IBnLq#lUr&FpE1T;p5^hu_j;`e4{;u#?!aa^IWTU#O^XlAW z^1t_wbEilF0LtNXa3}|zwuIF+`M$Uy-{$AaO6Su#bzYsDOg<-fiWC5#d}V5|zbjp{ zEsbAWWu^1!oH{R=d|vJpDF8q@oed84`<1fN`N-t!=I&mkX4BmJhZO(-z}4&~6I-Ni z^VIu500017-Dc^lB9&3300017P1CV0QqLEO_kjQa0JzG>Be6v)Ph#%_0RRASm5qjC zi_~=-c^?P>0Dvp)PPRx*qD$`s0RRASHHj|77OCX&%=nUe->lDZOsT`=9^- z0IYw@lRehEo{Xi_ZrLJb%T&u4>HqG@-~J!(g988ncqvb_X)eWao@v+;_3ru^hEl0i j0002+%8xLV;LUmhL_}E7+o+tu00000NkvXXu0mjfy~|R3 literal 0 HcmV?d00001 diff --git a/doc/windowspecific/pager-4-desktops.png b/doc/windowspecific/pager-4-desktops.png new file mode 100644 index 0000000000000000000000000000000000000000..4f2b28324fd0ac1456c2a77b4be55b1f8d57d5d3 GIT binary patch literal 11817 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�(z_~t`0O{{>vzi1Z9zjt1IFLfBmVZnI5qDc&m`| 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 literal 0 HcmV?d00001 diff --git a/doc/windowspecific/tbird-main-info.png b/doc/windowspecific/tbird-main-info.png new file mode 100644 index 0000000000000000000000000000000000000000..c151bb7b6fb9063d65174dba159542c98b897de8 GIT binary patch literal 36343 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 Ai2wiq literal 0 HcmV?d00001 diff --git a/doc/windowspecific/tbird-reminder-info.png b/doc/windowspecific/tbird-reminder-info.png new file mode 100644 index 0000000000000000000000000000000000000000..7b764cd5e6a04e44644b853e5dc70565063ad8b1 GIT binary patch literal 36154 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 literal 0 HcmV?d00001 diff --git a/doc/windowspecific/window-matching-emacs.png b/doc/windowspecific/window-matching-emacs.png new file mode 100644 index 0000000000000000000000000000000000000000..e0f1ad76c31a0b9f6256600b0079e3bbb47c0bbe GIT binary patch literal 55919 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

    Hier kan jy venster instellings " +"pasmaakspesifiek net vir skeer vensters.

    Neem asseblief kennis dat hierdie " +"konfigurasie nie in werking sal tree as jy nieKWIn as jou venster bestuurder " +"gebruik nie. As jy wel 'n ander venster bestuurder gebruik,verwys asseblief " +"na hierdie dokumentasie hoe om venster gedrag te pasmaak" + +#: kcmrules.cpp:243 +#, kde-format +msgid "Copy of %1" +msgstr "" + +#: kcmrules.cpp:422 +#, kde-format +msgid "Application settings for %1" +msgstr "Toepassings instellings vir %1" + +#: kcmrules.cpp:442 rulesmodel.cpp:215 +#, kde-format +msgid "Window settings for %1" +msgstr "Venster instellings vir %1" + +#: main.cpp:31 +#, kde-format +msgid "KWinRules KCM launcher" +msgstr "" + +#: main.cpp:32 +#, kde-format +msgid "KWin id of the window for special window settings." +msgstr "" + +#: main.cpp:33 +#, kde-format +msgid "Whether the settings should affect all windows of the application." +msgstr "" + +#: main.cpp:40 +#, kde-format +msgid "This helper utility is not supposed to be called directly." +msgstr "" +"Hierdie helper nutsprogramme is nie veronderstel om direk geroep te word nie" + +#: main.cpp:44 +#, fuzzy, kde-format +#| msgid "Edit Window-Specific Settings" +msgctxt "Window caption for the application wide rules dialog" +msgid "Edit Application-Specific Settings" +msgstr "Redigeer Venster-Spesifieke Instellings" + +#: main.cpp:45 +#, kde-format +msgid "Edit Window-Specific Settings" +msgstr "Redigeer Venster-Spesifieke Instellings" + +#: optionsmodel.cpp:198 +#, kde-format +msgid "Unimportant" +msgstr "Onbelangrik" + +#: optionsmodel.cpp:199 +#, kde-format +msgid "Exact Match" +msgstr "Presiese Pasmaat" + +#: optionsmodel.cpp:200 +#, kde-format +msgid "Substring Match" +msgstr "Substring Pasmaat" + +#: optionsmodel.cpp:201 +#, kde-format +msgid "Regular Expression" +msgstr "Gewone Uitdrukking" + +#: optionsmodel.cpp:205 +#, kde-format +msgid "Apply Initially" +msgstr "Pas Aanvanklik Toe" + +#: optionsmodel.cpp:206 +#, kde-format +msgid "" +"The window property will be only set to the given value after the window is " +"created.\n" +"No further changes will be affected." +msgstr "" + +#: optionsmodel.cpp:209 +#, kde-format +msgid "Apply Now" +msgstr "Pas Nou Toe" + +#: optionsmodel.cpp:210 +#, kde-format +msgid "" +"The window property will be set to the given value immediately and will not " +"be affected later\n" +"(this action will be deleted afterwards)." +msgstr "" + +#: optionsmodel.cpp:213 +#, kde-format +msgid "Remember" +msgstr "Herroep" + +#: optionsmodel.cpp:214 +#, kde-format +msgid "" +"The value of the window property will be remembered and, every time the " +"window is created, the last remembered value will be applied." +msgstr "" + +#: optionsmodel.cpp:217 +#, kde-format +msgid "Do Not Affect" +msgstr "Het Geen Effek" + +#: optionsmodel.cpp:218 +#, kde-format +msgid "" +"The window property will not be affected and therefore the default handling " +"for it will be used.\n" +"Specifying this will block more generic window settings from taking effect." +msgstr "" + +#: optionsmodel.cpp:221 +#, kde-format +msgid "Force" +msgstr "Forseer" + +#: optionsmodel.cpp:222 +#, kde-format +msgid "The window property will be always forced to the given value." +msgstr "" + +#: optionsmodel.cpp:224 +#, kde-format +msgid "Force Temporarily" +msgstr "Forseer Tydelik" + +#: optionsmodel.cpp:225 +#, kde-format +msgid "" +"The window property will be forced to the given value until it is hidden\n" +"(this action will be deleted after the window is hidden)." +msgstr "" + +#: package/contents/ui/FileDialogLoader.qml:14 +#, kde-format +msgid "Select File" +msgstr "" + +#: package/contents/ui/FileDialogLoader.qml:26 +#, kde-format +msgid "KWin Rules (*.kwinrule)" +msgstr "" + +#: package/contents/ui/main.qml:59 +#, kde-format +msgid "No rules for specific windows are currently set" +msgstr "" + +#: package/contents/ui/main.qml:60 +#, kde-kuit-format +msgctxt "@info" +msgid "Click the Add New... button below to add some" +msgstr "" + +#: package/contents/ui/main.qml:68 +#, kde-format +msgid "Select the rules to export" +msgstr "" + +#: package/contents/ui/main.qml:72 +#, kde-format +msgid "Unselect All" +msgstr "" + +#: package/contents/ui/main.qml:72 +#, kde-format +msgid "Select All" +msgstr "" + +#: package/contents/ui/main.qml:86 +#, kde-format +msgid "Save Rules" +msgstr "" + +#: package/contents/ui/main.qml:97 +#, fuzzy, kde-format +#| msgid "&New..." +msgid "Add New..." +msgstr "Nuwe..." + +#: package/contents/ui/main.qml:108 +#, kde-format +msgid "Import..." +msgstr "" + +#: package/contents/ui/main.qml:116 +#, kde-format +msgid "Cancel Export" +msgstr "" + +#: package/contents/ui/main.qml:116 +#, fuzzy, kde-format +#| msgid "Edit..." +msgid "Export..." +msgstr "Redigeer..." + +#: package/contents/ui/main.qml:206 +#, fuzzy, kde-format +msgid "Edit" +msgstr "Redigeer..." + +#: package/contents/ui/main.qml:215 +#, kde-format +msgid "Duplicate" +msgstr "" + +#: package/contents/ui/main.qml:224 +#, fuzzy, kde-format +msgid "Delete" +msgstr "Ondersoek" + +#: package/contents/ui/main.qml:237 +#, kde-format +msgid "Import Rules" +msgstr "" + +#: package/contents/ui/main.qml:249 +#, kde-format +msgid "Export Rules" +msgstr "" + +#: package/contents/ui/OptionsComboBox.qml:35 +#, kde-format +msgid "None selected" +msgstr "" + +#: package/contents/ui/OptionsComboBox.qml:41 +#, kde-format +msgid "All selected" +msgstr "" + +#: package/contents/ui/OptionsComboBox.qml:43 +#, kde-format +msgid "%1 selected" +msgid_plural "%1 selected" +msgstr[0] "" +msgstr[1] "" + +#: package/contents/ui/RulesEditor.qml:63 +#, fuzzy, kde-format +#| msgid "Detect Window Properties" +msgid "No window properties changed" +msgstr "Ondersoek Venster eienskappe" + +#: package/contents/ui/RulesEditor.qml:64 +#, kde-kuit-format +msgctxt "@info" +msgid "" +"Click the Add Property... button below to add some " +"window properties that will be affected by the rule" +msgstr "" + +#: package/contents/ui/RulesEditor.qml:85 +#, fuzzy, kde-format +#| msgid "&Closeable" +msgid "Close" +msgstr "Toemaakbaar" + +#: package/contents/ui/RulesEditor.qml:85 +#, fuzzy, kde-format +#| msgid "&New..." +msgid "Add Property..." +msgstr "Nuwe..." + +#: package/contents/ui/RulesEditor.qml:98 +#, fuzzy, kde-format +#| msgid "Detect Window Properties" +msgid "Detect Window Properties" +msgstr "Ondersoek Venster eienskappe" + +#: package/contents/ui/RulesEditor.qml:114 +#: package/contents/ui/RulesEditor.qml:121 +#, kde-format +msgid "Instantly" +msgstr "" + +#: package/contents/ui/RulesEditor.qml:115 +#: package/contents/ui/RulesEditor.qml:126 +#, kde-format +msgid "After %1 second" +msgid_plural "After %1 seconds" +msgstr[0] "" +msgstr[1] "" + +#: package/contents/ui/RulesEditor.qml:147 +#, kde-format +msgid "Error" +msgstr "" + +#: package/contents/ui/RulesEditor.qml:162 +#, kde-format +msgid "Add property to the rule" +msgstr "" + +#: package/contents/ui/RulesEditor.qml:260 +#: package/contents/ui/ValueEditor.qml:54 +#, kde-format +msgid "Yes" +msgstr "" + +#: package/contents/ui/RulesEditor.qml:260 +#: package/contents/ui/ValueEditor.qml:60 +#, fuzzy, kde-format +#| msgid "None" +msgid "No" +msgstr "Geen" + +#: package/contents/ui/RulesEditor.qml:262 +#: package/contents/ui/ValueEditor.qml:171 +#: package/contents/ui/ValueEditor.qml:178 +#, kde-format +msgid "%1 %" +msgstr "" + +#: package/contents/ui/RulesEditor.qml:264 +#, kde-format +msgctxt "Coordinates (x, y)" +msgid "(%1, %2)" +msgstr "" + +#: package/contents/ui/RulesEditor.qml:266 +#, kde-format +msgctxt "Size (width, height)" +msgid "(%1, %2)" +msgstr "" + +#: package/contents/ui/ValueEditor.qml:206 +#, kde-format +msgctxt "(x, y) coordinates separator in size/position" +msgid "x" +msgstr "" + +#: rulesmodel.cpp:218 +#, kde-format +msgid "Settings for %1" +msgstr "Instellings vir %1" + +#: rulesmodel.cpp:221 +#, fuzzy, kde-format +#| msgid "Window settings for %1" +msgid "New window settings" +msgstr "Venster instellings vir %1" + +#: rulesmodel.cpp:237 +#, kde-format +msgid "" +"You have specified the window class as unimportant.\n" +"This means the settings will possibly apply to windows from all " +"applications. If you really want to create a generic setting, it is " +"recommended you at least limit the window types to avoid special window " +"types." +msgstr "" +"Jy het die venster klas as onbelangrik gespesifiseer.\n" +"Dit beteken dat die instellings moontlik op alle toepassings van toepassing " +"sal wees.as jy regtig 'n generiese instelling wil skep, word dit aanbeveel " +"dat jy ten minste die venster tipes beperk om sodoende spesiale venster " +"tipes te voorkom." + +#: rulesmodel.cpp:244 +#, kde-format +msgid "" +"Some applications set their own geometry after starting, overriding your " +"initial settings for size and position. To enforce these settings, also " +"force the property \"%1\" to \"Yes\"." +msgstr "" + +#: rulesmodel.cpp:359 +#, fuzzy, kde-format +#| msgid "De&scription:" +msgid "Description" +msgstr "Beskrywing" + +#: rulesmodel.cpp:359 rulesmodel.cpp:367 rulesmodel.cpp:375 rulesmodel.cpp:382 +#: rulesmodel.cpp:388 rulesmodel.cpp:396 rulesmodel.cpp:401 rulesmodel.cpp:407 +#, fuzzy, kde-format +#| msgid "&Window" +msgid "Window matching" +msgstr "Venster" + +#: rulesmodel.cpp:367 +#, fuzzy, kde-format +#| msgid "Window &class (application type):" +msgid "Window class (application)" +msgstr "Venster klas (toepassing tipe):" + +#: rulesmodel.cpp:375 +#, fuzzy, kde-format +#| msgid "Match w&hole window class" +msgid "Match whole window class" +msgstr "Pas volldegie venster klas" + +#: rulesmodel.cpp:382 +#, fuzzy, kde-format +#| msgid "Match w&hole window class" +msgid "Whole window class" +msgstr "Pas volldegie venster klas" + +#: rulesmodel.cpp:388 +#, fuzzy, kde-format +#| msgid "Window &types:" +msgid "Window types" +msgstr "Venster Tipes:" + +#: rulesmodel.cpp:396 +#, fuzzy, kde-format +#| msgid "Window &role:" +msgid "Window role" +msgstr "Venster rol:" + +#: rulesmodel.cpp:401 +#, fuzzy, kde-format +#| msgid "Window t&itle:" +msgid "Window title" +msgstr "Venster titel:" + +#: rulesmodel.cpp:407 +#, fuzzy, kde-format +#| msgid "&Machine (hostname):" +msgid "Machine (hostname)" +msgstr "Masjien (gasheer-naam):" + +#: rulesmodel.cpp:413 +#, fuzzy, kde-format +#| msgid "&Position" +msgid "Position" +msgstr "Posisie" + +#: rulesmodel.cpp:413 rulesmodel.cpp:419 rulesmodel.cpp:425 rulesmodel.cpp:430 +#: rulesmodel.cpp:438 rulesmodel.cpp:444 rulesmodel.cpp:463 rulesmodel.cpp:479 +#: rulesmodel.cpp:484 rulesmodel.cpp:489 rulesmodel.cpp:494 rulesmodel.cpp:499 +#: rulesmodel.cpp:506 rulesmodel.cpp:516 rulesmodel.cpp:521 rulesmodel.cpp:526 +#, fuzzy, kde-format +#| msgid "&Position" +msgid "Size & Position" +msgstr "Posisie" + +#: rulesmodel.cpp:419 +#, fuzzy, kde-format +#| msgid "&Size" +msgid "Size" +msgstr "Grootte" + +#: rulesmodel.cpp:425 +#, fuzzy, kde-format +#| msgid "Maximized &horizontally" +msgid "Maximized horizontally" +msgstr "Horisontaal Gemaksimeer" + +#: rulesmodel.cpp:430 +#, fuzzy, kde-format +#| msgid "Maximized &vertically" +msgid "Maximized vertically" +msgstr "Vertikaal Gemaksimeer" + +#: rulesmodel.cpp:438 +#, fuzzy, kde-format +#| msgid "All Desktops" +msgid "Virtual Desktop" +msgstr "Alle Werkskerms" + +#: rulesmodel.cpp:444 +#, fuzzy, kde-format +#| msgid "All Desktops" +msgid "Virtual Desktops" +msgstr "Alle Werkskerms" + +#: rulesmodel.cpp:463 +#, fuzzy, kde-format +#| msgid "A&ctive opacity in %" +msgid "Activities" +msgstr "Aktiewe opacity in %" + +#: rulesmodel.cpp:479 +#, fuzzy, kde-format +#| msgid "Splash Screen" +msgid "Screen" +msgstr "Spat Skerm" + +#: rulesmodel.cpp:484 +#, fuzzy, kde-format +#| msgid "&Fullscreen" +msgid "Fullscreen" +msgstr "Volskerm" + +#: rulesmodel.cpp:489 +#, fuzzy, kde-format +#| msgid "M&inimized" +msgid "Minimized" +msgstr "Geminimiseer" + +#: rulesmodel.cpp:494 +#, fuzzy, kde-format +#| msgid "Sh&aded" +msgid "Shaded" +msgstr "Geskakeer" + +#: rulesmodel.cpp:499 +#, fuzzy, kde-format +#| msgid "P&lacement" +msgid "Initial placement" +msgstr "Plasing" + +#: rulesmodel.cpp:506 +#, fuzzy, kde-format +#| msgid "Ignore requested &geometry" +msgid "Ignore requested geometry" +msgstr "Ignoreer aangevraagde afmeting" + +#: rulesmodel.cpp:508 +#, kde-format +msgid "" +"Windows can ask to appear in a certain position.\n" +"By default this overrides the placement strategy\n" +"what might be nasty if the client abuses the feature\n" +"to unconditionally popup in the middle of your screen." +msgstr "" + +#: rulesmodel.cpp:516 +#, fuzzy, kde-format +#| msgid "M&inimum size" +msgid "Minimum Size" +msgstr "Minimum grootte" + +#: rulesmodel.cpp:521 +#, fuzzy, kde-format +#| msgid "M&aximum size" +msgid "Maximum Size" +msgstr "Maksimum grootte" + +#: rulesmodel.cpp:526 +#, kde-format +msgid "Obey geometry restrictions" +msgstr "" + +#: rulesmodel.cpp:528 +#, kde-format +msgid "" +"Eg. terminals or video players can ask to keep a certain aspect ratio\n" +"or only grow by values larger than one\n" +"(eg. by the dimensions of one character).\n" +"This may be pointless and the restriction prevents arbitrary dimensions\n" +"like your complete screen area." +msgstr "" + +#: rulesmodel.cpp:537 +#, kde-format +msgid "Keep above other windows" +msgstr "" + +#: rulesmodel.cpp:537 rulesmodel.cpp:542 rulesmodel.cpp:547 rulesmodel.cpp:553 +#: rulesmodel.cpp:559 rulesmodel.cpp:565 +#, kde-format +msgid "Arrangement & Access" +msgstr "" + +#: rulesmodel.cpp:542 +#, kde-format +msgid "Keep below other windows" +msgstr "" + +#: rulesmodel.cpp:547 +#, fuzzy, kde-format +#| msgid "Skip &taskbar" +msgid "Skip taskbar" +msgstr "Slaan taakbalk oor" + +#: rulesmodel.cpp:549 +#, kde-format +msgid "Window shall (not) appear in the taskbar." +msgstr "" + +#: rulesmodel.cpp:553 +#, fuzzy, kde-format +#| msgid "Skip pa&ger" +msgid "Skip pager" +msgstr "Slaan blaaier om" + +#: rulesmodel.cpp:555 +#, kde-format +msgid "Window shall (not) appear in the manager for virtual desktops" +msgstr "" + +#: rulesmodel.cpp:559 +#, fuzzy, kde-format +#| msgid "Skip pa&ger" +msgid "Skip switcher" +msgstr "Slaan blaaier om" + +#: rulesmodel.cpp:561 +#, kde-format +msgid "Window shall (not) appear in the Alt+Tab list" +msgstr "" + +#: rulesmodel.cpp:565 +#, kde-format +msgid "Shortcut" +msgstr "Kortpad" + +#: rulesmodel.cpp:571 +#, kde-format +msgid "No titlebar and frame" +msgstr "" + +#: rulesmodel.cpp:571 rulesmodel.cpp:576 rulesmodel.cpp:582 rulesmodel.cpp:587 +#: rulesmodel.cpp:592 rulesmodel.cpp:603 rulesmodel.cpp:614 rulesmodel.cpp:622 +#: rulesmodel.cpp:635 rulesmodel.cpp:640 rulesmodel.cpp:646 rulesmodel.cpp:651 +#, kde-format +msgid "Appearance & Fixes" +msgstr "" + +#: rulesmodel.cpp:576 +#, kde-format +msgid "Titlebar color scheme" +msgstr "" + +#: rulesmodel.cpp:582 +#, fuzzy, kde-format +#| msgid "A&ctive opacity in %" +msgid "Active opacity" +msgstr "Aktiewe opacity in %" + +#: rulesmodel.cpp:587 +#, fuzzy, kde-format +#| msgid "I&nactive opacity in %" +msgid "Inactive opacity" +msgstr "Onaktiewe opacity in %" + +#: rulesmodel.cpp:592 +#, fuzzy, kde-format +#| msgid "&Focus stealing prevention" +msgid "Focus stealing prevention" +msgstr "Fokus diefstal voorkoming" + +#: rulesmodel.cpp:594 +#, kde-format +msgid "" +"KWin tries to prevent windows from taking the focus\n" +"(\"activate\") while you're working in another window,\n" +"but this may sometimes fail or superact.\n" +"\"None\" will unconditionally allow this window to get the focus while\n" +"\"Extreme\" will completely prevent it from taking the focus." +msgstr "" + +#: rulesmodel.cpp:603 +#, fuzzy, kde-format +#| msgid "&Focus stealing prevention" +msgid "Focus protection" +msgstr "Fokus diefstal voorkoming" + +#: rulesmodel.cpp:605 +#, kde-format +msgid "" +"This controls the focus protection of the currently active window.\n" +"None will always give the focus away,\n" +"Extreme will keep it.\n" +"Otherwise it's interleaved with the stealing prevention\n" +"assigned to the window that wants the focus." +msgstr "" + +#: rulesmodel.cpp:614 +#, fuzzy, kde-format +#| msgid "Accept &focus" +msgid "Accept focus" +msgstr "Aanvaar fokus" + +#: rulesmodel.cpp:616 +#, kde-format +msgid "" +"Windows may prevent to get the focus (activate) when being clicked.\n" +"On the other hand you might wish to prevent a window\n" +"from getting focused on a mouse click." +msgstr "" + +#: rulesmodel.cpp:622 +#, fuzzy, kde-format +#| msgid "Block global shortcuts" +msgid "Ignore global shortcuts" +msgstr "Blokeer globale kortpaaie" + +#: rulesmodel.cpp:624 +#, kde-format +msgid "" +"When used, a window will receive\n" +"all keyboard inputs while it is active, including Alt+Tab etc.\n" +"This is especially interesting for emulators or virtual machines.\n" +"\n" +"Be warned:\n" +"you won't be able to Alt+Tab out of the window\n" +"nor use any other global shortcut (such as Alt+F2 to show KRunner)\n" +"while it's active!" +msgstr "" + +#: rulesmodel.cpp:635 +#, fuzzy, kde-format +#| msgid "&Closeable" +msgid "Closeable" +msgstr "Toemaakbaar" + +#: rulesmodel.cpp:640 +#, fuzzy, kde-format +#| msgid "Window &type" +msgid "Set window type" +msgstr "Venster tipe" + +#: rulesmodel.cpp:646 +#, kde-format +msgid "Desktop file name" +msgstr "" + +#: rulesmodel.cpp:651 +#, kde-format +msgid "Block compositing" +msgstr "" + +#: rulesmodel.cpp:727 +#, fuzzy, kde-format +#| msgid "Window &types:" +msgid "All Window Types" +msgstr "Venster Tipes:" + +#: rulesmodel.cpp:728 +#, kde-format +msgid "Normal Window" +msgstr "Normale Venster" + +#: rulesmodel.cpp:729 +#, kde-format +msgid "Dialog Window" +msgstr "Dialoog Venster" + +#: rulesmodel.cpp:730 +#, kde-format +msgid "Utility Window" +msgstr "Nuts Venster" + +#: rulesmodel.cpp:731 +#, kde-format +msgid "Dock (panel)" +msgstr "Meer vas (paneel)" + +#: rulesmodel.cpp:732 +#, kde-format +msgid "Toolbar" +msgstr "Nutsbalk" + +#: rulesmodel.cpp:733 +#, kde-format +msgid "Torn-Off Menu" +msgstr "Afskeur kieslys" + +#: rulesmodel.cpp:734 +#, kde-format +msgid "Splash Screen" +msgstr "Spat Skerm" + +#: rulesmodel.cpp:735 +#, kde-format +msgid "Desktop" +msgstr "Werkskerm" + +#. i18n("Unmanaged Window")}, deprecated +#: rulesmodel.cpp:737 +#, kde-format +msgid "Standalone Menubar" +msgstr "Alleenstaande Kiesbalk" + +#: rulesmodel.cpp:738 +#, kde-format +msgid "On Screen Display" +msgstr "" + +#: rulesmodel.cpp:748 +#, kde-format +msgid "All Desktops" +msgstr "Alle Werkskerms" + +#: rulesmodel.cpp:750 +#, kde-format +msgctxt "@info:tooltip in the virtual desktop list" +msgid "Make the window available on all desktops" +msgstr "" + +#: rulesmodel.cpp:769 +#, kde-format +msgid "All Activities" +msgstr "" + +#: rulesmodel.cpp:771 +#, kde-format +msgctxt "@info:tooltip in the activity list" +msgid "Make the window available on all activities" +msgstr "" + +#: rulesmodel.cpp:792 +#, kde-format +msgid "Default" +msgstr "" + +#: rulesmodel.cpp:793 +#, kde-format +msgid "No Placement" +msgstr "Geen Plasing" + +#: rulesmodel.cpp:794 +#, kde-format +msgid "Minimal Overlapping" +msgstr "" + +#: rulesmodel.cpp:795 +#, fuzzy, kde-format +#| msgid "Maximizing" +msgid "Maximized" +msgstr "Maksimerend" + +#: rulesmodel.cpp:796 +#, fuzzy, kde-format +#| msgid "Cascade" +msgid "Cascaded" +msgstr "Kaskade" + +#: rulesmodel.cpp:797 +#, kde-format +msgid "Centered" +msgstr "In die middel" + +#: rulesmodel.cpp:798 +#, kde-format +msgid "Random" +msgstr "Lukraak" + +#: rulesmodel.cpp:799 +#, fuzzy, kde-format +#| msgid "Top-Left Corner" +msgid "In Top-Left Corner" +msgstr "Boonste-linker hoek" + +#: rulesmodel.cpp:800 +#, kde-format +msgid "Under Mouse" +msgstr "Onder Muis" + +#: rulesmodel.cpp:801 +#, kde-format +msgid "On Main Window" +msgstr "Op Hoof Venster" + +#: rulesmodel.cpp:808 +#, fuzzy, kde-format +#| msgid "None" +msgid "None" +msgstr "Geen" + +#: rulesmodel.cpp:809 +#, kde-format +msgid "Low" +msgstr "Laag" + +#: rulesmodel.cpp:810 +#, kde-format +msgid "Normal" +msgstr "Normaal" + +#: rulesmodel.cpp:811 +#, kde-format +msgid "High" +msgstr "Hoog" + +#: rulesmodel.cpp:812 +#, kde-format +msgid "Extreme" +msgstr "Buitensporige" + +#: rulesmodel.cpp:855 +#, kde-format +msgid "Could not detect window properties. The window is not managed by KWin." +msgstr "" \ No newline at end of file diff --git a/po/af/kcmkwm.po b/po/af/kcmkwm.po new file mode 100644 index 0000000..f93c3bb --- /dev/null +++ b/po/af/kcmkwm.po @@ -0,0 +1,1438 @@ +# UTF-8 test:äëïöü +# Copyright (C) 2001 Free Software Foundation, Inc. +# Kobus Venter , 2005. +# +msgid "" +msgstr "" +"Project-Id-Version: kcmkwm stable\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2022-09-02 00:47+0000\n" +"PO-Revision-Date: 2005-11-25 19:56+0200\n" +"Last-Translator: Kobus Venter \n" +"Language-Team: AFRIKAANS \n" +"Language: af\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#, kde-format +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Kobus Venter" + +#, kde-format +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "kabousv@therugby.co.za" + +#. i18n: ectx: property (title), widget (QGroupBox, groupBox_1) +#: actions.ui:17 +#, fuzzy, kde-format +#| msgid "Inactive Inner Window" +msgid "Inactive Inner Window Actions" +msgstr "Onaktiewe Binneste Venster" + +#. i18n: ectx: property (text), widget (QLabel, label_1) +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: actions.ui:26 mouse.ui:177 +#, fuzzy, kde-format +#| msgid "&Titlebar double-click:" +msgid "&Left click:" +msgstr "Titelbalk dubbel-kliek:" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandWindow1) +#: actions.ui:39 +#, kde-format +msgid "" +"In this row you can customize left click behavior when clicking into an " +"inactive inner window ('inner' means: not titlebar, not frame)." +msgstr "" +"In hierdie ry kan jy die linker kliek gedrag pasmaak vir wanneer jy kliek " +"binne 'n onaktiewe binneste venster ('binneste' beteken: nie titelbalk, nie " +"raam)." + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow3) +#: actions.ui:43 actions.ui:83 actions.ui:123 +#, fuzzy, kde-format +#| msgid "Activate, Raise & Pass Click" +msgid "Activate, raise and pass click" +msgstr "Aktiveer, Lig & Herhaling Kliek" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow3) +#: actions.ui:48 actions.ui:88 actions.ui:128 +#, fuzzy, kde-format +#| msgid "Activate & Pass Click" +msgid "Activate and pass click" +msgstr "Aktiveer & Herhaling Kliek" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar3) +#: actions.ui:53 actions.ui:93 actions.ui:133 mouse.ui:293 mouse.ui:408 +#: mouse.ui:523 +#, kde-format +msgid "Activate" +msgstr "Aktiveer" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar3) +#: actions.ui:58 actions.ui:98 actions.ui:138 mouse.ui:283 mouse.ui:398 +#: mouse.ui:513 +#, fuzzy, kde-format +#| msgid "Activate & Raise" +msgid "Activate and raise" +msgstr "Aktiveer & Lig" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: actions.ui:66 mouse.ui:200 +#, fuzzy, kde-format +#| msgid "&Titlebar double-click:" +msgid "&Middle click:" +msgstr "Titelbalk dubbel-kliek:" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandWindow2) +#: actions.ui:79 +#, kde-format +msgid "" +"In this row you can customize middle click behavior when clicking into an " +"inactive inner window ('inner' means: not titlebar, not frame)." +msgstr "" +"In hierdie ry kan jy die middelste kliek gedrag pasmaak vir wanneer jy kliek " +"binne 'n onaktiewe binneste venster ('binneste' beteken: nie titelbalk, nie " +"raam)." + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: actions.ui:106 mouse.ui:213 +#, kde-format +msgid "&Right click:" +msgstr "" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandWindow3) +#: actions.ui:119 +#, kde-format +msgid "" +"In this row you can customize right click behavior when clicking into an " +"inactive inner window ('inner' means: not titlebar, not frame)." +msgstr "" +"In hierdie ry kan jy die regter kliek gedrag pasmaak vir wanneer jy kliek " +"binne 'n onaktiewe binneste venster ('binneste' beteken: nie titelbalk, nie " +"raam)." + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: actions.ui:146 mouse.ui:88 +#, fuzzy, kde-format +#| msgid "Modifier key + mouse wheel:" +msgid "Mouse &wheel:" +msgstr "Modifikasie sleutel + muis wiel:" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandWindowWheel) +#: actions.ui:159 +#, fuzzy, kde-format +#| msgid "" +#| "In this row you can customize left click behavior when clicking into an " +#| "inactive inner window ('inner' means: not titlebar, not frame)." +msgid "" +"In this row you can customize behavior when scrolling into an inactive inner " +"window ('inner' means: not titlebar, not frame)." +msgstr "" +"In hierdie ry kan jy die linker kliek gedrag pasmaak vir wanneer jy kliek " +"binne 'n onaktiewe binneste venster ('binneste' beteken: nie titelbalk, nie " +"raam)." + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindowWheel) +#: actions.ui:163 +#, kde-format +msgid "Scroll" +msgstr "" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindowWheel) +#: actions.ui:168 +#, fuzzy, kde-format +#| msgid "Activate & Lower" +msgid "Activate and scroll" +msgstr "Aktiveer & Verlaag" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindowWheel) +#: actions.ui:173 +#, fuzzy, kde-format +#| msgid "Activate, Raise and Move" +msgid "Activate, raise and scroll" +msgstr "Aktiveer, Lig & Skuif" + +#. i18n: ectx: property (title), widget (QGroupBox, groupBox_2) +#: actions.ui:184 +#, fuzzy, kde-format +#| msgid "Inner Window, Titlebar && Frame" +msgid "Inner Window, Titlebar and Frame Actions" +msgstr "Binneste Venster, Titelbalk && Raam" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: actions.ui:195 +#, fuzzy, kde-format +#| msgid "Modifier key:" +msgid "Mo&difier key:" +msgstr "Verander sleutel:" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandAllKey) +#: actions.ui:205 +#, kde-format +msgid "" +"Here you select whether holding the Meta key or Alt key will allow you to " +"perform the following actions." +msgstr "" +"Hier kan jy kies of die inhou van die Meta sleutel of Alt sleutel jou sal " +"toelaat om die volgende aksies uit te voer." + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAllKey) +#: actions.ui:209 +#, kde-format +msgid "Meta" +msgstr "Meta" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAllKey) +#: actions.ui:214 +#, kde-format +msgid "Alt" +msgstr "Alt" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: actions.ui:236 +#, kde-format +msgid " + " +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: actions.ui:248 mouse.ui:601 +#, fuzzy, kde-format +#| msgid "&Titlebar double-click:" +msgid "L&eft click:" +msgstr "Titelbalk dubbel-kliek:" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandAll1) +#: actions.ui:261 +#, kde-format +msgid "" +"In this row you can customize left click behavior when clicking into the " +"titlebar or the frame." +msgstr "" +"In hierdie ry jy kan linkskliek gedrag pasmaak wanneer gekliek word binne " +"die titelbalk of die raam." + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll3) +#: actions.ui:265 actions.ui:335 actions.ui:405 +#, kde-format +msgid "Move" +msgstr "" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll3) +#: actions.ui:270 actions.ui:340 actions.ui:410 +#, fuzzy, kde-format +#| msgid "Activate, Raise and Move" +msgid "Activate, raise and move" +msgstr "Aktiveer, Lig & Skuif" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar3) +#: actions.ui:275 actions.ui:345 actions.ui:415 mouse.ui:246 mouse.ui:308 +#: mouse.ui:361 mouse.ui:423 mouse.ui:476 mouse.ui:538 +#, fuzzy, kde-format +#| msgid "Toggle Raise & Lower" +msgid "Toggle raise and lower" +msgstr "Wissel Lig & Verlaag" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll3) +#: actions.ui:280 actions.ui:350 actions.ui:420 +#, kde-format +msgid "Resize" +msgstr "Hervergroot" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar3) +#: actions.ui:285 actions.ui:355 actions.ui:425 mouse.ui:236 mouse.ui:298 +#: mouse.ui:351 mouse.ui:413 mouse.ui:466 mouse.ui:528 +#, kde-format +msgid "Raise" +msgstr "Lig" + +#. i18n: @item:inlistbox behavior on double click +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_TitlebarDoubleClickCommand) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar3) +#: actions.ui:290 actions.ui:360 actions.ui:430 mouse.ui:65 mouse.ui:241 +#: mouse.ui:303 mouse.ui:356 mouse.ui:418 mouse.ui:471 mouse.ui:533 +#, kde-format +msgid "Lower" +msgstr "Verlaag" + +#. i18n: @item:inlistbox behavior on double click +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_TitlebarDoubleClickCommand) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar3) +#: actions.ui:295 actions.ui:365 actions.ui:435 mouse.ui:55 mouse.ui:251 +#: mouse.ui:313 mouse.ui:366 mouse.ui:428 mouse.ui:481 mouse.ui:543 +#, kde-format +msgid "Minimize" +msgstr "Verklein" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll3) +#: actions.ui:300 actions.ui:370 actions.ui:440 +#, fuzzy, kde-format +#| msgid "Change Opacity" +msgid "Decrease opacity" +msgstr "Verander Opacity" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll3) +#: actions.ui:305 actions.ui:375 actions.ui:445 +#, fuzzy, kde-format +#| msgid "Change Opacity" +msgid "Increase opacity" +msgstr "Verander Opacity" + +#. i18n: @item:inlistbox behavior on double click +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAllWheel) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_TitlebarDoubleClickCommand) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandTitlebarWheel) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar3) +#: actions.ui:310 actions.ui:380 actions.ui:450 actions.ui:505 mouse.ui:80 +#: mouse.ui:132 mouse.ui:271 mouse.ui:333 mouse.ui:386 mouse.ui:448 +#: mouse.ui:501 mouse.ui:563 +#, fuzzy, kde-format +#| msgid "Nothing" +msgid "Do nothing" +msgstr "Niks" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: actions.ui:318 +#, fuzzy, kde-format +#| msgid "Middle button:" +msgid "Middle &click:" +msgstr "Middelste knoppie:" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandAll2) +#: actions.ui:331 +#, kde-format +msgid "" +"In this row you can customize middle click behavior when clicking into the " +"titlebar or the frame." +msgstr "" +"In hierdie ry jy kan middelste kliek gedrag pasmaak wanneer gekliek word " +"binne die titelbalk of die raam." + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#. i18n: ectx: property (text), widget (QLabel, label_10) +#: actions.ui:388 mouse.ui:671 +#, kde-format +msgid "Right clic&k:" +msgstr "" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandAll3) +#: actions.ui:401 +#, kde-format +msgid "" +"In this row you can customize right click behavior when clicking into the " +"titlebar or the frame." +msgstr "" +"In hierdie ry jy kan regterkant kliek gedrag pasmaak wanneer gekliek word " +"binne die titelbalk of die raam." + +#. i18n: ectx: property (text), widget (QLabel, label_10) +#: actions.ui:458 +#, fuzzy, kde-format +#| msgid "Modifier key + mouse wheel:" +msgid "Mo&use wheel:" +msgstr "Modifikasie sleutel + muis wiel:" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandAllWheel) +#: actions.ui:471 +#, fuzzy, kde-format +#| msgid "" +#| "Here you can customize KDE's behavior when scrolling with the mouse " +#| "wheel in a window while pressing the modifier key." +msgid "" +"Here you can customize KDE's behavior when scrolling with the mouse wheel in " +"a window while pressing the modifier key." +msgstr "" +"Jy kan KDE se gedrag hier pasmaak vir wanneer die muis wiel binne 'n venster " +"gedraai word, terwyl 'n modifikasie sleutel gedruk word." + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAllWheel) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandTitlebarWheel) +#: actions.ui:475 mouse.ui:102 +#, fuzzy, kde-format +#| msgid "Raise/Lower" +msgid "Raise/lower" +msgstr "Lig && Verlaag" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAllWheel) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandTitlebarWheel) +#: actions.ui:480 mouse.ui:107 +#, fuzzy, kde-format +#| msgid "Shade/Unshade" +msgid "Shade/unshade" +msgstr "Skadu/Ontskadu" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAllWheel) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandTitlebarWheel) +#: actions.ui:485 mouse.ui:112 +#, fuzzy, kde-format +#| msgid "Maximize/Restore" +msgid "Maximize/restore" +msgstr "Maksimeer/Herstel" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAllWheel) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandTitlebarWheel) +#: actions.ui:490 mouse.ui:117 +#, fuzzy, kde-format +#| msgid "Keep Above/Below" +msgid "Keep above/below" +msgstr "Ho bo/onder" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAllWheel) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandTitlebarWheel) +#: actions.ui:495 mouse.ui:122 +#, fuzzy, kde-format +#| msgid "Move to Previous/Next Desktop" +msgid "Move to previous/next desktop" +msgstr "Gaan na Vorige/Volgende Werkskerm" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAllWheel) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandTitlebarWheel) +#: actions.ui:500 mouse.ui:127 +#, fuzzy, kde-format +#| msgid "Change Opacity" +msgid "Change opacity" +msgstr "Verander Opacity" + +#. i18n: ectx: property (text), widget (QLabel, shadeHoverLabel) +#: advanced.ui:20 +#, fuzzy, kde-format +#| msgid "Window Actio&ns" +msgid "Window &unshading:" +msgstr "Venster Aksies" + +#. i18n: ectx: property (whatsThis), widget (QCheckBox, kcfg_ShadeHover) +#: advanced.ui:32 +#, fuzzy, kde-format +#| msgid "" +#| "If Shade Hover is enabled, a shaded window will un-shade automatically " +#| "when the mouse pointer has been over the title bar for some time." +msgid "" +"

    If this option is enabled, a shaded window will " +"unshade automatically when the mouse pointer has been over the titlebar for " +"some time.

    " +msgstr "" +"As Skadu Sweef geaktiveer is, sal 'n beskadude venster automaties ontskadu " +"wanneer die muis wyser 'n geruime tyd oor die titel balk is." + +#. i18n: ectx: property (text), widget (QCheckBox, kcfg_ShadeHover) +#: advanced.ui:35 +#, kde-format +msgid "On titlebar hover after:" +msgstr "" + +#. i18n: ectx: property (whatsThis), widget (QSpinBox, kcfg_ShadeHoverInterval) +#: advanced.ui:42 +#, kde-format +msgid "" +"Sets the time in milliseconds before the window unshades when the mouse " +"pointer goes over the shaded window." +msgstr "" +"Verstel die tyd in millisekondes voordat die venster ontskadu wanneer die " +"muis wyser bo-oor die beskadude venster gaan." + +#. i18n: ectx: property (suffix), widget (QSpinBox, kcfg_DelayFocusInterval) +#. i18n: ectx: property (suffix), widget (QSpinBox, kcfg_AutoRaiseInterval) +#. i18n: ectx: property (suffix), widget (QSpinBox, kcfg_ShadeHoverInterval) +#: advanced.ui:45 focus.ui:85 focus.ui:178 +#, fuzzy, kde-format +#| msgid " msec" +msgid " ms" +msgstr " msek" + +#. i18n: ectx: property (text), widget (QLabel, windowPlacementLabel) +#: advanced.ui:66 +#, fuzzy, kde-format +#| msgid "&Placement:" +msgid "Window &placement:" +msgstr "Plasing:" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_Placement) +#: advanced.ui:76 +#, kde-format +msgid "" +"

    The placement policy determines where a new window " +"will appear on the desktop.

    • Smart will try to achieve a minimum overlap of windows
    • Maximizing will try to maximize every window to fill the " +"whole screen. It might be useful to selectively affect placement of some " +"windows using the window-specific settings.
    • Cascade will " +"cascade the windows
    • Random will use a random " +"position
    • Centered will place the window " +"centered
    • Zero-cornered will place the window in " +"the top-left corner
    • Under mouse will place the " +"window under the pointer
    " +msgstr "" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_Placement) +#: advanced.ui:80 +#, fuzzy, kde-format +#| msgid "Snap windows onl&y when overlapping" +msgid "Minimal Overlapping" +msgstr "Klamp vensters slegs wanneer oorvleuel" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_Placement) +#: advanced.ui:85 +#, fuzzy, kde-format +#| msgid "Maximize" +msgid "Maximized" +msgstr "Vergroot" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_Placement) +#: advanced.ui:90 +#, fuzzy, kde-format +#| msgid "Cascade" +msgid "Cascaded" +msgstr "Kaskade" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_Placement) +#: advanced.ui:95 +#, kde-format +msgid "Random" +msgstr "Lukrake" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_Placement) +#: advanced.ui:100 +#, kde-format +msgid "Centered" +msgstr "Gesentreer" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_Placement) +#: advanced.ui:105 +#, kde-format +msgid "In Top-Left Corner" +msgstr "" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_Placement) +#: advanced.ui:110 +#, fuzzy, kde-format +#| msgid "Focus Under Mouse" +msgid "Under mouse" +msgstr "Fokus Onder Muis" + +#. i18n: ectx: property (whatsThis), widget (QCheckBox, kcfg_AllowKDEAppsToRememberWindowPositions) +#: advanced.ui:118 +#, kde-format +msgid "" +"When turned on, apps which are able to remember the positions of their " +"windows are allowed to do so. This will override the window placement mode " +"defined above." +msgstr "" + +#. i18n: ectx: property (text), widget (QCheckBox, kcfg_AllowKDEAppsToRememberWindowPositions) +#: advanced.ui:121 +#, kde-format +msgid "" +"Allow apps to remember the positions of their own windows, if they support it" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, specialWindowsLabel) +#: advanced.ui:128 +#, fuzzy, kde-format +#| msgid "Windows" +msgid "&Special windows:" +msgstr "Vensters" + +#. i18n: ectx: property (whatsThis), widget (QCheckBox, kcfg_HideUtilityWindowsForInactive) +#: advanced.ui:138 +#, kde-format +msgid "" +"When turned on, utility windows (tool windows, torn-off menus,...) of " +"inactive applications will be hidden and will be shown only when the " +"application becomes active. Note that applications have to mark the windows " +"with the proper window type for this feature to work." +msgstr "" +"Wanneer hierdie aktief is, sal nutsvensters (gereedskap vensters, afskeur " +"kieslyste,...) van onaktiewe programme weg gesteek word. Hulle sal net gewys " +"word wanneer die program aktief word. Die program moet die vensters met die " +"regte tipe merk vir hierdie eienskap om te werk." + +#. i18n: ectx: property (text), widget (QCheckBox, kcfg_HideUtilityWindowsForInactive) +#: advanced.ui:141 +#, kde-format +msgid "Hide utility windows for inactive applications" +msgstr "Steek nutsvensters van onaktiewe programme weg" + +#. i18n: ectx: property (text), widget (QLabel, activationDesktopPolicyLabel) +#: advanced.ui:148 +#, kde-format +msgid "Virtual Desktop behavior:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, activationDesktopPolicyDescriptionLabel) +#: advanced.ui:158 +#, kde-format +msgid "When activating a window on a different Virtual Desktop:" +msgstr "" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_ActivationDesktopPolicy) +#: advanced.ui:165 +#, kde-format +msgid "" +"

    This setting controls what happens when an open window " +"located on a Virtual Desktop other than the current one is activated.

    Switch to that Virtual Desktop will switch to the Virtual Desktop where the window is currently " +"located.

    Bring window to current " +"Virtual Desktop will cause the window to jump to the active Virtual " +"Desktop.

    " +msgstr "" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_ActivationDesktopPolicy) +#: advanced.ui:169 +#, kde-format +msgid "Switch to that Virtual Desktop" +msgstr "" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_ActivationDesktopPolicy) +#: advanced.ui:174 +#, kde-format +msgid "Bring window to current Virtual Desktop" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, windowFocusPolicyLabel) +#: focus.ui:22 +#, kde-format +msgid "Window &activation policy:" +msgstr "" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, windowFocusPolicy) +#: focus.ui:32 +#, fuzzy, kde-format +#| msgid "" +#| "Enable this option if you want an animation shown when windows are " +#| "minimized or restored." +msgid "With this option you can specify how and when windows will be focused." +msgstr "" +"Aktiveer hierdie opsie as jy 'n animasie vertoon wil hê wanneer vensters " +"verklein herstel word." + +#. i18n: ectx: property (text), item, widget (QComboBox, windowFocusPolicy) +#: focus.ui:36 +#, fuzzy, kde-format +#| msgid "Click to Focus" +msgctxt "sassa asas" +msgid "Click to focus" +msgstr "Kliek om te Fokus" + +#. i18n: ectx: property (text), item, widget (QComboBox, windowFocusPolicy) +#: focus.ui:41 +#, kde-format +msgid "Click to focus (mouse precedence)" +msgstr "" + +#. i18n: ectx: property (text), item, widget (QComboBox, windowFocusPolicy) +#: focus.ui:46 +#, fuzzy, kde-format +#| msgid "Focus Follows Mouse" +msgid "Focus follows mouse" +msgstr "Fokus Volg Muis" + +#. i18n: ectx: property (text), item, widget (QComboBox, windowFocusPolicy) +#: focus.ui:51 +#, kde-format +msgid "Focus follows mouse (mouse precedence)" +msgstr "" + +#. i18n: ectx: property (text), item, widget (QComboBox, windowFocusPolicy) +#: focus.ui:56 +#, fuzzy, kde-format +#| msgid "Focus Under Mouse" +msgid "Focus under mouse" +msgstr "Fokus Onder Muis" + +#. i18n: ectx: property (text), item, widget (QComboBox, windowFocusPolicy) +#: focus.ui:61 +#, fuzzy, kde-format +#| msgid "Focus Strictly Under Mouse" +msgid "Focus strictly under mouse" +msgstr "Fokus Streng Onder Muis" + +#. i18n: ectx: property (text), widget (QLabel, delayFocusOnLabel) +#: focus.ui:69 +#, fuzzy, kde-format +#| msgid "Delay focus" +msgid "&Delay focus by:" +msgstr "Vertraag Fokus" + +#. i18n: ectx: property (whatsThis), widget (QSpinBox, kcfg_DelayFocusInterval) +#: focus.ui:82 +#, kde-format +msgid "" +"This is the delay after which the window the mouse pointer is over will " +"automatically receive focus." +msgstr "" +"Hierdie is die vertraging waarna die venster waaroor die muis wyser is " +"automaties op gefokus sal word." + +#. i18n: ectx: property (text), widget (QLabel, focusStealingLabel) +#: focus.ui:101 +#, fuzzy, kde-format +#| msgid "Focus stealing prevention level:" +msgid "Focus &stealing prevention:" +msgstr "Fokus steel voorkoming vlak" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_FocusStealingPreventionLevel) +#: focus.ui:114 +#, fuzzy, kde-format +#| msgid "" +#| "

    This option specifies how much KWin will try to prevent unwanted focus " +#| "stealing caused by unexpected activation of new windows. (Note: This " +#| "feature does not work with the Focus Under Mouse or Focus Strictly Under " +#| "Mouse focus policies.)

    • None: Prevention is turned off and " +#| "new windows always become activated.
    • Low: Prevention is " +#| "enabled; when some window does not have support for the underlying " +#| "mechanism and KWin cannot reliably decide whether to activate the window " +#| "or not, it will be activated. This setting may have both worse and better " +#| "results than normal level, depending on the applications.
    • Normal: Prevention is enabled.
    • High: New " +#| "windows get activated only if no window is currently active or if they " +#| "belong to the currently active application. This setting is probably not " +#| "really usable when not using mouse focus policy.
    • Extreme: All windows must be explicitly activated by the user.

    Windows that are prevented from stealing focus are marked as " +#| "demanding attention, which by default means their taskbar entry will be " +#| "highlighted. This can be changed in the Notifications control module.

    " +msgid "" +"

    This option specifies how much KWin will try to " +"prevent unwanted focus stealing caused by unexpected activation of new " +"windows. (Note: This feature does not work with the Focus under mouse or Focus strictly under mouse focus policies.)

    • None: Prevention is turned off and new " +"windows always become activated.
    • Low: Prevention is " +"enabled; when some window does not have support for the underlying mechanism " +"and KWin cannot reliably decide whether to activate the window or not, it " +"will be activated. This setting may have both worse and better results than " +"the medium level, depending on the applications.
    • Medium: Prevention is enabled.
    • High: New windows " +"get activated only if no window is currently active or if they belong to the " +"currently active application. This setting is probably not really usable " +"when not using mouse focus policy.
    • Extreme: All " +"windows must be explicitly activated by the user.

    Windows that " +"are prevented from stealing focus are marked as demanding attention, which " +"by default means their taskbar entry will be highlighted. This can be " +"changed in the Notifications control module.

    " +msgstr "" +"

    Hiedie opsie spesifiseer hoeveel KWin probeer om te keer dat nuwe " +"vensters die fokus steel.Hierdie opsie werk nie vir 'Fokus onder Muis' en " +"'Fokus streng onder Muis' beleid nie.

    • Geen:Voorkoming word " +"af gesit en nuwe vensters word altyd aktief
    • Laag:Voorkoming " +"is aktief. Vir vensters wat nie die onderliggende meganisme ondersteun nie " +"sal dit aktief gemaak word
    • Normaal:Voorkoming is aktief.
    • Hoog:Nuwe vensters sal geaktiveer word as dit aan die " +"huidige aktiewe program behoort. Dit sal nie geaktiveer word as 'n ander " +"venster aktief is nie. Hierdie opsie maak meeste sin saam met 'n muis fokus " +"beleid.
    • Ekstreem: Alle vensters moet spesifiek deur die " +"gebruiker geaktiveer word

    Vensters wat verhoed word om die " +"fokus te steel sal aandui dat hulle aandag benodig. Gewoonlik beteken dit " +"dat hulle taakbalk inskrywing verlig is. Dit kan in die Inkennisstelling " +"beheer module verander word.

    " + +#. i18n: Focus Stealing Prevention Level +#. i18n: ectx: property (specialValueText), widget (QSpinBox, kcfg_BorderSnapZone) +#. i18n: ectx: property (specialValueText), widget (QSpinBox, kcfg_WindowSnapZone) +#. i18n: ectx: property (specialValueText), widget (QSpinBox, kcfg_CenterSnapZone) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_FocusStealingPreventionLevel) +#: focus.ui:118 moving.ui:33 moving.ui:65 moving.ui:97 +#, fuzzy, kde-format +#| msgctxt "Focus Stealing Prevention Level" +#| msgid "None" +msgid "None" +msgstr "Geen" + +#. i18n: Focus Stealing Prevention Level +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_FocusStealingPreventionLevel) +#: focus.ui:123 +#, fuzzy, kde-format +#| msgctxt "Focus Stealing Prevention Level" +#| msgid "Low" +msgid "Low" +msgstr "Laag" + +#. i18n: Focus Stealing Prevention Level +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_FocusStealingPreventionLevel) +#: focus.ui:128 +#, kde-format +msgid "Medium" +msgstr "" + +#. i18n: Focus Stealing Prevention Level +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_FocusStealingPreventionLevel) +#: focus.ui:133 +#, fuzzy, kde-format +#| msgctxt "Focus Stealing Prevention Level" +#| msgid "High" +msgid "High" +msgstr "Hoog" + +#. i18n: Focus Stealing Prevention Level +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_FocusStealingPreventionLevel) +#: focus.ui:138 +#, fuzzy, kde-format +#| msgctxt "Focus Stealing Prevention Level" +#| msgid "Extreme" +msgid "Extreme" +msgstr "Uiters" + +#. i18n: ectx: property (text), widget (QLabel, raisingWindowsLabel) +#: focus.ui:146 +#, fuzzy, kde-format +#| msgid "Moving windows:" +msgid "Raising windows:" +msgstr "Skuif vensters" + +#. i18n: ectx: property (whatsThis), widget (QCheckBox, kcfg_ClickRaise) +#: focus.ui:153 +#, kde-format +msgid "" +"When this option is enabled, the active window will be brought to the front " +"when you click somewhere into the window contents. To change it for inactive " +"windows, you need to change the settings in the Actions tab." +msgstr "" +"Wanneer geaktiveer, sal die aktiewe venster na vore gebring word wanneer jy " +"êrens binne die venster inhoud kliek. Om dit verander na onaktiewe vensters, " +"moet jy dit stel in die Aksie oortjie." + +#. i18n: ectx: property (text), widget (QCheckBox, kcfg_ClickRaise) +#: focus.ui:156 +#, fuzzy, kde-format +#| msgid "C&lick raise active window" +msgid "&Click raises active window" +msgstr "Kliek lig aktiewe venster" + +#. i18n: ectx: property (whatsThis), widget (QCheckBox, kcfg_AutoRaise) +#: focus.ui:165 +#, kde-format +msgid "" +"When this option is enabled, a window in the background will automatically " +"come to the front when the mouse pointer has been over it for some time." +msgstr "" +"Wanneer hierdie opsie geaktiveer is, sal 'n venster in die agtergrond " +"automaties na vore kom wanneer die muis wyser bo-oor dit is vir 'n geruime " +"tyd." + +#. i18n: ectx: property (text), widget (QCheckBox, kcfg_AutoRaise) +#: focus.ui:168 +#, kde-format +msgid "&Raise on hover, delayed by:" +msgstr "" + +#. i18n: ectx: property (whatsThis), widget (QSpinBox, kcfg_AutoRaiseInterval) +#: focus.ui:175 +#, kde-format +msgid "" +"This is the delay after which the window that the mouse pointer is over will " +"automatically come to the front." +msgstr "" +"Hierdie is die vertraging waarna die venster waaroor die muis wyser is " +"automaties na vore sal kom." + +#. i18n: ectx: property (text), widget (QLabel, multiscreenBehaviorLabel) +#: focus.ui:196 +#, kde-format +msgid "Multiscreen behavior:" +msgstr "" + +#. i18n: ectx: property (whatsThis), widget (QCheckBox, kcfg_ActiveMouseScreen) +#: focus.ui:203 +#, kde-format +msgid "" +"When this option is enabled, the active screen (where new windows appear, " +"for example) is the screen containing the mouse pointer. When disabled, the " +"active screen is the screen containing the focused window." +msgstr "" + +#. i18n: ectx: property (text), widget (QCheckBox, kcfg_ActiveMouseScreen) +#: focus.ui:206 +#, kde-format +msgid "Active screen follows &mouse" +msgstr "" + +#. i18n: ectx: property (whatsThis), widget (QCheckBox, kcfg_SeparateScreenFocus) +#: focus.ui:213 +#, kde-format +msgid "" +"When this option is enabled, focus operations are limited only to the active " +"screen" +msgstr "" + +#. i18n: ectx: property (text), widget (QCheckBox, kcfg_SeparateScreenFocus) +#: focus.ui:216 +#, kde-format +msgid "&Separate screen focus" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, windowFocusPolicyDescriptionLabel) +#: focus.ui:229 +#, kde-format +msgid "Window activation policy description" +msgstr "" + +#: main.cpp:73 +#, kde-format +msgid "&Focus" +msgstr "Fokus" + +#: main.cpp:79 +#, fuzzy, kde-format +#| msgid "&Titlebar Actions" +msgid "Titlebar A&ctions" +msgstr "Titelbalk Aksies" + +#: main.cpp:85 +#, fuzzy, kde-format +#| msgid "Window Actio&ns" +msgid "W&indow Actions" +msgstr "Venster Aksies" + +#: main.cpp:91 +#, fuzzy, kde-format +#| msgid "&Placement:" +msgid "Mo&vement" +msgstr "Plasing:" + +#: main.cpp:97 +#, fuzzy, kde-format +#| msgid "Ad&vanced" +msgid "Adva&nced" +msgstr "Gevorderde" + +#: main.cpp:101 +#, kde-format +msgid "Window Behavior Configuration Module" +msgstr "Venster Gedrag Opstelling Module" + +#: main.cpp:103 +#, kde-format +msgid "(c) 1997 - 2002 KWin and KControl Authors" +msgstr "(c) 1997 - 2002 Kwin en KControl Outeure" + +#: main.cpp:105 +#, kde-format +msgid "Matthias Ettrich" +msgstr "" + +#: main.cpp:106 +#, kde-format +msgid "Waldo Bastian" +msgstr "" + +#: main.cpp:107 +#, kde-format +msgid "Cristian Tibirna" +msgstr "" + +#: main.cpp:108 +#, kde-format +msgid "Matthias Kalle Dalheimer" +msgstr "" + +#: main.cpp:109 +#, kde-format +msgid "Daniel Molkentin" +msgstr "" + +#: main.cpp:110 +#, kde-format +msgid "Wynn Wilkes" +msgstr "" + +#: main.cpp:111 +#, kde-format +msgid "Pat Dowler" +msgstr "" + +#: main.cpp:112 +#, kde-format +msgid "Bernd Wuebben" +msgstr "" + +#: main.cpp:113 +#, kde-format +msgid "Matthias Hoelzer-Kluepfel" +msgstr "" + +#: main.cpp:161 +#, fuzzy, kde-format +#| msgid "" +#| "

    Window Behavior

    Here you can customize the way windows behave " +#| "when being moved, resized or clicked on. You can also specify a focus " +#| "policy as well as a placement policy for new windows.

    Please note that " +#| "this configuration will not take effect if you do not use KWin as your " +#| "window manager. If you do use a different window manager, please refer to " +#| "its documentation for how to customize window behavior." +msgid "" +"

    Window Behavior

    Here you can customize the way windows behave " +"when being moved, resized or clicked on. You can also specify a focus policy " +"as well as a placement policy for new windows.

    Please note that this " +"configuration will not take effect if you do not use KWin as your window " +"manager. If you do use a different window manager, please refer to its " +"documentation for how to customize window behavior.

    " +msgstr "" +"

    Venster Gedrag

    Hier kan jy die wyse waarop vensters hulle gedra " +"pasmaak wanneer hulle verskuif, hervergroot of daarop gekliek word. Jy kan " +"asook 'n fokus beleid spesifiseer, asook 'n plasing beleid vir nuwe " +"vensters.

    Let daarop dat hierdie opstelling geen effek sal hê as jy nie " +"Kwin gebruik as jou Venster Bestuurder nie. As jy 'n ander Venster " +"Bestuurder gebruik, verwys na sy dokumentasie hoe om venster gedrag te " +"pasmaak." + +#: main.cpp:202 +#, kde-format +msgid "&Titlebar Actions" +msgstr "Titelbalk Aksies" + +#: main.cpp:208 +#, kde-format +msgid "Window Actio&ns" +msgstr "Venster Aksies" + +#. i18n: ectx: property (title), widget (QGroupBox, groupBox_1) +#: mouse.ui:17 +#, fuzzy, kde-format +#| msgid "&Titlebar Actions" +msgid "Titlebar Actions" +msgstr "Titelbalk Aksies" + +#. i18n: ectx: property (text), widget (QLabel, label_1) +#: mouse.ui:26 +#, fuzzy, kde-format +#| msgid "&Titlebar double-click:" +msgid "&Double-click:" +msgstr "Titelbalk dubbel-kliek:" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_TitlebarDoubleClickCommand) +#: mouse.ui:36 +#, kde-format +msgid "Behavior on double click into the titlebar." +msgstr "Gedrag vir dubbel kliek binne die titelbalk." + +#. i18n: @item:inlistbox behavior on double click +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_TitlebarDoubleClickCommand) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_MaximizeButtonLeftClickCommand) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_MaximizeButtonMiddleClickCommand) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_MaximizeButtonRightClickCommand) +#: mouse.ui:40 mouse.ui:615 mouse.ui:650 mouse.ui:685 +#, fuzzy, kde-format +#| msgid "Maximize" +msgid "Maximize" +msgstr "Vergroot" + +#. i18n: @item:inlistbox behavior on double click +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_TitlebarDoubleClickCommand) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_MaximizeButtonLeftClickCommand) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_MaximizeButtonMiddleClickCommand) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_MaximizeButtonRightClickCommand) +#: mouse.ui:45 mouse.ui:620 mouse.ui:655 mouse.ui:690 +#, kde-format +msgid "Vertically maximize" +msgstr "" + +#. i18n: @item:inlistbox behavior on double click +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_TitlebarDoubleClickCommand) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_MaximizeButtonLeftClickCommand) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_MaximizeButtonMiddleClickCommand) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_MaximizeButtonRightClickCommand) +#: mouse.ui:50 mouse.ui:625 mouse.ui:660 mouse.ui:695 +#, fuzzy, kde-format +#| msgid "Horizontal offset:" +msgid "Horizontally maximize" +msgstr "Horisontale verskil:" + +#. i18n: @item:inlistbox behavior on double click +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_TitlebarDoubleClickCommand) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar3) +#: mouse.ui:60 mouse.ui:256 mouse.ui:318 mouse.ui:371 mouse.ui:433 mouse.ui:486 +#: mouse.ui:548 +#, kde-format +msgid "Shade" +msgstr "Skadu" + +#. i18n: @item:inlistbox behavior on double click +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_TitlebarDoubleClickCommand) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar3) +#: mouse.ui:70 mouse.ui:261 mouse.ui:323 mouse.ui:376 mouse.ui:438 mouse.ui:491 +#: mouse.ui:553 +#, kde-format +msgid "Close" +msgstr "" + +#. i18n: @item:inlistbox behavior on double click +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_TitlebarDoubleClickCommand) +#: mouse.ui:75 +#, fuzzy, kde-format +#| msgid "On All Desktops" +msgid "Show on all desktops" +msgstr "Op Alle Werkskerms" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandTitlebarWheel) +#: mouse.ui:98 +#, fuzzy, kde-format +#| msgid "Behavior on double click into the titlebar." +msgid "Behavior on mouse wheel scroll over the titlebar." +msgstr "Gedrag vir dubbel kliek binne die titelbalk." + +#. i18n: ectx: property (title), widget (QGroupBox, groupBox_2) +#: mouse.ui:143 +#, fuzzy, kde-format +#| msgid "&Titlebar Actions" +msgid "Titlebar and Frame Actions" +msgstr "Titelbalk Aksies" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: mouse.ui:167 +#, kde-format +msgid "Active" +msgstr "Aktief" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: mouse.ui:190 +#, kde-format +msgid "Inactive" +msgstr "Onaktief" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandActiveTitlebar1) +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandActiveTitlebar2) +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandActiveTitlebar3) +#: mouse.ui:232 mouse.ui:347 mouse.ui:462 +#, kde-format +msgid "" +"Behavior on left click into the titlebar or frame of an active window." +msgstr "" +"Gedrag vir links kliek binne die titelbalk of raam van 'n " +"aktiewe venster." + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar3) +#: mouse.ui:266 mouse.ui:328 mouse.ui:381 mouse.ui:443 mouse.ui:496 +#: mouse.ui:558 +#, fuzzy, kde-format +#| msgid "Operations Menu" +msgid "Show actions menu" +msgstr "Operasies Kieslys" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandInactiveTitlebar1) +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandInactiveTitlebar2) +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandInactiveTitlebar3) +#: mouse.ui:279 mouse.ui:394 mouse.ui:509 +#, kde-format +msgid "" +"Behavior on left click into the titlebar or frame of an " +"inactive window." +msgstr "" +"Gedrag vir links kliek binne die titelbalk of raam van 'n " +"onaktiewe venster." + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar3) +#: mouse.ui:288 mouse.ui:403 mouse.ui:518 +#, fuzzy, kde-format +#| msgid "Activate & Lower" +msgid "Activate and lower" +msgstr "Aktiveer & Verlaag" + +#. i18n: ectx: property (title), widget (QGroupBox, groupBox_3) +#: mouse.ui:589 +#, fuzzy, kde-format +#| msgid "Maximize Button" +msgid "Maximize Button Actions" +msgstr "Vergroot Knoppie" + +#. i18n: ectx: property (whatsThis), widget (QLabel, label_8) +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_MaximizeButtonLeftClickCommand) +#: mouse.ui:598 mouse.ui:611 +#, kde-format +msgid "Behavior on left click onto the maximize button." +msgstr "Gedrag vir links kliek op die vergroot knoppie." + +#. i18n: ectx: property (whatsThis), widget (QLabel, label_9) +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_MaximizeButtonMiddleClickCommand) +#: mouse.ui:633 mouse.ui:646 +#, kde-format +msgid "Behavior on middle click onto the maximize button." +msgstr "Gedrag vir middel kliek op die vergroot knoppie." + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: mouse.ui:636 +#, fuzzy, kde-format +#| msgid "Middle button:" +msgid "Middle c&lick:" +msgstr "Middelste knoppie:" + +#. i18n: ectx: property (whatsThis), widget (QLabel, label_10) +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_MaximizeButtonRightClickCommand) +#: mouse.ui:668 mouse.ui:681 +#, kde-format +msgid "Behavior on right click onto the maximize button." +msgstr "Gedrag vir regs kliek op die vergroot knoppie." + +#. i18n: ectx: property (text), widget (QLabel, borderSnapLabel) +#: moving.ui:20 +#, fuzzy, kde-format +#| msgid "&Border snap zone:" +msgid "Screen &edge snap zone:" +msgstr "Grens klamp sone:" + +#. i18n: ectx: property (whatsThis), widget (QSpinBox, kcfg_BorderSnapZone) +#: moving.ui:30 +#, fuzzy, kde-format +#| msgid "" +#| "Here you can set the snap zone for screen borders, i.e. the 'strength' of " +#| "the magnetic field which will make windows snap to the border when moved " +#| "near it." +msgid "" +"Here you can set the snap zone for screen edges, i.e. the 'strength' of the " +"magnetic field which will make windows snap to the border when moved near it." +msgstr "" +"Hier kan jy die klamp sone van die skerm rame stel, byvoorbeeld die " +"'sterkte' van die magnetiese veld wat sal veroorsaak dat vensters aan die " +"raam klamp wanneer hulle naby daaraan gekuif word." + +#. i18n: ectx: property (suffix), widget (QSpinBox, kcfg_BorderSnapZone) +#. i18n: ectx: property (suffix), widget (QSpinBox, kcfg_WindowSnapZone) +#. i18n: ectx: property (suffix), widget (QSpinBox, kcfg_CenterSnapZone) +#: moving.ui:36 moving.ui:68 moving.ui:100 +#, kde-format +msgid " px" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, windowSnapLabel) +#: moving.ui:52 +#, kde-format +msgid "&Window snap zone:" +msgstr "Venster klamp sone:" + +#. i18n: ectx: property (whatsThis), widget (QSpinBox, kcfg_WindowSnapZone) +#: moving.ui:62 +#, fuzzy, kde-format +#| msgid "" +#| "Here you can set the snap zone for windows, i.e. the 'strength' of the " +#| "magnetic field which will make windows snap to each other when they're " +#| "moved near another window." +msgid "" +"Here you can set the snap zone for windows, i.e. the 'strength' of the " +"magnetic field which will make windows snap to each other when they are " +"moved near another window." +msgstr "" +"Hier kan jy die klamp sone van die vensters stel, byvoorbeeld die 'sterkte' " +"van die magnetiese veld wat sal veroorsaak dat vensters aan mekaar klamp " +"wanneer hulle naby aan 'n ander venster gekuif word." + +#. i18n: ectx: property (text), widget (QLabel, centerSnaplabel) +#: moving.ui:84 +#, fuzzy, kde-format +#| msgid "&Border snap zone:" +msgid "&Center snap zone:" +msgstr "Grens klamp sone:" + +#. i18n: ectx: property (whatsThis), widget (QSpinBox, kcfg_CenterSnapZone) +#: moving.ui:94 +#, fuzzy, kde-format +#| msgid "" +#| "Here you can set the snap zone for screen borders, i.e. the 'strength' of " +#| "the magnetic field which will make windows snap to the border when moved " +#| "near it." +msgid "" +"Here you can set the snap zone for the screen center, i.e. the 'strength' of " +"the magnetic field which will make windows snap to the center of the screen " +"when moved near it." +msgstr "" +"Hier kan jy die klamp sone van die skerm rame stel, byvoorbeeld die " +"'sterkte' van die magnetiese veld wat sal veroorsaak dat vensters aan die " +"raam klamp wanneer hulle naby daaraan gekuif word." + +#. i18n: ectx: property (text), widget (QLabel, OverlapSnapLabel) +#: moving.ui:113 +#, fuzzy, kde-format +#| msgid "Inactive windows:" +msgid "&Snap windows:" +msgstr "Onaktiewe venster" + +#. i18n: ectx: property (whatsThis), widget (QCheckBox, kcfg_SnapOnlyWhenOverlapping) +#: moving.ui:123 +#, kde-format +msgid "" +"Here you can set that windows will be only snapped if you try to overlap " +"them, i.e. they will not be snapped if the windows comes only near another " +"window or border." +msgstr "" +"Hier kan jy kies dat vensters net klamp as jy probeer om dit oor mekaar te " +"plaas, hulle sal byvoorbeeld nie klamp wanneer 'n venster naby 'n ander " +"venster of raam kom nie." + +#. i18n: ectx: property (text), widget (QCheckBox, kcfg_SnapOnlyWhenOverlapping) +#: moving.ui:126 +#, fuzzy, kde-format +#| msgid "Snap windows onl&y when overlapping" +msgid "Only when overlapping" +msgstr "Klamp vensters slegs wanneer oorvleuel" + +#: windows.cpp:97 +#, kde-format +msgid "" +"Click to focus: A window becomes active when you click into it. " +"This behavior is common on other operating systems and likely what you want." +msgstr "" + +#: windows.cpp:101 +#, kde-format +msgid "" +"Click to focus (mouse precedence): Mostly the same as Click to " +"focus. If an active window has to be chosen by the system (eg. because " +"the currently active one was closed) the window under the mouse is the " +"preferred candidate. Unusual, but possible variant of Click to focus." +msgstr "" + +#: windows.cpp:106 +#, kde-format +msgid "" +"Focus follows mouse: Moving the mouse onto a window will activate " +"it. Eg. windows randomly appearing under the mouse will not gain the focus. " +"Focus stealing prevention takes place as usual. Think as Click " +"to focus just without having to actually click." +msgstr "" + +#: windows.cpp:110 +#, kde-format +msgid "" +"This is mostly the same as Focus follows mouse. If an active window " +"has to be chosen by the system (eg. because the currently active one was " +"closed) the window under the mouse is the preferred candidate. Choose this, " +"if you want a hover controlled focus." +msgstr "" + +#: windows.cpp:115 +#, kde-format +msgid "" +"Focus under mouse: The focus always remains on the window under the " +"mouse.
    Warning: Focus stealing prevention and " +"the tabbox ('Alt+Tab') contradict the activation policy and will " +"not work. You very likely want to use Focus follows mouse (mouse " +"precedence) instead!" +msgstr "" + +#: windows.cpp:119 +#, kde-format +msgid "" +"Focus strictly under mouse: The focus is always on the window under " +"the mouse (in doubt nowhere) very much like the focus behavior in an " +"unmanaged legacy X11 environment.
    Warning: Focus " +"stealing prevention and the tabbox ('Alt+Tab') contradict the " +"activation policy and will not work. You very likely want to use Focus " +"follows mouse (mouse precedence) instead!" +msgstr "" \ No newline at end of file diff --git a/po/af/kwin.po b/po/af/kwin.po new file mode 100644 index 0000000..710d972 --- /dev/null +++ b/po/af/kwin.po @@ -0,0 +1,2773 @@ +# UTF-8 test:äëïöü +# Copyright (C) 2001, 2005 Free Software Foundation, Inc. +# Frikkie Thirion , 2001,2002. +# Kobus Venter , 2005. +# +msgid "" +msgstr "" +"Project-Id-Version: kwin stable\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2022-12-17 02:44+0000\n" +"PO-Revision-Date: 2005-11-28 12:28+0200\n" +"Last-Translator: Kobus Venter \n" +"Language-Team: AFRIKAANS \n" +"Language: af\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.10\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#, kde-format +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "WEB-Vertaler (http://kde.af.org.za), Kobus Venter" + +#, kde-format +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "frix@expertron.co.za, kabousv@therugby.co.za" + +#: composite.cpp:615 +#, kde-format +msgid "Desktop effects were restarted due to a graphics reset" +msgstr "" + +#: composite.cpp:820 +#, kde-format +msgid "" +"Desktop effects have been suspended by another application.
    You can " +"resume using the '%1' shortcut." +msgstr "" + +#: debug_console.cpp:78 +#, kde-format +msgid "Timestamp" +msgstr "" + +#: debug_console.cpp:83 +#, kde-format +msgid "Timestamp (µsec)" +msgstr "" + +#: debug_console.cpp:90 +#, kde-format +msgctxt "A mouse button" +msgid "Left" +msgstr "" + +#: debug_console.cpp:92 +#, kde-format +msgctxt "A mouse button" +msgid "Right" +msgstr "" + +#: debug_console.cpp:94 +#, kde-format +msgctxt "A mouse button" +msgid "Middle" +msgstr "" + +#: debug_console.cpp:96 +#, kde-format +msgctxt "A mouse button" +msgid "Back" +msgstr "" + +#: debug_console.cpp:98 +#, kde-format +msgctxt "A mouse button" +msgid "Forward" +msgstr "" + +#: debug_console.cpp:100 +#, kde-format +msgctxt "A mouse button" +msgid "Task" +msgstr "" + +#: debug_console.cpp:102 +#, kde-format +msgctxt "A mouse button" +msgid "Extra Button 4" +msgstr "" + +#: debug_console.cpp:104 +#, kde-format +msgctxt "A mouse button" +msgid "Extra Button 5" +msgstr "" + +#: debug_console.cpp:106 +#, kde-format +msgctxt "A mouse button" +msgid "Extra Button 6" +msgstr "" + +#: debug_console.cpp:108 +#, kde-format +msgctxt "A mouse button" +msgid "Extra Button 7" +msgstr "" + +#: debug_console.cpp:110 +#, kde-format +msgctxt "A mouse button" +msgid "Extra Button 8" +msgstr "" + +#: debug_console.cpp:112 +#, kde-format +msgctxt "A mouse button" +msgid "Extra Button 9" +msgstr "" + +#: debug_console.cpp:114 +#, kde-format +msgctxt "A mouse button" +msgid "Extra Button 10" +msgstr "" + +#: debug_console.cpp:116 +#, kde-format +msgctxt "A mouse button" +msgid "Extra Button 11" +msgstr "" + +#: debug_console.cpp:118 +#, kde-format +msgctxt "A mouse button" +msgid "Extra Button 12" +msgstr "" + +#: debug_console.cpp:120 +#, kde-format +msgctxt "A mouse button" +msgid "Extra Button 13" +msgstr "" + +#: debug_console.cpp:122 +#, kde-format +msgctxt "A mouse button" +msgid "Extra Button 14" +msgstr "" + +#: debug_console.cpp:124 +#, kde-format +msgctxt "A mouse button" +msgid "Extra Button 15" +msgstr "" + +#: debug_console.cpp:126 +#, kde-format +msgctxt "A mouse button" +msgid "Extra Button 16" +msgstr "" + +#: debug_console.cpp:128 +#, kde-format +msgctxt "A mouse button" +msgid "Extra Button 17" +msgstr "" + +#: debug_console.cpp:130 +#, kde-format +msgctxt "A mouse button" +msgid "Extra Button 18" +msgstr "" + +#: debug_console.cpp:132 +#, kde-format +msgctxt "A mouse button" +msgid "Extra Button 19" +msgstr "" + +#: debug_console.cpp:134 +#, kde-format +msgctxt "A mouse button" +msgid "Extra Button 20" +msgstr "" + +#: debug_console.cpp:136 +#, kde-format +msgctxt "A mouse button" +msgid "Extra Button 21" +msgstr "" + +#: debug_console.cpp:138 +#, kde-format +msgctxt "A mouse button" +msgid "Extra Button 22" +msgstr "" + +#: debug_console.cpp:140 +#, kde-format +msgctxt "A mouse button" +msgid "Extra Button 23" +msgstr "" + +#: debug_console.cpp:142 +#, kde-format +msgctxt "A mouse button" +msgid "Extra Button 24" +msgstr "" + +#: debug_console.cpp:151 debug_console.cpp:153 +#, kde-format +msgid "Input Device" +msgstr "" + +#: debug_console.cpp:151 +#, kde-format +msgctxt "The input device of the event is not known" +msgid "Unknown" +msgstr "" + +#: debug_console.cpp:188 +#, kde-format +msgctxt "A mouse pointer motion event" +msgid "Pointer Motion" +msgstr "" + +#: debug_console.cpp:195 +#, kde-format +msgctxt "The relative mouse movement" +msgid "Delta" +msgstr "" + +#: debug_console.cpp:199 +#, kde-format +msgctxt "The relative mouse movement" +msgid "Delta (not accelerated)" +msgstr "" + +#: debug_console.cpp:202 +#, kde-format +msgctxt "The global mouse pointer position" +msgid "Global Position" +msgstr "" + +#: debug_console.cpp:206 +#, kde-format +msgctxt "A mouse pointer button press event" +msgid "Pointer Button Press" +msgstr "" + +#: debug_console.cpp:209 debug_console.cpp:217 +#, kde-format +msgctxt "A button in a mouse press/release event" +msgid "Button" +msgstr "" + +#: debug_console.cpp:210 debug_console.cpp:218 +#, kde-format +msgctxt "A button in a mouse press/release event" +msgid "Native Button code" +msgstr "" + +#: debug_console.cpp:211 debug_console.cpp:219 +#, kde-format +msgctxt "All currently pressed buttons in a mouse press/release event" +msgid "Pressed Buttons" +msgstr "" + +#: debug_console.cpp:214 +#, kde-format +msgctxt "A mouse pointer button release event" +msgid "Pointer Button Release" +msgstr "" + +#: debug_console.cpp:234 +#, kde-format +msgctxt "A mouse pointer axis (wheel) event" +msgid "Pointer Axis" +msgstr "" + +#: debug_console.cpp:238 +#, kde-format +msgctxt "The orientation of a pointer axis event" +msgid "Orientation" +msgstr "" + +#: debug_console.cpp:239 +#, kde-format +msgctxt "An orientation of a pointer axis event" +msgid "Horizontal" +msgstr "" + +#: debug_console.cpp:240 +#, kde-format +msgctxt "An orientation of a pointer axis event" +msgid "Vertical" +msgstr "" + +#: debug_console.cpp:241 +#, kde-format +msgctxt "The angle delta of a pointer axis event" +msgid "Delta" +msgstr "" + +#: debug_console.cpp:256 +#, kde-format +msgctxt "A key press event" +msgid "Key Press" +msgstr "" + +#: debug_console.cpp:259 +#, kde-format +msgctxt "A key release event" +msgid "Key Release" +msgstr "" + +#: debug_console.cpp:268 +#, kde-format +msgctxt "A keyboard modifier" +msgid "Shift" +msgstr "" + +#: debug_console.cpp:272 +#, kde-format +msgctxt "A keyboard modifier" +msgid "Control" +msgstr "" + +#: debug_console.cpp:276 +#, kde-format +msgctxt "A keyboard modifier" +msgid "Alt" +msgstr "" + +#: debug_console.cpp:280 +#, kde-format +msgctxt "A keyboard modifier" +msgid "Meta" +msgstr "" + +#: debug_console.cpp:284 +#, kde-format +msgctxt "A keyboard modifier" +msgid "Keypad" +msgstr "" + +#: debug_console.cpp:288 +#, kde-format +msgctxt "A keyboard modifier" +msgid "Group-switch" +msgstr "" + +#: debug_console.cpp:294 +#, kde-format +msgctxt "Whether the event is an automatic key repeat" +msgid "Repeat" +msgstr "" + +#: debug_console.cpp:298 +#, kde-format +msgctxt "The code as read from the input device" +msgid "Scan code" +msgstr "" + +#: debug_console.cpp:299 +#, kde-format +msgctxt "Key according to Qt" +msgid "Qt::Key code" +msgstr "" + +#: debug_console.cpp:301 +#, kde-format +msgctxt "The translated code to an Xkb symbol" +msgid "Xkb symbol" +msgstr "" + +#: debug_console.cpp:302 +#, kde-format +msgctxt "The translated code interpreted as text" +msgid "Utf8" +msgstr "" + +#: debug_console.cpp:303 +#, kde-format +msgctxt "The currently active modifiers" +msgid "Modifiers" +msgstr "" + +#: debug_console.cpp:315 +#, kde-format +msgctxt "A touch down event" +msgid "Touch down" +msgstr "" + +#: debug_console.cpp:317 debug_console.cpp:332 debug_console.cpp:347 +#, kde-format +msgctxt "The id of the touch point in the touch event" +msgid "Point identifier" +msgstr "" + +#: debug_console.cpp:318 debug_console.cpp:333 +#, kde-format +msgctxt "The global position of the touch point" +msgid "Global position" +msgstr "" + +#: debug_console.cpp:330 +#, kde-format +msgctxt "A touch motion event" +msgid "Touch Motion" +msgstr "" + +#: debug_console.cpp:345 +#, kde-format +msgctxt "A touch up event" +msgid "Touch Up" +msgstr "" + +#: debug_console.cpp:358 +#, kde-format +msgctxt "A pinch gesture is started" +msgid "Pinch start" +msgstr "" + +#: debug_console.cpp:360 +#, kde-format +msgctxt "Number of fingers in this pinch gesture" +msgid "Finger count" +msgstr "" + +#: debug_console.cpp:371 +#, kde-format +msgctxt "A pinch gesture is updated" +msgid "Pinch update" +msgstr "" + +#: debug_console.cpp:373 +#, kde-format +msgctxt "Current scale in pinch gesture" +msgid "Scale" +msgstr "" + +#: debug_console.cpp:374 +#, kde-format +msgctxt "Current angle in pinch gesture" +msgid "Angle delta" +msgstr "" + +#: debug_console.cpp:375 +#, kde-format +msgctxt "Current delta in pinch gesture" +msgid "Delta x" +msgstr "" + +#: debug_console.cpp:376 +#, kde-format +msgctxt "Current delta in pinch gesture" +msgid "Delta y" +msgstr "" + +#: debug_console.cpp:387 +#, kde-format +msgctxt "A pinch gesture ended" +msgid "Pinch end" +msgstr "" + +#: debug_console.cpp:399 +#, kde-format +msgctxt "A pinch gesture got cancelled" +msgid "Pinch cancelled" +msgstr "" + +#: debug_console.cpp:411 +#, kde-format +msgctxt "A swipe gesture is started" +msgid "Swipe start" +msgstr "" + +#: debug_console.cpp:413 +#, kde-format +msgctxt "Number of fingers in this swipe gesture" +msgid "Finger count" +msgstr "" + +#: debug_console.cpp:424 +#, kde-format +msgctxt "A swipe gesture is updated" +msgid "Swipe update" +msgstr "" + +#: debug_console.cpp:426 +#, kde-format +msgctxt "Current delta in swipe gesture" +msgid "Delta x" +msgstr "" + +#: debug_console.cpp:427 +#, kde-format +msgctxt "Current delta in swipe gesture" +msgid "Delta y" +msgstr "" + +#: debug_console.cpp:438 +#, kde-format +msgctxt "A swipe gesture ended" +msgid "Swipe end" +msgstr "" + +#: debug_console.cpp:450 +#, kde-format +msgctxt "A swipe gesture got cancelled" +msgid "Swipe cancelled" +msgstr "" + +#: debug_console.cpp:462 +#, fuzzy, kde-format +#| msgid "Switch to Desktop 10" +msgctxt "A hardware switch (e.g. notebook lid) got toggled" +msgid "Switch toggled" +msgstr "Wissel na Werkskerm 10" + +#: debug_console.cpp:470 +#, kde-format +msgctxt "Name of a hardware switch" +msgid "Notebook lid" +msgstr "" + +#: debug_console.cpp:472 +#, kde-format +msgctxt "Name of a hardware switch" +msgid "Tablet mode" +msgstr "" + +#: debug_console.cpp:474 +#, fuzzy, kde-format +#| msgid "Switch to Desktop 10" +msgctxt "A hardware switch" +msgid "Switch" +msgstr "Wissel na Werkskerm 10" + +#: debug_console.cpp:478 +#, kde-format +msgctxt "The hardware switch got turned off" +msgid "Off" +msgstr "" + +#: debug_console.cpp:481 +#, kde-format +msgctxt "The hardware switch got turned on" +msgid "On" +msgstr "" + +#: debug_console.cpp:486 +#, kde-format +msgctxt "State of a hardware switch (on/off)" +msgid "State" +msgstr "" + +#: debug_console.cpp:501 +#, kde-format +msgid "Tablet Tool" +msgstr "" + +#: debug_console.cpp:502 +#, kde-format +msgid "EventType" +msgstr "" + +#: debug_console.cpp:503 debug_console.cpp:548 debug_console.cpp:562 +#, kde-format +msgid "Position" +msgstr "" + +#: debug_console.cpp:505 +#, kde-format +msgid "Tilt" +msgstr "" + +#: debug_console.cpp:507 +#, kde-format +msgid "Rotation" +msgstr "" + +#: debug_console.cpp:508 +#, kde-format +msgid "Pressure" +msgstr "" + +#: debug_console.cpp:509 +#, fuzzy, kde-format +#| msgid "Mouse Emulation" +msgid "Buttons" +msgstr "Muis Emulering" + +#. i18n: ectx: property (title), widget (QGroupBox, modifiersBox) +#: debug_console.cpp:510 debug_console.ui:356 +#, kde-format +msgid "Modifiers" +msgstr "" + +#: debug_console.cpp:519 +#, kde-format +msgid "Tablet Tool Button" +msgstr "" + +#: debug_console.cpp:520 debug_console.cpp:534 +#, fuzzy, kde-format +#| msgid "Mouse Emulation" +msgid "Button" +msgstr "Muis Emulering" + +#: debug_console.cpp:521 debug_console.cpp:535 +#, fuzzy, kde-format +#| msgid "Mouse Emulation" +msgid "Pressed" +msgstr "Muis Emulering" + +#: debug_console.cpp:522 debug_console.cpp:536 debug_console.cpp:550 +#: debug_console.cpp:564 +#, kde-format +msgid "Tablet" +msgstr "" + +#: debug_console.cpp:533 +#, kde-format +msgid "Tablet Pad Button" +msgstr "" + +#: debug_console.cpp:546 +#, kde-format +msgid "Tablet Pad Strip" +msgstr "" + +#: debug_console.cpp:547 debug_console.cpp:561 +#, kde-format +msgid "Number" +msgstr "" + +#: debug_console.cpp:549 debug_console.cpp:563 +#, kde-format +msgid "isFinger" +msgstr "" + +#: debug_console.cpp:560 +#, kde-format +msgid "Tablet Pad Ring" +msgstr "" + +#: debug_console.cpp:789 +#, fuzzy, kde-format +#| msgid "Mouse Emulation" +msgid "No Mouse Buttons" +msgstr "Muis Emulering" + +#: debug_console.cpp:793 +#, kde-format +msgctxt "Mouse Button" +msgid "left" +msgstr "" + +#: debug_console.cpp:796 +#, kde-format +msgctxt "Mouse Button" +msgid "right" +msgstr "" + +#: debug_console.cpp:799 +#, kde-format +msgctxt "Mouse Button" +msgid "middle" +msgstr "" + +#: debug_console.cpp:802 +#, kde-format +msgctxt "Mouse Button" +msgid "back" +msgstr "" + +#: debug_console.cpp:805 +#, kde-format +msgctxt "Mouse Button" +msgid "forward" +msgstr "" + +#: debug_console.cpp:808 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 1" +msgstr "" + +#: debug_console.cpp:811 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 2" +msgstr "" + +#: debug_console.cpp:814 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 3" +msgstr "" + +#: debug_console.cpp:817 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 4" +msgstr "" + +#: debug_console.cpp:820 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 5" +msgstr "" + +#: debug_console.cpp:823 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 6" +msgstr "" + +#: debug_console.cpp:826 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 7" +msgstr "" + +#: debug_console.cpp:829 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 8" +msgstr "" + +#: debug_console.cpp:832 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 9" +msgstr "" + +#: debug_console.cpp:835 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 10" +msgstr "" + +#: debug_console.cpp:838 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 11" +msgstr "" + +#: debug_console.cpp:841 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 12" +msgstr "" + +#: debug_console.cpp:844 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 13" +msgstr "" + +#: debug_console.cpp:847 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 14" +msgstr "" + +#: debug_console.cpp:850 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 15" +msgstr "" + +#: debug_console.cpp:853 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 16" +msgstr "" + +#: debug_console.cpp:856 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 17" +msgstr "" + +#: debug_console.cpp:859 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 18" +msgstr "" + +#: debug_console.cpp:862 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 19" +msgstr "" + +#: debug_console.cpp:865 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 20" +msgstr "" + +#: debug_console.cpp:868 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 21" +msgstr "" + +#: debug_console.cpp:871 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 22" +msgstr "" + +#: debug_console.cpp:874 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 23" +msgstr "" + +#: debug_console.cpp:877 +#, kde-format +msgctxt "Mouse Button" +msgid "extra 24" +msgstr "" + +#: debug_console.cpp:880 +#, kde-format +msgctxt "Mouse Button" +msgid "task" +msgstr "" + +#: debug_console.cpp:1216 +#, fuzzy, kde-format +#| msgid "Windows" +msgid "X11 Windows" +msgstr "Vensters" + +#: debug_console.cpp:1218 +#, kde-format +msgid "X11 Unmanaged Windows" +msgstr "" + +#: debug_console.cpp:1220 +#, fuzzy, kde-format +#| msgid "Shade Window" +msgid "Wayland Windows" +msgstr "Verskadu Vensters" + +#: debug_console.cpp:1222 +#, fuzzy, kde-format +#| msgid "Raise Window" +msgid "Internal Windows" +msgstr "Lig Venster" + +#. i18n: ectx: property (text), widget (QPushButton, quitButton) +#: debug_console.ui:32 +#, kde-format +msgid "Quit Debug Console" +msgstr "" + +#. i18n: ectx: attribute (title), widget (QWidget, windows) +#: debug_console.ui:45 +#, kde-format +msgid "Windows" +msgstr "Vensters" + +#. i18n: ectx: attribute (title), widget (QWidget, surfaces) +#: debug_console.ui:59 +#, kde-format +msgid "Surfaces" +msgstr "" + +#. i18n: ectx: attribute (title), widget (QWidget, input) +#: debug_console.ui:69 +#, kde-format +msgid "Input Events" +msgstr "" + +#. i18n: ectx: attribute (title), widget (QWidget, inputDevices) +#: debug_console.ui:86 +#, kde-format +msgid "Input Devices" +msgstr "" + +#. i18n: ectx: attribute (title), widget (QWidget, tab) +#: debug_console.ui:96 +#, kde-format +msgid "OpenGL" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, noOpenGLLabel) +#: debug_console.ui:102 +#, kde-format +msgid "No OpenGL compositor running" +msgstr "" + +#. i18n: ectx: property (title), widget (QGroupBox, driverInfoBox) +#: debug_console.ui:130 +#, kde-format +msgid "OpenGL (ES) driver information" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: debug_console.ui:136 +#, kde-format +msgid "Vendor:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: debug_console.ui:143 +#, kde-format +msgid "Renderer:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: debug_console.ui:150 +#, kde-format +msgid "Version:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#: debug_console.ui:157 +#, kde-format +msgid "Shading Language Version:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: debug_console.ui:164 +#, kde-format +msgid "Driver:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: debug_console.ui:171 +#, kde-format +msgid "GPU class:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: debug_console.ui:178 +#, kde-format +msgid "OpenGL Version:" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: debug_console.ui:185 +#, kde-format +msgid "GLSL Version:" +msgstr "" + +#. i18n: ectx: property (title), widget (QGroupBox, platformExtensionsBox) +#: debug_console.ui:251 +#, kde-format +msgid "Platform Extensions" +msgstr "" + +#. i18n: ectx: property (title), widget (QGroupBox, glExtensionsBox) +#: debug_console.ui:267 +#, kde-format +msgid "OpenGL (ES) Extensions" +msgstr "" + +#. i18n: ectx: attribute (title), widget (QWidget, keyboard) +#: debug_console.ui:288 +#, kde-format +msgid "Keyboard" +msgstr "" + +#. i18n: ectx: property (title), widget (QGroupBox, layoutBox) +#: debug_console.ui:315 +#, kde-format +msgid "Keymap Layouts" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#: debug_console.ui:337 +#, fuzzy, kde-format +#| msgid "Configur&e Window Behavior..." +msgid "Current Layout:" +msgstr "Konfigureer Venster Gedrag..." + +#. i18n: ectx: property (title), widget (QGroupBox, activeModifiersBox) +#: debug_console.ui:372 +#, kde-format +msgid "Active Modifiers" +msgstr "" + +#. i18n: ectx: property (title), widget (QGroupBox, ledsBox) +#: debug_console.ui:388 +#, kde-format +msgid "LEDs" +msgstr "" + +#. i18n: ectx: property (title), widget (QGroupBox, activeLedsBox) +#: debug_console.ui:404 +#, kde-format +msgid "Active LEDs" +msgstr "" + +#. i18n: ectx: attribute (title), widget (QWidget, clipboard) +#. i18n: ectx: property (text), widget (QLabel, label) +#: debug_console.ui:425 debug_console.ui:445 +#, kde-format +msgid "Clipboard" +msgstr "" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: debug_console.ui:491 +#, kde-format +msgid "Primary Selection" +msgstr "" + +#: helpers/killer/killer.cpp:34 +#, fuzzy, kde-format +#| msgid "KDE window manager" +msgid "Window Manager" +msgstr "Kde venster bestuurder." + +#: helpers/killer/killer.cpp:38 +#, kde-format +msgid "PID of the application to terminate" +msgstr "" + +#: helpers/killer/killer.cpp:38 +#, kde-format +msgid "pid" +msgstr "" + +#: helpers/killer/killer.cpp:40 +#, kde-format +msgid "Hostname on which the application is running" +msgstr "" + +#: helpers/killer/killer.cpp:40 +#, kde-format +msgid "hostname" +msgstr "" + +#: helpers/killer/killer.cpp:42 +#, kde-format +msgid "Caption of the window to be terminated" +msgstr "" + +#: helpers/killer/killer.cpp:42 +#, kde-format +msgid "caption" +msgstr "" + +#: helpers/killer/killer.cpp:44 +#, kde-format +msgid "Name of the application to be terminated" +msgstr "" + +#: helpers/killer/killer.cpp:44 +#, kde-format +msgid "name" +msgstr "" + +#: helpers/killer/killer.cpp:46 +#, kde-format +msgid "ID of resource belonging to the application" +msgstr "" + +#: helpers/killer/killer.cpp:46 +#, kde-format +msgid "id" +msgstr "" + +#: helpers/killer/killer.cpp:48 +#, kde-format +msgid "Time of user action causing termination" +msgstr "" + +#: helpers/killer/killer.cpp:48 +#, kde-format +msgid "time" +msgstr "" + +#: helpers/killer/killer.cpp:50 +#, kde-format +msgid "KWin helper utility" +msgstr "Kwin hulp program" + +#: helpers/killer/killer.cpp:74 +#, kde-format +msgid "This helper utility is not supposed to be called directly." +msgstr "Hierdie hulp program is nie veronderstel om direk geroep te word nie." + +#: helpers/killer/killer.cpp:84 +#, kde-format +msgctxt "@info" +msgid "Application \"%1\" is not responding" +msgstr "" + +#: helpers/killer/killer.cpp:86 +#, kde-kuit-format +msgctxt "@info" +msgid "" +"You tried to close window \"%1\" from application \"%2\" (Process ID: " +"%3) but the application is not responding." +msgstr "" + +#: helpers/killer/killer.cpp:88 +#, kde-kuit-format +msgctxt "@info" +msgid "" +"You tried to close window \"%1\" from application \"%2\" (Process ID: " +"%3), running on host \"%4\", but the application is not responding." +msgstr "" + +#: helpers/killer/killer.cpp:91 +#, kde-kuit-format +msgctxt "@info" +msgid "" +"Do you want to terminate this application?Terminating the application will close all of its child " +"windows. Any unsaved data will be lost." +msgstr "" + +#: helpers/killer/killer.cpp:94 +#, kde-format +msgid "&Terminate Application %1" +msgstr "" + +#: helpers/killer/killer.cpp:95 +#, kde-format +msgid "Wait Longer" +msgstr "" + +#: input.cpp:3141 +#, kde-format +msgid "Touchpad" +msgstr "" + +#: keyboard_layout.cpp:46 +#, kde-format +msgid "Keyboard Layout Switcher" +msgstr "" + +#: killwindow.cpp:33 +#, kde-format +msgid "" +"Select window to force close with left click or enter.\n" +"Escape or right click to cancel." +msgstr "" + +#: main.cpp:166 +#, kde-format +msgid "KWin" +msgstr "Kwin" + +#: main.cpp:168 main.cpp:191 +#, kde-format +msgid "KDE window manager" +msgstr "Kde venster bestuurder." + +#: main.cpp:170 +#, fuzzy, kde-format +#| msgid "(c) 1999-2005, The KDE Developers" +msgid "(c) 1999-2019, The KDE Developers" +msgstr "(c) 1999-2005, Die Kde Ontwikkelaars" + +#: main.cpp:172 +#, kde-format +msgid "Matthias Ettrich" +msgstr "" + +#: main.cpp:173 +#, kde-format +msgid "Cristian Tibirna" +msgstr "" + +#: main.cpp:174 +#, kde-format +msgid "Daniel M. Duley" +msgstr "" + +#: main.cpp:175 +#, kde-format +msgid "Luboš Luňák" +msgstr "" + +#: main.cpp:176 +#, kde-format +msgid "Martin Flöser" +msgstr "" + +#: main.cpp:177 +#, kde-format +msgid "David Edmundson" +msgstr "" + +#: main.cpp:178 +#, kde-format +msgid "Roman Gilg" +msgstr "" + +#: main.cpp:179 +#, kde-format +msgid "Vlad Zahorodnii" +msgstr "" + +#: main.cpp:188 +#, kde-format +msgid "Disable configuration options" +msgstr "Skakel konfigusrie opsies af" + +#: main.cpp:189 +#, kde-format +msgid "Indicate that KWin has recently crashed n times" +msgstr "" + +#: main_wayland.cpp:314 +#, kde-format +msgid "Start a rootless Xwayland server." +msgstr "" + +#: main_wayland.cpp:316 +#, kde-format +msgid "" +"Name of the Wayland socket to listen on. If not set \"wayland-0\" is used." +msgstr "" + +#: main_wayland.cpp:319 +#, kde-format +msgid "The X11 Display to use in windowed mode on platform X11." +msgstr "" + +#: main_wayland.cpp:322 +#, kde-format +msgid "The Wayland Display to use in windowed mode on platform Wayland." +msgstr "" + +#: main_wayland.cpp:324 +#, kde-format +msgid "Render to a virtual framebuffer." +msgstr "" + +#: main_wayland.cpp:326 +#, kde-format +msgid "The width for windowed mode. Default width is 1024." +msgstr "" + +#: main_wayland.cpp:330 +#, kde-format +msgid "The height for windowed mode. Default height is 768." +msgstr "" + +#: main_wayland.cpp:335 +#, kde-format +msgid "The scale for windowed mode. Default value is 1." +msgstr "" + +#: main_wayland.cpp:340 +#, kde-format +msgid "" +"The number of windows to open as outputs in windowed mode. Default value is 1" +msgstr "" + +#: main_wayland.cpp:345 +#, kde-format +msgid "" +"Wayland socket to use for incoming connections. This can be combined with --" +"socket to name the socket" +msgstr "" + +#: main_wayland.cpp:349 +#, kde-format +msgid "" +"XWayland socket to use for Xwayland's incoming connections. This can be set " +"multiple times" +msgstr "" + +#: main_wayland.cpp:353 +#, kde-format +msgid "Name of the xwayland display that has been pre-set up" +msgstr "" + +#: main_wayland.cpp:357 +#, kde-format +msgid "Name of the xauthority file " +msgstr "" + +#: main_wayland.cpp:361 +#, kde-format +msgid "Exits this instance so it can be restarted by kwin_wayland_wrapper." +msgstr "" + +#: main_wayland.cpp:363 +#, kde-format +msgid "Render through drm node." +msgstr "" + +#: main_wayland.cpp:384 +#, kde-format +msgid "Input method that KWin starts." +msgstr "" + +#: main_wayland.cpp:390 +#, kde-format +msgid "Starts the session in locked mode." +msgstr "" + +#: main_wayland.cpp:394 +#, kde-format +msgid "Starts the session without lock screen support." +msgstr "" + +#: main_wayland.cpp:399 +#, kde-format +msgid "Starts the session without global shortcuts support." +msgstr "" + +#: main_wayland.cpp:404 main_x11.cpp:378 +#, kde-format +msgid "Disable KActivities integration." +msgstr "" + +#: main_wayland.cpp:409 +#, kde-format +msgid "Exit after the session application, which is started by KWin, closed." +msgstr "" + +#: main_wayland.cpp:414 +#, kde-format +msgid "Applications to start once Wayland and Xwayland server are started" +msgstr "" + +#: main_x11.cpp:63 +#, kde-format +msgid "" +"KWin is unstable.\n" +"It seems to have crashed several times in a row.\n" +"You can select another window manager to run:" +msgstr "" + +#: main_x11.cpp:216 +#, kde-format +msgid "" +"kwin: unable to claim manager selection, another wm running? (try using --" +"replace)\n" +msgstr "" +"kwin: kon nie bestuurder seleksie vasmaak, is daar 'n ander wm wat loop? " +"(probeer --replace)\n" + +#: main_x11.cpp:237 +#, fuzzy, kde-format +#| msgid "" +#| "kwin: unable to claim manager selection, another wm running? (try using --" +#| "replace)\n" +msgid "kwin: another window manager is running (try using --replace)\n" +msgstr "" +"kwin: kon nie bestuurder seleksie vasmaak, is daar 'n ander wm wat loop? " +"(probeer --replace)\n" + +#: main_x11.cpp:371 +#, kde-format +msgid "Replace already-running ICCCM2.0-compliant window manager" +msgstr "Vervang ICCCM2.0 aanpasbare venster beheerder wat reeds loop" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:49 +#: plugins/krunner-integration/windowsrunnerinterface.cpp:51 +#, kde-format +msgctxt "Note this is a KRunner keyword" +msgid "activate" +msgstr "" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:52 +#: plugins/krunner-integration/windowsrunnerinterface.cpp:54 +#, kde-format +msgctxt "Note this is a KRunner keyword" +msgid "close" +msgstr "" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:55 +#: plugins/krunner-integration/windowsrunnerinterface.cpp:57 +#, kde-format +msgctxt "Note this is a KRunner keyword" +msgid "min" +msgstr "" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:58 +#: plugins/krunner-integration/windowsrunnerinterface.cpp:60 +#, fuzzy, kde-format +#| msgid "Minimize" +msgctxt "Note this is a KRunner keyword" +msgid "minimize" +msgstr "Verklein" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:61 +#: plugins/krunner-integration/windowsrunnerinterface.cpp:63 +#, kde-format +msgctxt "Note this is a KRunner keyword" +msgid "max" +msgstr "" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:64 +#: plugins/krunner-integration/windowsrunnerinterface.cpp:66 +#, fuzzy, kde-format +#| msgid "Maximize" +msgctxt "Note this is a KRunner keyword" +msgid "maximize" +msgstr "Vergroot" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:67 +#: plugins/krunner-integration/windowsrunnerinterface.cpp:69 +#, fuzzy, kde-format +#| msgid "&Fullscreen" +msgctxt "Note this is a KRunner keyword" +msgid "fullscreen" +msgstr "Volskerm" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:70 +#: plugins/krunner-integration/windowsrunnerinterface.cpp:72 +#, fuzzy, kde-format +#| msgid "Unshade" +msgctxt "Note this is a KRunner keyword" +msgid "shade" +msgstr "Ontskadu" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:73 +#: plugins/krunner-integration/windowsrunnerinterface.cpp:75 +#, fuzzy, kde-format +#| msgid "Keep above others" +msgctxt "Note this is a KRunner keyword" +msgid "keep above" +msgstr "Hou bo-op ander" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:76 +#: plugins/krunner-integration/windowsrunnerinterface.cpp:78 +#, fuzzy, kde-format +#| msgid "Keep below others" +msgctxt "Note this is a KRunner keyword" +msgid "keep below" +msgstr "Hou onder ander" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:83 +#, fuzzy, kde-format +#| msgid "Windows" +msgctxt "Note this is a KRunner keyword" +msgid "window" +msgstr "Vensters" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:93 +#, kde-format +msgctxt "Note this is a KRunner keyword" +msgid "name" +msgstr "" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:95 +#, kde-format +msgctxt "Note this is a KRunner keyword" +msgid "appname" +msgstr "" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:97 +#: plugins/krunner-integration/windowsrunnerinterface.cpp:150 +#, fuzzy, kde-format +#| msgid "&All Desktops" +msgctxt "Note this is a KRunner keyword" +msgid "desktop" +msgstr "Alle Werkskerms" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:256 +#, fuzzy, kde-format +#| msgid "Switch to Desktop 1" +msgid "Switch to desktop %1" +msgstr "Wissel na Werkskerm 1" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:297 +#, kde-format +msgid "Close running window on %1" +msgstr "" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:300 +#, kde-format +msgid "(Un)minimize running window on %1" +msgstr "" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:303 +#, kde-format +msgid "Maximize/restore running window on %1" +msgstr "" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:306 +#, kde-format +msgid "Toggle fullscreen for running window on %1" +msgstr "" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:309 +#, kde-format +msgid "(Un)shade running window on %1" +msgstr "" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:312 +#, kde-format +msgid "Toggle keep above for running window on %1" +msgstr "" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:315 +#, kde-format +msgid "Toggle keep below running window on %1" +msgstr "" + +#: plugins/krunner-integration/windowsrunnerinterface.cpp:319 +#, kde-format +msgid "Activate running window on %1" +msgstr "" + +#: plugins/nightcolor/nightcolormanager.cpp:64 +#, kde-format +msgctxt "Night Color was disabled" +msgid "Night Color Off" +msgstr "" + +#: plugins/nightcolor/nightcolormanager.cpp:65 +#, kde-format +msgctxt "Night Color was enabled" +msgid "Night Color On" +msgstr "" + +#: plugins/nightcolor/nightcolormanager.cpp:88 +#: plugins/nightcolor/nightcolormanager.cpp:91 +#: plugins/nightcolor/nightcolormanager.cpp:98 +#, kde-format +msgid "Toggle Night Color" +msgstr "" + +#: plugins/nightcolor/nightcolormanager.cpp:493 +#, kde-format +msgid "Color Temperature Preview" +msgstr "" + +#. i18n: ectx: label, entry (count), group (General) +#: rulebooksettingsbase.kcfg:9 +#, kde-format +msgid "Total rules count (legacy)" +msgstr "" + +#. i18n: ectx: label, entry (ruleGroupList), group (General) +#: rulebooksettingsbase.kcfg:13 +#, kde-format +msgid "Ordered list of rules groups" +msgstr "" + +#. i18n: ectx: label, entry (description), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:10 +#, kde-format +msgid "Rule description" +msgstr "" + +#. i18n: ectx: label, entry (descriptionLegacy), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:13 +#, kde-format +msgid "Rule description (legacy)" +msgstr "" + +#. i18n: ectx: label, entry (DeleteRule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:16 +#, kde-format +msgid "Delete this rule (for use in imports)" +msgstr "" + +#. i18n: ectx: label, entry (wmclass), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:20 +#, kde-format +msgid "Window class (application)" +msgstr "" + +#. i18n: ectx: label, entry (wmclassmatch), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:23 +#, kde-format +msgid "Window class string match type" +msgstr "" + +#. i18n: ectx: label, entry (wmclasscomplete), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:29 +#, kde-format +msgid "Match whole window class" +msgstr "" + +#. i18n: ectx: label, entry (windowrole), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:34 +#, fuzzy, kde-format +#| msgid "Windows" +msgid "Window role" +msgstr "Vensters" + +#. i18n: ectx: label, entry (windowrolematch), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:37 +#, kde-format +msgid "Window role string match type" +msgstr "" + +#. i18n: ectx: label, entry (title), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:44 +#, fuzzy, kde-format +#| msgid "Windows" +msgid "Window title" +msgstr "Vensters" + +#. i18n: ectx: label, entry (titlematch), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:47 +#, kde-format +msgid "Window title string match type" +msgstr "" + +#. i18n: ectx: label, entry (clientmachine), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:54 +#, kde-format +msgid "Machine (hostname)" +msgstr "" + +#. i18n: ectx: label, entry (clientmachinematch), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:57 +#, kde-format +msgid "Machine string match type" +msgstr "" + +#. i18n: ectx: label, entry (types), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:64 +#, kde-format +msgid "Window types that match" +msgstr "" + +#. i18n: ectx: label, entry (placement), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:69 +#, kde-format +msgid "Initial placement" +msgstr "" + +#. i18n: ectx: label, entry (placementrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:74 +#, fuzzy, kde-format +#| msgid "&Fullscreen" +msgid "Initial placement rule type" +msgstr "Volskerm" + +#. i18n: ectx: label, entry (position), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:79 +#, fuzzy, kde-format +#| msgid "Window Operations Menu" +msgid "Window position" +msgstr "Venster Operasies Kieslys" + +#. i18n: ectx: label, entry (positionrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:83 +#, fuzzy, kde-format +#| msgid "Window to Desktop 10" +msgid "Window position rule type" +msgstr "Venster na Werkskerm 10" + +#. i18n: ectx: label, entry (size), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:90 +#, fuzzy, kde-format +#| msgid "Windows" +msgid "Window size" +msgstr "Vensters" + +#. i18n: ectx: label, entry (sizerule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:93 +#, kde-format +msgid "Window size rule type" +msgstr "" + +#. i18n: ectx: label, entry (minsize), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:100 +#, kde-format +msgid "Window minimum size" +msgstr "" + +#. i18n: ectx: label, entry (minsizerule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:104 +#, kde-format +msgid "Window minimum size rule type" +msgstr "" + +#. i18n: ectx: label, entry (maxsize), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:109 +#, kde-format +msgid "Window maximum size" +msgstr "" + +#. i18n: ectx: label, entry (maxsizerule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:113 +#, kde-format +msgid "Window maximum size rule type" +msgstr "" + +#. i18n: ectx: label, entry (opacityactive), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:118 +#, kde-format +msgid "Active opacity" +msgstr "" + +#. i18n: ectx: label, entry (opacityactiverule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:124 +#, kde-format +msgid "Active opacity rule type" +msgstr "" + +#. i18n: ectx: label, entry (opacityinactive), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:129 +#, kde-format +msgid "Inactive opacity" +msgstr "" + +#. i18n: ectx: label, entry (opacityinactiverule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:135 +#, kde-format +msgid "Inactive opacity rule type" +msgstr "" + +#. i18n: ectx: label, entry (ignoregeometry), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:140 +#, kde-format +msgid "Ignore requested geometry" +msgstr "" + +#. i18n: ectx: label, entry (ignoregeometryrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:144 +#, kde-format +msgid "Ignore requested geometry rule type" +msgstr "" + +#. i18n: ectx: label, entry (desktops), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:151 +#, fuzzy, kde-format +#| msgid "Switch to Desktop 2" +msgid "List of Desktop Ids" +msgstr "Wissel na Werkskerm 2" + +#. i18n: ectx: label, entry (desktopsrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:155 +#, fuzzy, kde-format +#| msgid "Desktop %1" +msgid "Desktop Ids rule type" +msgstr "Werkskerm %1" + +#. i18n: ectx: label, entry (screen), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:162 +#, kde-format +msgid "Screen number" +msgstr "" + +#. i18n: ectx: label, entry (screenrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:166 +#, kde-format +msgid "Screen number rule type" +msgstr "" + +#. i18n: ectx: label, entry (activity), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:173 +#, kde-format +msgid "Activity" +msgstr "" + +#. i18n: ectx: label, entry (activityrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:176 +#, kde-format +msgid "Activity rule type" +msgstr "" + +#. i18n: ectx: label, entry (type), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:183 +#, fuzzy, kde-format +#| msgid "Setup Window Shortcut" +msgid "Set window type to" +msgstr "Stel Venster Kortpad" + +#. i18n: ectx: label, entry (typerule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:189 +#, kde-format +msgid "Set window type rule type" +msgstr "" + +#. i18n: ectx: label, entry (maximizevert), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:194 +#, fuzzy, kde-format +#| msgid "Maximize Window Vertically" +msgid "Maximized vertically" +msgstr "Vergroot Venster Vertikaal" + +#. i18n: ectx: label, entry (maximizevertrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:198 +#, fuzzy, kde-format +#| msgid "Maximize Window Vertically" +msgid "Maximized vertically rule type" +msgstr "Vergroot Venster Vertikaal" + +#. i18n: ectx: label, entry (maximizehoriz), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:205 +#, fuzzy, kde-format +#| msgid "Maximize Window Horizontally" +msgid "Maximized horizontally" +msgstr "Vergroot Venster Horisontaal" + +#. i18n: ectx: label, entry (maximizehorizrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:209 +#, fuzzy, kde-format +#| msgid "Maximize Window Horizontally" +msgid "Maximized horizontally rule type" +msgstr "Vergroot Venster Horisontaal" + +#. i18n: ectx: label, entry (minimize), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:216 +#, fuzzy, kde-format +#| msgid "Minimize" +msgid "Minimized" +msgstr "Verklein" + +#. i18n: ectx: label, entry (minimizerule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:220 +#, kde-format +msgid "Minimized rule type" +msgstr "" + +#. i18n: ectx: label, entry (shade), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:227 +#, fuzzy, kde-format +#| msgid "Shade" +msgid "Shaded" +msgstr "Beskadu" + +#. i18n: ectx: label, entry (shaderule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:231 +#, kde-format +msgid "Shaded rule type" +msgstr "" + +#. i18n: ectx: label, entry (skiptaskbar), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:238 +#, kde-format +msgid "Skip taskbar" +msgstr "" + +#. i18n: ectx: label, entry (skiptaskbarrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:242 +#, kde-format +msgid "Skip taskbar rule type" +msgstr "" + +#. i18n: ectx: label, entry (skippager), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:249 +#, kde-format +msgid "Skip pager" +msgstr "" + +#. i18n: ectx: label, entry (skippagerrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:253 +#, kde-format +msgid "Skip pager rule type" +msgstr "" + +#. i18n: ectx: label, entry (skipswitcher), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:260 +#, fuzzy, kde-format +#| msgid "Switch to Desktop 10" +msgid "Skip switcher" +msgstr "Wissel na Werkskerm 10" + +#. i18n: ectx: label, entry (skipswitcherrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:264 +#, kde-format +msgid "Skip switcher rule type" +msgstr "" + +#. i18n: ectx: label, entry (above), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:271 +#, fuzzy, kde-format +#| msgid "Keep above others" +msgid "Keep above" +msgstr "Hou bo-op ander" + +#. i18n: ectx: label, entry (aboverule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:275 +#, fuzzy, kde-format +#| msgid "Keep above others" +msgid "Keep above rule type" +msgstr "Hou bo-op ander" + +#. i18n: ectx: label, entry (below), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:282 +#, fuzzy, kde-format +#| msgid "Keep below others" +msgid "Keep below" +msgstr "Hou onder ander" + +#. i18n: ectx: label, entry (belowrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:286 +#, fuzzy, kde-format +#| msgid "Keep below others" +msgid "Keep below rule type" +msgstr "Hou onder ander" + +#. i18n: ectx: label, entry (fullscreen), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:293 +#, fuzzy, kde-format +#| msgid "&Fullscreen" +msgid "Fullscreen" +msgstr "Volskerm" + +#. i18n: ectx: label, entry (fullscreenrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:297 +#, fuzzy, kde-format +#| msgid "&Fullscreen" +msgid "Fullscreen rule type" +msgstr "Volskerm" + +#. i18n: ectx: label, entry (noborder), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:304 +#, kde-format +msgid "No titlebar and frame" +msgstr "" + +#. i18n: ectx: label, entry (noborderrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:308 +#, kde-format +msgid "No titlebar rule type" +msgstr "" + +#. i18n: ectx: label, entry (decocolor), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:315 +#, kde-format +msgid "Titlebar color and scheme" +msgstr "" + +#. i18n: ectx: label, entry (decocolorrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:318 +#, kde-format +msgid "Titlebar color rule type" +msgstr "" + +#. i18n: ectx: label, entry (blockcompositing), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:323 +#, kde-format +msgid "Block Compositing" +msgstr "" + +#. i18n: ectx: label, entry (blockcompositingrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:327 +#, kde-format +msgid "Block Compositing rule type" +msgstr "" + +#. i18n: ectx: label, entry (fsplevel), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:332 +#, kde-format +msgid "Focus stealing prevention" +msgstr "" + +#. i18n: ectx: label, entry (fsplevelrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:338 +#, kde-format +msgid "Focus stealing prevention rule type" +msgstr "" + +#. i18n: ectx: label, entry (fpplevel), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:343 +#, kde-format +msgid "Focus protection" +msgstr "" + +#. i18n: ectx: label, entry (fpplevelrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:349 +#, kde-format +msgid "Focus protection rule type" +msgstr "" + +#. i18n: ectx: label, entry (acceptfocus), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:354 +#, kde-format +msgid "Accept Focus" +msgstr "" + +#. i18n: ectx: label, entry (acceptfocusrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:358 +#, kde-format +msgid "Accept Focus rule type" +msgstr "" + +#. i18n: ectx: label, entry (closeable), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:363 +#, kde-format +msgid "Closeable" +msgstr "" + +#. i18n: ectx: label, entry (closeablerule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:367 +#, kde-format +msgid "Closeable rule type" +msgstr "" + +#. i18n: ectx: label, entry (autogroup), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:372 +#, kde-format +msgid "Autogroup with identical" +msgstr "" + +#. i18n: ectx: label, entry (autogrouprule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:376 +#, kde-format +msgid "Autogroup with identical rule type" +msgstr "" + +#. i18n: ectx: label, entry (autogroupfg), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:381 +#, kde-format +msgid "Autogroup in foreground" +msgstr "" + +#. i18n: ectx: label, entry (autogroupfgrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:385 +#, kde-format +msgid "Autogroup in foreground rule type" +msgstr "" + +#. i18n: ectx: label, entry (autogroupid), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:390 +#, kde-format +msgid "Autogroup by ID" +msgstr "" + +#. i18n: ectx: label, entry (autogroupidrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:393 +#, kde-format +msgid "Autogroup by ID rule type" +msgstr "" + +#. i18n: ectx: label, entry (strictgeometry), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:398 +#, kde-format +msgid "Obey geometry restrictions" +msgstr "" + +#. i18n: ectx: label, entry (strictgeometryrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:402 +#, kde-format +msgid "Obey geometry restrictions rule type" +msgstr "" + +#. i18n: ectx: label, entry (shortcut), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:407 +#, kde-format +msgid "Shortcut" +msgstr "" + +#. i18n: ectx: label, entry (shortcutrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:410 +#, kde-format +msgid "Shortcut rule type" +msgstr "" + +#. i18n: ectx: label, entry (disableglobalshortcuts), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:417 +#, fuzzy, kde-format +#| msgid "Block Global Shortcuts" +msgid "Ignore global shortcuts" +msgstr "Blok Globale kortpaaie" + +#. i18n: ectx: label, entry (disableglobalshortcutsrule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:421 +#, kde-format +msgid "Ignore global shortcuts rule type" +msgstr "" + +#. i18n: ectx: label, entry (desktopfile), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:426 +#, fuzzy, kde-format +#| msgid "Desktop %1" +msgid "Desktop file name" +msgstr "Werkskerm %1" + +#. i18n: ectx: label, entry (desktopfilerule), group ($(ruleDescriptionOrNumber)) +#: rulesettings.kcfg:429 +#, kde-format +msgid "Desktop file name rule type" +msgstr "" + +#: scripting/genericscriptedconfig.cpp:76 +#, kde-format +msgctxt "Error message" +msgid "Plugin does not provide configuration file in expected location" +msgstr "" + +#. i18n: ectx: property (windowTitle), widget (QDialog, ShortcutDialog) +#: shortcutdialog.ui:14 +#, kde-format +msgid "Dialog" +msgstr "" + +#. i18n: ectx: property (text), widget (QToolButton, clearButton) +#: shortcutdialog.ui:25 +#, kde-format +msgid "..." +msgstr "" + +#: tabbox/tabbox.cpp:381 +#, fuzzy, kde-format +#| msgid "Switch to Desktop 1" +msgctxt "Special entry in alt+tab list for minimizing all windows" +msgid "Show Desktop" +msgstr "Wissel na Werkskerm 1" + +#: tabbox/tabbox.cpp:521 +msgid "Walk Through Windows" +msgstr "Stap Deur Vensters" + +#: tabbox/tabbox.cpp:522 +msgid "Walk Through Windows (Reverse)" +msgstr "Stap Deur Vensters (Omgekeerde)" + +#: tabbox/tabbox.cpp:523 +#, fuzzy +#| msgid "Walk Through Windows (Reverse)" +msgid "Walk Through Windows Alternative" +msgstr "Stap Deur Vensters (Omgekeerde)" + +#: tabbox/tabbox.cpp:524 +#, fuzzy +#| msgid "Walk Through Windows (Reverse)" +msgid "Walk Through Windows Alternative (Reverse)" +msgstr "Stap Deur Vensters (Omgekeerde)" + +#: tabbox/tabbox.cpp:525 +#, fuzzy +#| msgid "Walk Through Windows (Reverse)" +msgid "Walk Through Windows of Current Application" +msgstr "Stap Deur Vensters (Omgekeerde)" + +#: tabbox/tabbox.cpp:526 +#, fuzzy +#| msgid "Walk Through Windows (Reverse)" +msgid "Walk Through Windows of Current Application (Reverse)" +msgstr "Stap Deur Vensters (Omgekeerde)" + +#: tabbox/tabbox.cpp:527 +#, fuzzy +#| msgid "Walk Through Windows (Reverse)" +msgid "Walk Through Windows of Current Application Alternative" +msgstr "Stap Deur Vensters (Omgekeerde)" + +#: tabbox/tabbox.cpp:528 +#, fuzzy +#| msgid "Walk Through Windows (Reverse)" +msgid "Walk Through Windows of Current Application Alternative (Reverse)" +msgstr "Stap Deur Vensters (Omgekeerde)" + +#: tabbox/tabbox.cpp:529 +msgid "Walk Through Desktops" +msgstr "Stap Deur Werkskerms" + +#: tabbox/tabbox.cpp:530 +msgid "Walk Through Desktops (Reverse)" +msgstr "Stap Deur Werkskerms (Omgekeerde)" + +#: tabbox/tabbox.cpp:531 +msgid "Walk Through Desktop List" +msgstr "Stap Deur Werkskerm Lys" + +#: tabbox/tabbox.cpp:532 +msgid "Walk Through Desktop List (Reverse)" +msgstr "Stap Deur Werkskerm Lys (Omgekeerde)" + +#: tabbox/tabboxhandler.cpp:290 +#, kde-format +msgid "" +"The Window Switcher installation is broken, resources are missing.\n" +"Contact your distribution about this." +msgstr "" + +#: useractions.cpp:160 +#, kde-format +msgid "" +"You have selected to show a window without its border.\n" +"Without the border, you will not be able to enable the border again using " +"the mouse: use the window operations menu instead, activated using the %1 " +"keyboard shortcut." +msgstr "" +"Jy het gekies om 'n venster sonder sy raam te vertoon.\n" +"Sonder die raam, sal jy nie in staat wees om weer die raam aan te skakel " +"deur gebruik te maak van die muis nie: gebruik die venster beheer kieslys " +"daarvoor, geaktiveer gebruik die %1 sleutelbord kortpad." + +#: useractions.cpp:167 +#, kde-format +msgid "" +"You have selected to show a window in fullscreen mode.\n" +"If the application itself does not have an option to turn the fullscreen " +"mode off you will not be able to disable it again using the mouse: use the " +"window operations menu instead, activated using the %1 keyboard shortcut." +msgstr "" +"Jy het gekies om 'n venster te vertoon in vol-skerm modus.\n" +"Indien die program nie 'n opsie het om die volskerm af te sit nie, sal jy " +"nie in staat wees om dit weer af te skakel deur gebruik te maak van die muis " +"nie: gebruik die venster beheer kieslys daarvoor, geaktiveer gebruik die %1 " +"sleutelbord kortpad." + +#: useractions.cpp:237 +#, kde-format +msgid "&Move" +msgstr "Beweeg" + +#: useractions.cpp:242 +#, fuzzy, kde-format +#| msgid "Re&size" +msgid "&Resize" +msgstr "Hervergroot" + +#: useractions.cpp:247 +#, kde-format +msgid "Keep &Above Others" +msgstr "Hou B-Op Ander" + +#: useractions.cpp:253 +#, kde-format +msgid "Keep &Below Others" +msgstr "Hou Onder Ander" + +#: useractions.cpp:259 +#, kde-format +msgid "&Fullscreen" +msgstr "Volskerm" + +#: useractions.cpp:265 +#, fuzzy, kde-format +#| msgid "Shade" +msgid "&Shade" +msgstr "Beskadu" + +#: useractions.cpp:271 +#, kde-format +msgid "&No Border" +msgstr "Geen Raam" + +#: useractions.cpp:279 +#, fuzzy, kde-format +#| msgid "Window &Shortcut..." +msgid "Set Window Short&cut..." +msgstr "Venster &Kortpad..." + +#: useractions.cpp:285 +#, fuzzy, kde-format +#| msgid "&Special Window Settings..." +msgid "Configure Special &Window Settings..." +msgstr "Spesiale Venster Instellings..." + +#: useractions.cpp:290 +#, fuzzy, kde-format +#| msgid "&Special Application Settings..." +msgid "Configure S&pecial Application Settings..." +msgstr "Spesiale Program Instellings..." + +#: useractions.cpp:297 +#, fuzzy, kde-format +#| msgid "KDE window manager" +msgctxt "" +"Entry in context menu of window decoration to open the configuration module " +"of KWin" +msgid "Configure W&indow Manager..." +msgstr "Kde venster bestuurder." + +#: useractions.cpp:324 +#, kde-format +msgid "Ma&ximize" +msgstr "Maksimeer" + +#: useractions.cpp:330 +#, kde-format +msgid "Mi&nimize" +msgstr "Minimeer" + +#: useractions.cpp:336 +#, kde-format +msgid "&More Actions" +msgstr "" + +#: useractions.cpp:339 +#, kde-format +msgid "&Close" +msgstr "" + +#: useractions.cpp:409 +#, kde-format +msgid "&Extensions" +msgstr "" + +#: useractions.cpp:460 +#, fuzzy, kde-format +#| msgid "&All Desktops" +msgid "&Desktops" +msgstr "Alle Werkskerms" + +#: useractions.cpp:474 +#, fuzzy, kde-format +#| msgid "To &Desktop" +msgid "Move to &Desktop" +msgstr "Na Werkskerm" + +#: useractions.cpp:491 +#, fuzzy, kde-format +#| msgid "To &Desktop" +msgid "Move to &Screen" +msgstr "Na Werkskerm" + +#: useractions.cpp:508 +#, kde-format +msgid "Show in &Activities" +msgstr "" + +#: useractions.cpp:524 useractions.cpp:594 +#, kde-format +msgid "&All Desktops" +msgstr "Alle Werkskerms" + +#: useractions.cpp:567 +#, fuzzy, kde-format +#| msgid "Switch to Desktop 1" +msgctxt "Create a new desktop and move the window there" +msgid "&New Desktop" +msgstr "Wissel na Werkskerm 1" + +#: useractions.cpp:638 +#, kde-format +msgid "Move to %1 %2" +msgstr "" + +#: useractions.cpp:651 +#, fuzzy, kde-format +#| msgid "Switch to Desktop 1" +msgctxt "Create a new desktop and add the window to that desktop" +msgid "Add to &New Desktop" +msgstr "Wissel na Werkskerm 1" + +#: useractions.cpp:663 +#, fuzzy, kde-format +#| msgid "To &Desktop" +msgctxt "Create a new desktop and move the window to that desktop" +msgid "Move to New Desktop" +msgstr "Na Werkskerm" + +#: useractions.cpp:694 +#, fuzzy, kde-format +#| msgid "Window to Desktop 1" +msgctxt "" +"@item:inmenu List of all Screens to send a window to. First argument is a " +"number, second the output identifier. E.g. Screen 1 (HDMI1)" +msgid "Screen &%1 (%2)" +msgstr "Venster na Werkskerm 1" + +#: useractions.cpp:720 +#, kde-format +msgid "&All Activities" +msgstr "" + +#: useractions.cpp:902 +#, kde-format +msgctxt "'%1' is a keyboard shortcut like 'ctrl+w'" +msgid "%1 is already in use" +msgstr "" + +#: useractions.cpp:904 +#, kde-format +msgctxt "keyboard shortcut '%1' is used by action '%2' in application '%3'" +msgid "%1 is used by %2 in %3" +msgstr "" + +#: useractions.cpp:984 +#, kde-format +msgid "Window Operations Menu" +msgstr "Venster Operasies Kieslys" + +#: useractions.cpp:986 +#, kde-format +msgid "Close Window" +msgstr "Sluit Venster" + +#: useractions.cpp:988 +#, kde-format +msgid "Maximize Window" +msgstr "Vergoot Venster" + +#: useractions.cpp:990 +#, kde-format +msgid "Maximize Window Vertically" +msgstr "Vergroot Venster Vertikaal" + +#: useractions.cpp:992 +#, kde-format +msgid "Maximize Window Horizontally" +msgstr "Vergroot Venster Horisontaal" + +#: useractions.cpp:994 +#, kde-format +msgid "Minimize Window" +msgstr "Verklein Venster" + +#: useractions.cpp:996 +#, kde-format +msgid "Shade Window" +msgstr "Verskadu Vensters" + +#: useractions.cpp:998 +#, kde-format +msgid "Move Window" +msgstr "Skuif Vensters" + +#: useractions.cpp:1000 +#, kde-format +msgid "Resize Window" +msgstr "Hervergroot Venster" + +#: useractions.cpp:1002 +#, kde-format +msgid "Raise Window" +msgstr "Lig Venster" + +#: useractions.cpp:1004 +#, kde-format +msgid "Lower Window" +msgstr "Verlaag Vensters" + +#: useractions.cpp:1006 +#, kde-format +msgid "Toggle Window Raise/Lower" +msgstr "Wissel Venster Lig/Sagter" + +#: useractions.cpp:1008 +#, kde-format +msgid "Make Window Fullscreen" +msgstr "Maak Venster Volskerm" + +#: useractions.cpp:1010 +#, kde-format +msgid "Hide Window Border" +msgstr "Versteek Venster Raam" + +#: useractions.cpp:1012 +#, kde-format +msgid "Keep Window Above Others" +msgstr "Hou Venster Bo-Op Ander" + +#: useractions.cpp:1014 +#, kde-format +msgid "Keep Window Below Others" +msgstr "Hou Venster Onder Ander" + +#: useractions.cpp:1016 +#, kde-format +msgid "Activate Window Demanding Attention" +msgstr "Aktiveer Venster Versoek Aandag" + +#: useractions.cpp:1018 +#, kde-format +msgid "Setup Window Shortcut" +msgstr "Stel Venster Kortpad" + +#: useractions.cpp:1020 +#, fuzzy, kde-format +#| msgid "Move Window" +msgid "Move Window to the Center" +msgstr "Skuif Vensters" + +#: useractions.cpp:1022 +#, fuzzy, kde-format +#| msgid "Move Window" +msgid "Move Window Right" +msgstr "Skuif Vensters" + +#: useractions.cpp:1024 +#, fuzzy, kde-format +#| msgid "Move Window" +msgid "Move Window Left" +msgstr "Skuif Vensters" + +#: useractions.cpp:1026 +#, fuzzy, kde-format +#| msgid "Move Window" +msgid "Move Window Up" +msgstr "Skuif Vensters" + +#: useractions.cpp:1028 +#, fuzzy, kde-format +#| msgid "Move Window" +msgid "Move Window Down" +msgstr "Skuif Vensters" + +#: useractions.cpp:1030 +#, fuzzy, kde-format +#| msgid "Maximize Window Horizontally" +msgid "Expand Window Horizontally" +msgstr "Vergroot Venster Horisontaal" + +#: useractions.cpp:1032 +#, fuzzy, kde-format +#| msgid "Maximize Window Vertically" +msgid "Expand Window Vertically" +msgstr "Vergroot Venster Vertikaal" + +#: useractions.cpp:1034 +#, fuzzy, kde-format +#| msgid "Pack Shrink Window Horizontally" +msgid "Shrink Window Horizontally" +msgstr "Verpak Verklein Venster Horisontaal" + +#: useractions.cpp:1036 +#, fuzzy, kde-format +#| msgid "Pack Shrink Window Vertically" +msgid "Shrink Window Vertically" +msgstr "Verpak Verklein Venster Vertikaal" + +#: useractions.cpp:1038 +#, fuzzy, kde-format +#| msgid "Pack Window to the Left" +msgid "Quick Tile Window to the Left" +msgstr "Verpak Venster na Links" + +#: useractions.cpp:1040 +#, fuzzy, kde-format +#| msgid "Pack Window to the Right" +msgid "Quick Tile Window to the Right" +msgstr "Verpak Venster na Regs" + +#: useractions.cpp:1042 +#, fuzzy, kde-format +#| msgid "Pack Window to the Left" +msgid "Quick Tile Window to the Top" +msgstr "Verpak Venster na Links" + +#: useractions.cpp:1044 +#, fuzzy, kde-format +#| msgid "Pack Window to the Left" +msgid "Quick Tile Window to the Bottom" +msgstr "Verpak Venster na Links" + +#: useractions.cpp:1046 +#, fuzzy, kde-format +#| msgid "Pack Window to the Left" +msgid "Quick Tile Window to the Top Left" +msgstr "Verpak Venster na Links" + +#: useractions.cpp:1048 +#, fuzzy, kde-format +#| msgid "Pack Window to the Left" +msgid "Quick Tile Window to the Bottom Left" +msgstr "Verpak Venster na Links" + +#: useractions.cpp:1050 +#, fuzzy, kde-format +#| msgid "Pack Window to the Right" +msgid "Quick Tile Window to the Top Right" +msgstr "Verpak Venster na Regs" + +#: useractions.cpp:1052 +#, fuzzy, kde-format +#| msgid "Pack Window to the Right" +msgid "Quick Tile Window to the Bottom Right" +msgstr "Verpak Venster na Regs" + +#: useractions.cpp:1054 +#, fuzzy, kde-format +#| msgid "Switch to Desktop 10" +msgid "Switch to Window Above" +msgstr "Wissel na Werkskerm 10" + +#: useractions.cpp:1056 +#, fuzzy, kde-format +#| msgid "Switch to Previous Desktop" +msgid "Switch to Window Below" +msgstr "Wissel na Vorige Werkskerm" + +#: useractions.cpp:1058 +#, fuzzy, kde-format +#| msgid "Pack Window to the Right" +msgid "Switch to Window to the Right" +msgstr "Verpak Venster na Regs" + +#: useractions.cpp:1060 +#, fuzzy, kde-format +#| msgid "Pack Window to the Left" +msgid "Switch to Window to the Left" +msgstr "Verpak Venster na Links" + +#: useractions.cpp:1062 +#, kde-format +msgid "Increase Opacity of Active Window by 5 %" +msgstr "" + +#: useractions.cpp:1064 +#, kde-format +msgid "Decrease Opacity of Active Window by 5 %" +msgstr "" + +#: useractions.cpp:1067 +#, kde-format +msgid "Keep Window on All Desktops" +msgstr "Hou Venster op Alle Werkskerm" + +#: useractions.cpp:1078 +#, fuzzy, kde-format +#| msgid "Window to Desktop 1" +msgid "Window to Desktop %1" +msgstr "Venster na Werkskerm 1" + +#: useractions.cpp:1080 +#, kde-format +msgid "Window to Next Desktop" +msgstr "Venster na Volgende Werkskerm" + +#: useractions.cpp:1081 +#, kde-format +msgid "Window to Previous Desktop" +msgstr "Venster na Vorige Werkskerm" + +#: useractions.cpp:1082 +#, kde-format +msgid "Window One Desktop to the Right" +msgstr "Vesnter Een Werkskerm na Regs" + +#: useractions.cpp:1084 +#, kde-format +msgid "Window One Desktop to the Left" +msgstr "Venster Een Werkskerm na Links" + +#: useractions.cpp:1086 +#, kde-format +msgid "Window One Desktop Up" +msgstr "Venster Een Werkskerm Op" + +#: useractions.cpp:1088 +#, kde-format +msgid "Window One Desktop Down" +msgstr "Venster Een Werkskerm Af" + +#: useractions.cpp:1092 +#, fuzzy, kde-format +#| msgid "Window to Desktop 1" +msgid "Window to Screen %1" +msgstr "Venster na Werkskerm 1" + +#: useractions.cpp:1099 +#, fuzzy, kde-format +#| msgid "Window to Next Desktop" +msgid "Window to Next Screen" +msgstr "Venster na Volgende Werkskerm" + +#: useractions.cpp:1101 +#, fuzzy, kde-format +#| msgid "Window to Previous Desktop" +msgid "Window to Previous Screen" +msgstr "Venster na Vorige Werkskerm" + +#: useractions.cpp:1103 +#, fuzzy, kde-format +#| msgid "To &Desktop" +msgid "Peek at Desktop" +msgstr "Na Werkskerm" + +#: useractions.cpp:1107 +#, fuzzy, kde-format +#| msgid "Switch to Desktop 1" +msgid "Switch to Screen %1" +msgstr "Wissel na Werkskerm 1" + +#: useractions.cpp:1115 +#, fuzzy, kde-format +#| msgid "Switch to Next Desktop" +msgid "Switch to Next Screen" +msgstr "Wissel na Volgende Werkskerm" + +#: useractions.cpp:1116 +#, fuzzy, kde-format +#| msgid "Switch to Previous Desktop" +msgid "Switch to Previous Screen" +msgstr "Wissel na Vorige Werkskerm" + +#: useractions.cpp:1118 +#, kde-format +msgid "Kill Window" +msgstr "Stop Venster" + +#: useractions.cpp:1119 +#, kde-format +msgid "Suspend Compositing" +msgstr "" + +#: useractions.cpp:1120 +#, kde-format +msgid "Invert Screen Colors" +msgstr "" + +#: useractions.cpp:1181 +#, kde-format +msgid "Activate Window (%1)" +msgstr "" + +#: useractions.cpp:1318 +#, kde-format +msgid "" +"The window manager is configured to consider the screen with the mouse on it " +"as active one.\n" +"Therefore it is not possible to switch to a screen explicitly." +msgstr "" + +#: virtualdesktops.cpp:688 virtualdesktops.cpp:759 +#, kde-format +msgid "Desktop %1" +msgstr "Werkskerm %1" + +#: virtualdesktops.cpp:794 +#, kde-format +msgid "Switch to Next Desktop" +msgstr "Wissel na Volgende Werkskerm" + +#: virtualdesktops.cpp:795 +#, kde-format +msgid "Switch to Previous Desktop" +msgstr "Wissel na Vorige Werkskerm" + +#: virtualdesktops.cpp:798 +#, kde-format +msgid "Switch One Desktop to the Right" +msgstr "Wissel Een Werkskerm na die Regterkant" + +#: virtualdesktops.cpp:800 +#, kde-format +msgid "Switch One Desktop to the Left" +msgstr "Wissel Een Werkskerm na die Links" + +#: virtualdesktops.cpp:802 +#, kde-format +msgid "Switch One Desktop Up" +msgstr "Wissel Een Werkskerm Begin" + +#: virtualdesktops.cpp:804 +#, kde-format +msgid "Switch One Desktop Down" +msgstr "Wissel Een Werkskerm Ondertoe" + +#: virtualdesktops.cpp:893 +#, fuzzy, kde-format +#| msgid "Switch to Desktop 1" +msgid "Switch to Desktop %1" +msgstr "Wissel na Werkskerm 1" + +#: window.cpp:3428 +#, kde-format +msgctxt "Application is not responding, appended to window title" +msgid "(Not Responding)" +msgstr "" + +#: workspace.cpp:1704 +#, kde-format +msgctxt "Introductory text shown in the support information." +msgid "" +"KWin Support Information:\n" +"The following information should be used when requesting support on e.g. " +"https://forum.kde.org.\n" +"It provides information about the currently running instance, which options " +"are used,\n" +"what OpenGL driver and which effects are running.\n" +"Please post the information provided underneath this introductory text to a " +"paste bin service\n" +"like https://paste.kde.org instead of pasting into support threads.\n" +msgstr "" \ No newline at end of file diff --git a/po/af/kwin_clients.po b/po/af/kwin_clients.po new file mode 100644 index 0000000..4d5152d --- /dev/null +++ b/po/af/kwin_clients.po @@ -0,0 +1,132 @@ +# UTF-8 test:äëïöü +# Copyright (C) 2001, 2005 Free Software Foundation, Inc. +# Kobus , 2005. +# +msgid "" +msgstr "" +"Project-Id-Version: kwin_clients stable\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2022-08-07 00:48+0000\n" +"PO-Revision-Date: 2005-11-25 22:32+0200\n" +"Last-Translator: Kobus \n" +"Language-Team: AFRIKAANS \n" +"Language: af\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.10.2\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#: aurorae/src/aurorae.cpp:725 +#, kde-format +msgctxt "@item:inlistbox Button size:" +msgid "Tiny" +msgstr "" + +#: aurorae/src/aurorae.cpp:726 +#, kde-format +msgctxt "@item:inlistbox Button size:" +msgid "Normal" +msgstr "" + +#: aurorae/src/aurorae.cpp:727 +#, fuzzy, kde-format +#| msgid "Large" +msgctxt "@item:inlistbox Button size:" +msgid "Large" +msgstr "Groot" + +#: aurorae/src/aurorae.cpp:728 +#, fuzzy, kde-format +#| msgid "Large" +msgctxt "@item:inlistbox Button size:" +msgid "Very Large" +msgstr "Groot" + +#: aurorae/src/aurorae.cpp:729 +#, fuzzy, kde-format +#| msgid "Large" +msgctxt "@item:inlistbox Button size:" +msgid "Huge" +msgstr "Groot" + +#: aurorae/src/aurorae.cpp:730 +#, fuzzy, kde-format +#| msgid "Large" +msgctxt "@item:inlistbox Button size:" +msgid "Very Huge" +msgstr "Groot" + +#: aurorae/src/aurorae.cpp:731 +#, fuzzy, kde-format +#| msgid "Resize" +msgctxt "@item:inlistbox Button size:" +msgid "Oversized" +msgstr "Vergroot" + +#: aurorae/src/aurorae.cpp:734 +#, kde-format +msgid "Button size:" +msgstr "" + +#. i18n: ectx: property (windowTitle), widget (QWidget, PlastikConfigDialog) +#: aurorae/themes/plastik/package/contents/ui/config.ui:14 +#, kde-format +msgid "Config Dialog" +msgstr "Konfigurasie Dialoog" + +#. i18n: ectx: property (title), widget (KButtonGroup, titleAlign) +#: aurorae/themes/plastik/package/contents/ui/config.ui:23 +#, kde-format +msgid "Title &Alignment" +msgstr "Titel Belyning" + +#. i18n: ectx: property (text), widget (QRadioButton, kcfg_titleAlignLeft) +#: aurorae/themes/plastik/package/contents/ui/config.ui:29 +#, kde-format +msgid "Left" +msgstr "" + +#. i18n: ectx: property (text), widget (QRadioButton, kcfg_titleAlignCenter) +#: aurorae/themes/plastik/package/contents/ui/config.ui:36 +#, kde-format +msgid "Center" +msgstr "" + +#. i18n: ectx: property (text), widget (QRadioButton, kcfg_titleAlignRight) +#: aurorae/themes/plastik/package/contents/ui/config.ui:43 +#, kde-format +msgid "Right" +msgstr "" + +#. i18n: ectx: property (whatsThis), widget (QCheckBox, kcfg_coloredBorder) +#: aurorae/themes/plastik/package/contents/ui/config.ui:53 +#, kde-format +msgid "" +"Check this option if the window border should be painted in the titlebar " +"color. Otherwise it will be painted in the background color." +msgstr "" +"Kies hierdie opsie indien die venster rand dieslefde kleur as die titelbalk " +"moet wees. Andersins sal dit volgens die agtergrond kleur ingekleur word." + +#. i18n: ectx: property (text), widget (QCheckBox, kcfg_coloredBorder) +#: aurorae/themes/plastik/package/contents/ui/config.ui:56 +#, fuzzy, kde-format +msgid "Colored window border" +msgstr "Gekleurde venster rand" + +#. i18n: ectx: property (whatsThis), widget (QCheckBox, kcfg_animateButtons) +#: aurorae/themes/plastik/package/contents/ui/config.ui:66 +#, kde-format +msgid "" +"Check this option if you want the buttons to fade in when the mouse pointer " +"hovers over them and fade out again when it moves away." +msgstr "" +"Kies hierdie opsie indien jy wil hê dat die knoppies moet verhelder wanneer " +"die muis wyser daaroor huiwer, en weer verdof wanneer dit weg beweeg." + +#. i18n: ectx: property (text), widget (QCheckBox, kcfg_animateButtons) +#: aurorae/themes/plastik/package/contents/ui/config.ui:69 +#, kde-format +msgid "Animate buttons" +msgstr "Animeer knoppies" \ No newline at end of file diff --git a/po/ar/kcm_kwin_effects.po b/po/ar/kcm_kwin_effects.po new file mode 100644 index 0000000..b554e0b --- /dev/null +++ b/po/ar/kcm_kwin_effects.po @@ -0,0 +1,93 @@ +# Copyright (C) YEAR This file is copyright: +# This file is distributed under the same license as the kwin package. +# +# Zayed Al-Saidi , 2021. +msgid "" +msgstr "" +"Project-Id-Version: kwin\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2022-03-26 00:45+0000\n" +"PO-Revision-Date: 2021-07-20 00:26+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: \n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" +"X-Generator: Lokalize 19.12.3\n" + +#, kde-format +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "زايد السعيدي" + +#, kde-format +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "zayed.alsaidi@gmail.com" + +#: kcm.cpp:35 +#, kde-format +msgid "Desktop Effects" +msgstr "تأثيرات سطح المكتب" + +#: kcm.cpp:39 +#, kde-format +msgid "Vlad Zahorodnii" +msgstr "Vlad Zahorodnii" + +#: package/contents/ui/Effect.qml:92 +#, kde-format +msgid "" +"Author: %1\n" +"License: %2" +msgstr "" +"المؤلف: %1\n" +"الرخصة: %2" + +#: package/contents/ui/Effect.qml:121 +#, kde-format +msgctxt "@info:tooltip" +msgid "Show/Hide Video" +msgstr "أظهر/أخف الفيديو" + +#: package/contents/ui/Effect.qml:128 +#, kde-format +msgctxt "@info:tooltip" +msgid "Configure..." +msgstr "اضبط..." + +#: package/contents/ui/main.qml:20 +#, kde-format +msgid "This module lets you configure desktop effects." +msgstr "تتيح لك هذه الوحدة ضبط تأثيرات سطح المكتب." + +#: package/contents/ui/main.qml:27 +#, kde-format +msgid "" +"Hint: To find out or configure how to activate an effect, look at the " +"effect's settings." +msgstr "" +"تلميح: لمعرفة كيفية تنشيط التأثير أو تكوينه ، انظر إلى إعدادات التأثير." + +#: package/contents/ui/main.qml:47 +#, kde-format +msgid "Configure Filter" +msgstr "مرشح الضبط" + +#: package/contents/ui/main.qml:59 +#, kde-format +msgid "Exclude unsupported effects" +msgstr "استبعد التأثير غير المدعومة" + +#: package/contents/ui/main.qml:67 +#, kde-format +msgid "Exclude internal effects" +msgstr "استبعد التأثيرات الداخلية" + +#: package/contents/ui/main.qml:125 +#, kde-format +msgid "Get New Desktop Effects..." +msgstr "احصل على تأثيرات جديدة..." \ No newline at end of file diff --git a/po/ar/kcm_kwin_scripts.po b/po/ar/kcm_kwin_scripts.po new file mode 100644 index 0000000..22bf35f --- /dev/null +++ b/po/ar/kcm_kwin_scripts.po @@ -0,0 +1,76 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# Safa Alfulaij , 2015. +# Zayed Al-Saidi , 2021, 2022. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2022-09-20 02:18+0000\n" +"PO-Revision-Date: 2022-09-20 20:34+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" +"X-Generator: Lokalize 21.12.3\n" + +#, kde-format +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "صفا الفليج" + +#, kde-format +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "safa1996alfulaij@gmail.com" + +#: module.cpp:57 +#, kde-format +msgid "Import KWin Script" +msgstr "استورد سكرِبت نوافذك" + +#: module.cpp:58 +#, kde-format +msgid "*.kwinscript|KWin scripts (*.kwinscript)" +msgstr "*.kwinscript|سكرِبتات نوافذك (*.kwinscript)" + +#: module.cpp:96 +#, kde-format +msgctxt "Placeholder is error message returned from the install service" +msgid "" +"Cannot import selected script.\n" +"%1" +msgstr "" +"لا يمكن استيراد السكربت المحدد.\n" +"%1" + +#: module.cpp:108 +#, kde-format +msgctxt "Placeholder is name of the script that was imported" +msgid "The script \"%1\" was successfully imported." +msgstr "استورد السّكرِبت \"%1\" بنجاح." + +#: module.cpp:148 +#, kde-format +msgid "Error when uninstalling KWin Script: %1" +msgstr "خطأ عند إلغاء تثبيت سكربت كوين: %1" + +#: package/contents/ui/main.qml:47 +#, kde-format +msgctxt "@info:tooltip" +msgid "Delete..." +msgstr "احذف..." + +#: package/contents/ui/main.qml:60 +#, kde-format +msgid "Install from File..." +msgstr "ثبت من ملف..." + +#: package/contents/ui/main.qml:64 +#, kde-format +msgid "Get New Scripts..." +msgstr "احصل على سكرِبت جديد..." \ No newline at end of file diff --git a/po/ar/kcm_kwin_virtualdesktops.po b/po/ar/kcm_kwin_virtualdesktops.po new file mode 100644 index 0000000..f7cca7f --- /dev/null +++ b/po/ar/kcm_kwin_virtualdesktops.po @@ -0,0 +1,143 @@ +# translation of kcm_kwindesktop.po to +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# محمد ابراهيم الحرقان , 2008. +# Safa Alfulaij , ٢٠١٥. +# Zayed Al-Saidi , 2021. +msgid "" +msgstr "" +"Project-Id-Version: kcm_kwindesktop\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2022-07-12 00:47+0000\n" +"PO-Revision-Date: 2021-06-29 12:20+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" +"X-Generator: Lokalize 21.07.70\n" + +#, kde-format +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "زايد السعيدي" + +#, kde-format +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "zayed.alsaidi@gmail.com" + +#: desktopsmodel.cpp:467 +#, kde-format +msgid "There was an error connecting to the compositor." +msgstr "حدث خطأ أثناء الاتصال ببرنامج مزج التأثيرات الخاصة للعرض." + +#: desktopsmodel.cpp:666 +#, kde-format +msgid "There was an error saving the settings to the compositor." +msgstr "حدث خطأ أثناء حفظ الإعدادات إلى برنامج مزج التأثيرات الخاصة للعرض." + +#: desktopsmodel.cpp:669 +#, kde-format +msgid "There was an error requesting information from the compositor." +msgstr "" +"حدث خطأ أثناء الحصول على معلومات حول برنامج مزج التأثيرات الخاصة للعرض." + +#: package/contents/ui/main.qml:17 +#, kde-format +msgid "" +"This module lets you configure the navigation, number and layout of virtual " +"desktops." +msgstr "تتيح لك هذه الوحدة تكوين التنقل ، وعدد وتخطيط أسطح المكتب الافتراضية." + +#: package/contents/ui/main.qml:91 +#, kde-format +msgctxt "@info:tooltip" +msgid "Rename" +msgstr "غيّر الاسم" + +#: package/contents/ui/main.qml:102 +#, kde-format +msgctxt "@info:tooltip" +msgid "Confirm new name" +msgstr "أكِّد عبارة الاسم الجديد" + +#: package/contents/ui/main.qml:110 +#, kde-format +msgctxt "@info:tooltip" +msgid "Remove" +msgstr "أزل" + +#: package/contents/ui/main.qml:137 +#, kde-format +msgid "" +"Virtual desktops have been changed outside this settings application. Saving " +"now will overwrite the changes." +msgstr "" +"غُيرت أسطح المكتب الافتراضية خارج تطبيق الإعدادات هذا. الحفظ الآن سيحل محل " +"تلك التغييرات." + +#: package/contents/ui/main.qml:153 +#, kde-format +msgid "Row %1" +msgstr "الصف %1" + +#: package/contents/ui/main.qml:166 +#, kde-format +msgctxt "@action:button" +msgid "Add" +msgstr "أضِف" + +#: package/contents/ui/main.qml:169 +#, kde-format +msgid "New Desktop" +msgstr "سطح مكتب جديد" + +#: package/contents/ui/main.qml:184 +#, kde-format +msgid "1 Row" +msgid_plural "%1 Rows" +msgstr[0] "٠ صف" +msgstr[1] "صف واحد" +msgstr[2] "صفين" +msgstr[3] "%1 صفوف" +msgstr[4] "%1 صفا" +msgstr[5] "%1 صف" + +#: package/contents/ui/main.qml:200 +#, kde-format +msgid "Options:" +msgstr "الخيارات:" + +#: package/contents/ui/main.qml:202 +#, kde-format +msgid "Navigation wraps around" +msgstr "يلف التنقل" + +#: package/contents/ui/main.qml:220 +#, kde-format +msgid "Show animation when switching:" +msgstr "أظهر التحريك عند التبديل:" + +#: package/contents/ui/main.qml:271 +#, kde-format +msgid "Show on-screen display when switching:" +msgstr "إظهار العرض على الشاشة عند التبديل:" + +#: package/contents/ui/main.qml:290 +#, kde-format +msgid "%1 ms" +msgstr "%1 مث" + +#: package/contents/ui/main.qml:314 +#, kde-format +msgid "Show desktop layout indicators" +msgstr "أظهر مؤشرات تخطيط سطح المكتب" + +#: virtualdesktops.cpp:33 +#, kde-format +msgid "Virtual Desktops" +msgstr "أسطح المكتب الافتراضية" \ No newline at end of file diff --git a/po/ar/kcm_kwindecoration.po b/po/ar/kcm_kwindecoration.po new file mode 100644 index 0000000..7051b47 --- /dev/null +++ b/po/ar/kcm_kwindecoration.po @@ -0,0 +1,296 @@ +# translation of kcmkwindecoration.po to Arabic +# translation of kcmkwindecoration.po to +# Copyright (C) 2001,2002, 2004, 2006, 2007, 2008 Free Software Foundation, Inc. +# Mohammed Gamal , 2001. +# Isam Bayazidi , 2002. +# Ossama Khayat , 2004. +# محمد سعد Mohamed SAAD , 2006. +# AbdulAziz AlSharif , 2007. +# Youssef Chahibi , 2007. +# zayed , 2008. +# Zayed Al-Saidi , 2010, 2021. +# Safa Alfulaij , ٢٠١٦. +msgid "" +msgstr "" +"Project-Id-Version: kcmkwindecoration\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2022-10-26 02:35+0000\n" +"PO-Revision-Date: 2021-07-20 00:38+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: \n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" +"X-Generator: Lokalize 19.12.3\n" + +#, kde-format +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Mohamed SAAD محمد سعد,زايد السعيدي" + +#, kde-format +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "metehyi@free.fr ,zayed.alsaidi@gmail.com" + +#: declarative-plugin/buttonsmodel.cpp:53 +#, kde-format +msgid "More actions for this window" +msgstr "إجراءات أكثر لهذه النافذة" + +#: declarative-plugin/buttonsmodel.cpp:55 +#, kde-format +msgid "Application menu" +msgstr "قائمة التّطبيق" + +#: declarative-plugin/buttonsmodel.cpp:57 +#, kde-format +msgid "On all desktops" +msgstr "على كلّ أسطح المكتب" + +#: declarative-plugin/buttonsmodel.cpp:59 +#, kde-format +msgid "Minimize" +msgstr "صغّر" + +#: declarative-plugin/buttonsmodel.cpp:61 +#, kde-format +msgid "Maximize" +msgstr "كبّر" + +#: declarative-plugin/buttonsmodel.cpp:63 +#, kde-format +msgid "Close" +msgstr "أغلق" + +#: declarative-plugin/buttonsmodel.cpp:65 +#, kde-format +msgid "Context help" +msgstr "مساعدة سياقيّة" + +#: declarative-plugin/buttonsmodel.cpp:67 +#, kde-format +msgid "Shade" +msgstr "ظلّل" + +#: declarative-plugin/buttonsmodel.cpp:69 +#, kde-format +msgid "Keep below other windows" +msgstr "أبقها أسفل النوافذ الأخرى" + +#: declarative-plugin/buttonsmodel.cpp:71 +#, kde-format +msgid "Keep above other windows" +msgstr "أبقها أعلى النوافذ الأخرى" + +#: kcm.cpp:49 +#, kde-format +msgid "Window Decorations" +msgstr "زخارف النوافذ" + +#: kcm.cpp:53 +#, kde-format +msgid "Valerio Pilo" +msgstr "Valerio Pilo" + +#: kcm.cpp:54 +#, kde-format +msgid "Author" +msgstr "المؤلف" + +#: kcm.cpp:183 +#, kde-format +msgctxt "%1 is the name of a border size" +msgid "Theme's default (%1)" +msgstr "السمة المبدئيّة (%1)" + +#: kwin-applywindowdecoration.cpp:32 +#, kde-format +msgid "" +"This tool allows you to set the window decoration theme for the currently " +"active session, without accidentally setting it to one that is either not " +"available, or which is already set." +msgstr "" +"تتيح لك هذه الأداة ضبط سمة زخارف النوافذ لجلسة البلازما الحالية، دون تعينها " +"عن طريق الخطأ لسمة إما غير متوفرة ، أو عينت بالفعل." + +#: kwin-applywindowdecoration.cpp:33 +#, kde-format +msgid "" +"The name of the window decoration theme you wish to set for KWin. Passing a " +"full path will attempt to find a theme in that directory, and then apply " +"that if one can be deduced." +msgstr "" +"اسم سمة زخرفة النافذة التي ترغب في تعيينها لـكوين. عند تقديم مسار كامل " +"سيحاول العثور على سمة في ذاك الدليل ، ثم تطبيق ذلك إذا وجدها." + +#: kwin-applywindowdecoration.cpp:34 +#, kde-format +msgid "" +"Show all the themes available on the system (and which is the current theme)" +msgstr "اعرض جميع السمات المتوفرة على النظام (وما السمة الحالية)" + +#: kwin-applywindowdecoration.cpp:65 +#, kde-format +msgid "" +"Resolved %1 to the KWin Aurorae theme \"%2\", and will attempt to set that " +"as your current theme." +msgstr "حل %1 إلى سمة أرورا لكوين \"%2\"، وسنحاول تعينه كسمتك الحالية." + +#: kwin-applywindowdecoration.cpp:71 +#, kde-format +msgid "" +"You attempted to pass a file path, but this could not be resolved to a " +"theme, and we will have to abort, due to having no theme to set" +msgstr "" +"لقد حاولت تمرير مسار ملف ، ولكن لا يمكن حل هذا إلى سمة ، وسيتعين علينا إحباط " +"ذلك ، نظرًا لعدم وجود سمة لتعيينها" + +#: kwin-applywindowdecoration.cpp:77 +#, kde-format +msgid "" +"The requested theme \"%1\" is already set as the window decoration theme." +msgstr "عينت السمة المطلوبة \"%1\" بالفعل كسمة لجلسة البلازما الحالية." + +#: kwin-applywindowdecoration.cpp:99 +#, kde-format +msgid "Successfully applied the cursor theme %1 to your current Plasma session" +msgstr "طبقت سمة المؤشر %1 بنجاح لجلسة بلازما الحالية" + +#: kwin-applywindowdecoration.cpp:103 +#, kde-format +msgid "" +"Failed to save your theme settings - the reason is unknown, but this is an " +"unrecoverable error. You may find that simply trying again will work." +msgstr "" +"فشل في حفظ إعدادات السمة - السبب غير معروف ، لكن هذا خطأ لا يمكن إصلاحه. قد " +"تجد أن مجرد المحاولة مرة أخرى ستنجح." + +#: kwin-applywindowdecoration.cpp:107 +#, kde-format +msgid "" +"Could not find theme \"%1\". The theme should be one of the following " +"options: %2" +msgstr "" +"لم يعثر على السمة المطلوبة \"%1\". السمة يجب أن تكون واحدة من الخيارات " +"التالية: %2" + +#: kwin-applywindowdecoration.cpp:112 +#, kde-format +msgid "You have the following KWin window decoration themes on your system:" +msgstr "لديك سمات زخرفة النوافذ لكوين التالية على نظامك:" + +#: package/contents/ui/Buttons.qml:85 +#, kde-format +msgid "Titlebar" +msgstr "شريط العنوان" + +#: package/contents/ui/Buttons.qml:245 +#, kde-format +msgid "Drop button here to remove it" +msgstr "أفلت هنا لإزالة الزّرّ" + +#: package/contents/ui/Buttons.qml:261 +#, kde-format +msgid "Drag buttons between here and the titlebar" +msgstr "اسحب الأزرار من وإلا شريط العنوان" + +#: package/contents/ui/main.qml:18 +#, kde-format +msgid "This module lets you configure the window decorations." +msgstr "تتيح لك هذه الوحدة ضبط زخارف النافذة." + +#: package/contents/ui/main.qml:52 +#, kde-format +msgctxt "tab label" +msgid "Theme" +msgstr "السمة" + +#: package/contents/ui/main.qml:56 +#, kde-format +msgctxt "tab label" +msgid "Titlebar Buttons" +msgstr "أزرار شريط العنوان" + +#: package/contents/ui/main.qml:92 +#, kde-format +msgctxt "Selector label" +msgid "Window border size:" +msgstr "حجم حافة النافذة:" + +#: package/contents/ui/main.qml:110 +#, kde-format +msgctxt "button text" +msgid "Get New Window Decorations..." +msgstr "اجلب زخرفة جديدة..." + +#: package/contents/ui/main.qml:139 +#, kde-format +msgctxt "checkbox label" +msgid "Close windows by double clicking the menu button" +msgstr "أغلق النّوافذ بنقر مزدوج على زر القائمة" + +#: package/contents/ui/main.qml:156 +#, kde-format +msgctxt "popup tip" +msgid "Click and hold on the menu button to show the menu." +msgstr "انقر وامسك على زر القائمة لإظهار القائمة" + +#: package/contents/ui/main.qml:163 +#, kde-format +msgctxt "checkbox label" +msgid "Show titlebar button tooltips" +msgstr "أظهر تلميحات زر شريط العنوان" + +#: package/contents/ui/Themes.qml:92 +#, kde-format +msgid "Edit %1 Theme" +msgstr "حرّر السّمة %1" + +#: utils.cpp:25 +#, kde-format +msgid "No Borders" +msgstr "لا حدود" + +#: utils.cpp:26 +#, kde-format +msgid "No Side Borders" +msgstr "لا حدود جانبيّة" + +#: utils.cpp:27 +#, kde-format +msgid "Tiny" +msgstr "ضئيل" + +#: utils.cpp:28 +#, kde-format +msgid "Normal" +msgstr "عادي" + +#: utils.cpp:29 +#, kde-format +msgid "Large" +msgstr "كبير" + +#: utils.cpp:30 +#, kde-format +msgid "Very Large" +msgstr "كبير جدًّا" + +#: utils.cpp:31 +#, kde-format +msgid "Huge" +msgstr "ضخم" + +#: utils.cpp:32 +#, kde-format +msgid "Very Huge" +msgstr "ضخم جدًّا" + +#: utils.cpp:33 +#, kde-format +msgid "Oversized" +msgstr "فوق الطّبيعيّ" \ No newline at end of file diff --git a/po/ar/kcm_kwinrules.po b/po/ar/kcm_kwinrules.po new file mode 100644 index 0000000..1ab3a12 --- /dev/null +++ b/po/ar/kcm_kwinrules.po @@ -0,0 +1,947 @@ +# translation of kcmkwinrules.po to Arabic +# translation of kcmkwinrules.po to +# محمد سعد Mohamed SAAD , 2006. +# AbdulAziz AlSharif , 2007. +# Youssef Chahibi , 2007. +# zayed , 2008, 2021, 2022. +msgid "" +msgstr "" +"Project-Id-Version: kcmkwinrules\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2022-08-19 00:47+0000\n" +"PO-Revision-Date: 2022-04-28 12:40+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: Arabic \n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" +"X-Generator: Lokalize 21.07.70\n" + +#, kde-format +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Mohamed SAAD محمد سعد,زايد السعيدي" + +#, kde-format +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "metehyi@free.fr,zayed.alsaidi@gmail.com" + +#: kcmrules.cpp:28 +#, kde-format +msgid "Window Rules" +msgstr "قواعد النوافذ" + +#: kcmrules.cpp:32 +#, kde-format +msgid "Ismael Asensio" +msgstr "Ismael Asensio" + +#: kcmrules.cpp:33 +#, kde-format +msgid "Author" +msgstr "المؤلف" + +#: kcmrules.cpp:37 +#, kde-format +msgid "" +"

    Window-specific Settings

    Here you can customize window settings " +"specifically only for some windows.

    Please note that this " +"configuration will not take effect if you do not use KWin as your window " +"manager. If you do use a different window manager, please refer to its " +"documentation for how to customize window behavior.

    " +msgstr "" +"

    إعدادات خاصة بالنافذة

    هنا يمكنك تخصيص إعدادات النافذة خصوصا لبعض " +"النوافذ فقط.

    الرجاء ملاحظة أن هذا الضبط لن يفعل إذا اختر غير كوِن " +"كمدير للنوافذ.إذا كنت تستخدم مدير نوافذ مختلف ، فالرجاء الرجوع إلى " +"وثائقهلكيفية تخصيص سلوك النافذة.

    " + +#: kcmrules.cpp:243 +#, kde-format +msgid "Copy of %1" +msgstr "نسخة عن %1" + +#: kcmrules.cpp:422 +#, kde-format +msgid "Application settings for %1" +msgstr "إعدادات التطبيق لـِ %1" + +#: kcmrules.cpp:442 rulesmodel.cpp:215 +#, kde-format +msgid "Window settings for %1" +msgstr "إعدادات النافذة لـِ %1" + +#: main.cpp:31 +#, kde-format +msgid "KWinRules KCM launcher" +msgstr "مطلق وحدة تحكم بقواد كوين" + +#: main.cpp:32 +#, kde-format +msgid "KWin id of the window for special window settings." +msgstr "معرف كوين للنافذة لإعدادات الخاصة بالنافذة." + +#: main.cpp:33 +#, kde-format +msgid "Whether the settings should affect all windows of the application." +msgstr "إذا كانت الإعدادات ستوثر على كلّ نوافذ التطبيق" + +#: main.cpp:40 +#, kde-format +msgid "This helper utility is not supposed to be called directly." +msgstr "أداة المساعدة هذه لا تدعم الاستدعاء مباشرة" + +#: main.cpp:44 +#, kde-format +msgctxt "Window caption for the application wide rules dialog" +msgid "Edit Application-Specific Settings" +msgstr "حرّر الإعدادات الخاصة بالتطبيق" + +#: main.cpp:45 +#, kde-format +msgid "Edit Window-Specific Settings" +msgstr "حرّر الإعدادات الخاصة بالنافذة" + +#: optionsmodel.cpp:198 +#, kde-format +msgid "Unimportant" +msgstr "غير مهم" + +#: optionsmodel.cpp:199 +#, kde-format +msgid "Exact Match" +msgstr "تطابق تام" + +#: optionsmodel.cpp:200 +#, kde-format +msgid "Substring Match" +msgstr "تطابق السلسلة الفرعية" + +#: optionsmodel.cpp:201 +#, kde-format +msgid "Regular Expression" +msgstr "التعبير النمطي" + +#: optionsmodel.cpp:205 +#, kde-format +msgid "Apply Initially" +msgstr "طبّق مبدئياً" + +#: optionsmodel.cpp:206 +#, kde-format +msgid "" +"The window property will be only set to the given value after the window is " +"created.\n" +"No further changes will be affected." +msgstr "" +"ستعين خاصية النافذة إلى القيمة المحددة فقط بعد إنشاء النافذة. \n" +"لن تتأثر أي تغييرات أخرى." + +#: optionsmodel.cpp:209 +#, kde-format +msgid "Apply Now" +msgstr "طبّق الآن" + +#: optionsmodel.cpp:210 +#, kde-format +msgid "" +"The window property will be set to the given value immediately and will not " +"be affected later\n" +"(this action will be deleted afterwards)." +msgstr "" +"ستعين خاصية النافذة على القيمة المحددة فورًا ولن تتأثر لاحقًا \n" +"(سيحذف هذا الإجراء بعد ذلك)." + +#: optionsmodel.cpp:213 +#, kde-format +msgid "Remember" +msgstr "تذكّر" + +#: optionsmodel.cpp:214 +#, kde-format +msgid "" +"The value of the window property will be remembered and, every time the " +"window is created, the last remembered value will be applied." +msgstr "" +"سيتم تذكر قيمة خاصية النافذة، وفي كل مرة يتم فيها إنشاء النافذة، سيطبق آخر " +"قيمة محفوظة." + +#: optionsmodel.cpp:217 +#, kde-format +msgid "Do Not Affect" +msgstr "لا يتأثر" + +#: optionsmodel.cpp:218 +#, kde-format +msgid "" +"The window property will not be affected and therefore the default handling " +"for it will be used.\n" +"Specifying this will block more generic window settings from taking effect." +msgstr "" +"لن تتأثر خاصية النافذة وبالتالي ستستعمل المعالجة الافتراضية لها. \n" +"سيؤدي تحديد هذا إلى منع المزيد من إعدادات النافذة العامة من أن تصبح سارية " +"المفعول." + +#: optionsmodel.cpp:221 +#, kde-format +msgid "Force" +msgstr "أجبر" + +#: optionsmodel.cpp:222 +#, kde-format +msgid "The window property will be always forced to the given value." +msgstr "ستفرض القيمة المعطاة لخاصية النافذة بشكل دائم" + +#: optionsmodel.cpp:224 +#, kde-format +msgid "Force Temporarily" +msgstr "أجبر بشكل مؤقت" + +#: optionsmodel.cpp:225 +#, kde-format +msgid "" +"The window property will be forced to the given value until it is hidden\n" +"(this action will be deleted after the window is hidden)." +msgstr "" +"ستفرض القيمة المعطاة لخاصية النافذة حتى تختفي\n" +"(هذا الإجراء سيحذف بعد أن تختفي النافذة)" + +#: package/contents/ui/FileDialogLoader.qml:14 +#, kde-format +msgid "Select File" +msgstr "اختر ملفًّا" + +#: package/contents/ui/FileDialogLoader.qml:26 +#, kde-format +msgid "KWin Rules (*.kwinrule)" +msgstr "قواعد كوين (‏*.kwinrule)" + +#: package/contents/ui/main.qml:59 +#, kde-format +msgid "No rules for specific windows are currently set" +msgstr "لا توجد قواعد للنوافذ المحددة حاليا" + +#: package/contents/ui/main.qml:60 +#, kde-kuit-format +msgctxt "@info" +msgid "Click the Add New... button below to add some" +msgstr "انقر على زر أضف أيضا... في الأسفل لتضيف بعضها" + +#: package/contents/ui/main.qml:68 +#, kde-format +msgid "Select the rules to export" +msgstr "اختر قواعد لتصدرها" + +#: package/contents/ui/main.qml:72 +#, kde-format +msgid "Unselect All" +msgstr "أزِل تحديد الكلّ" + +#: package/contents/ui/main.qml:72 +#, kde-format +msgid "Select All" +msgstr "حدّد الكلّ" + +#: package/contents/ui/main.qml:86 +#, kde-format +msgid "Save Rules" +msgstr "احفظ القواعد" + +#: package/contents/ui/main.qml:97 +#, kde-format +msgid "Add New..." +msgstr "أضف أيضاً ..." + +#: package/contents/ui/main.qml:108 +#, kde-format +msgid "Import..." +msgstr "استورد..." + +#: package/contents/ui/main.qml:116 +#, kde-format +msgid "Cancel Export" +msgstr "ألغ التصدير" + +#: package/contents/ui/main.qml:116 +#, kde-format +msgid "Export..." +msgstr "صدّر..." + +#: package/contents/ui/main.qml:206 +#, kde-format +msgid "Edit" +msgstr "حرّر" + +#: package/contents/ui/main.qml:215 +#, kde-format +msgid "Duplicate" +msgstr "كرّر" + +#: package/contents/ui/main.qml:224 +#, kde-format +msgid "Delete" +msgstr "احذف" + +#: package/contents/ui/main.qml:237 +#, kde-format +msgid "Import Rules" +msgstr "استورد قواعد" + +#: package/contents/ui/main.qml:249 +#, kde-format +msgid "Export Rules" +msgstr "صدر قواعد" + +#: package/contents/ui/OptionsComboBox.qml:35 +#, kde-format +msgid "None selected" +msgstr "غير المحدد" + +#: package/contents/ui/OptionsComboBox.qml:41 +#, kde-format +msgid "All selected" +msgstr "كل المحدد" + +#: package/contents/ui/OptionsComboBox.qml:43 +#, kde-format +msgid "%1 selected" +msgid_plural "%1 selected" +msgstr[0] "محدّد" +msgstr[1] "١ محدّد" +msgstr[2] "٢ محدّدين" +msgstr[3] "%1 محدّدات" +msgstr[4] "%1 محدّداً" +msgstr[5] "%1 محدّد" + +#: package/contents/ui/RulesEditor.qml:63 +#, kde-format +msgid "No window properties changed" +msgstr "لم يغير أي من خواص النافذة" + +#: package/contents/ui/RulesEditor.qml:64 +#, kde-kuit-format +msgctxt "@info" +msgid "" +"Click the Add Property... button below to add some " +"window properties that will be affected by the rule" +msgstr "" +"انقر على زر أضف خاصية... في الأسفل لتضيف بعضاً من " +"خصائص النافذة التي ستكون مشمولة بالقاعدة" + +#: package/contents/ui/RulesEditor.qml:85 +#, kde-format +msgid "Close" +msgstr "أغلق" + +#: package/contents/ui/RulesEditor.qml:85 +#, kde-format +msgid "Add Property..." +msgstr "أضف خاصية..." + +#: package/contents/ui/RulesEditor.qml:98 +#, kde-format +msgid "Detect Window Properties" +msgstr "اكتشف خاصيّات النّافذة" + +#: package/contents/ui/RulesEditor.qml:114 +#: package/contents/ui/RulesEditor.qml:121 +#, kde-format +msgid "Instantly" +msgstr "فوري" + +#: package/contents/ui/RulesEditor.qml:115 +#: package/contents/ui/RulesEditor.qml:126 +#, kde-format +msgid "After %1 second" +msgid_plural "After %1 seconds" +msgstr[0] "بعد %1 ثانية" +msgstr[1] "بعد ثانية" +msgstr[2] "بعد ثانيتين" +msgstr[3] "بعد %1 ثواني" +msgstr[4] "بعد %1 ثانية" +msgstr[5] "بعد %1 ثانية" + +#: package/contents/ui/RulesEditor.qml:147 +#, kde-format +msgid "Error" +msgstr "خطأ" + +#: package/contents/ui/RulesEditor.qml:162 +#, kde-format +msgid "Add property to the rule" +msgstr "أضف خاصية للقاعدة" + +#: package/contents/ui/RulesEditor.qml:260 +#: package/contents/ui/ValueEditor.qml:54 +#, kde-format +msgid "Yes" +msgstr "نعم" + +#: package/contents/ui/RulesEditor.qml:260 +#: package/contents/ui/ValueEditor.qml:60 +#, kde-format +msgid "No" +msgstr "لا" + +#: package/contents/ui/RulesEditor.qml:262 +#: package/contents/ui/ValueEditor.qml:171 +#: package/contents/ui/ValueEditor.qml:178 +#, kde-format +msgid "%1 %" +msgstr "%1 ٪" + +#: package/contents/ui/RulesEditor.qml:264 +#, kde-format +msgctxt "Coordinates (x, y)" +msgid "(%1, %2)" +msgstr "(%1، %2)" + +#: package/contents/ui/RulesEditor.qml:266 +#, kde-format +msgctxt "Size (width, height)" +msgid "(%1, %2)" +msgstr "(%1، %2)" + +#: package/contents/ui/ValueEditor.qml:206 +#, kde-format +msgctxt "(x, y) coordinates separator in size/position" +msgid "x" +msgstr "×" + +#: rulesmodel.cpp:218 +#, kde-format +msgid "Settings for %1" +msgstr "الإعدادات لِــ %1" + +#: rulesmodel.cpp:221 +#, kde-format +msgid "New window settings" +msgstr "إعدادات النافذة الجديدة" + +#: rulesmodel.cpp:237 +#, kde-format +msgid "" +"You have specified the window class as unimportant.\n" +"This means the settings will possibly apply to windows from all " +"applications. If you really want to create a generic setting, it is " +"recommended you at least limit the window types to avoid special window " +"types." +msgstr "" +"لقد حددت أن صنف النافذة غير مهم.\n" +"هذا يعني أنه من الممكن أن تطبق الإعدادات على نوافذ من جميع التطبيقات. إذا " +"كنت تريد فعلا إنشاء إعداد عام ، فإنه من الموصى أن تحدد أنواع النوافذ لمنع " +"أنواع النوافذ الخاصة." + +#: rulesmodel.cpp:244 +#, kde-format +msgid "" +"Some applications set their own geometry after starting, overriding your " +"initial settings for size and position. To enforce these settings, also " +"force the property \"%1\" to \"Yes\"." +msgstr "" +"تقوم بعض التطبيقات بتعيين الشكل الهندسي الخاص بها بعد البدء ، مما يؤدي إلى " +"تجاوز الإعدادات الأولية للحجم والموضع. لفرض هذه الإعدادات ، قم أيضًا بفرض " +"الخاصية \"‏%1\" على \"نعم\"." + +#: rulesmodel.cpp:359 +#, kde-format +msgid "Description" +msgstr "الوصف" + +#: rulesmodel.cpp:359 rulesmodel.cpp:367 rulesmodel.cpp:375 rulesmodel.cpp:382 +#: rulesmodel.cpp:388 rulesmodel.cpp:396 rulesmodel.cpp:401 rulesmodel.cpp:407 +#, kde-format +msgid "Window matching" +msgstr "مطابقة النافذة" + +#: rulesmodel.cpp:367 +#, kde-format +msgid "Window class (application)" +msgstr "صنف النافذة (التطبيق)" + +#: rulesmodel.cpp:375 +#, kde-format +msgid "Match whole window class" +msgstr "طابق كل صنف النافذة" + +#: rulesmodel.cpp:382 +#, kde-format +msgid "Whole window class" +msgstr "كل صنف النافذة" + +#: rulesmodel.cpp:388 +#, kde-format +msgid "Window types" +msgstr "أنواع النافذة" + +#: rulesmodel.cpp:396 +#, kde-format +msgid "Window role" +msgstr "دور النافذة" + +#: rulesmodel.cpp:401 +#, kde-format +msgid "Window title" +msgstr "عنوان النّافذة" + +#: rulesmodel.cpp:407 +#, kde-format +msgid "Machine (hostname)" +msgstr "الآلة (اسم المضيف)" + +#: rulesmodel.cpp:413 +#, kde-format +msgid "Position" +msgstr "الموقع" + +#: rulesmodel.cpp:413 rulesmodel.cpp:419 rulesmodel.cpp:425 rulesmodel.cpp:430 +#: rulesmodel.cpp:438 rulesmodel.cpp:444 rulesmodel.cpp:463 rulesmodel.cpp:479 +#: rulesmodel.cpp:484 rulesmodel.cpp:489 rulesmodel.cpp:494 rulesmodel.cpp:499 +#: rulesmodel.cpp:506 rulesmodel.cpp:516 rulesmodel.cpp:521 rulesmodel.cpp:526 +#, kde-format +msgid "Size & Position" +msgstr "الحجم والموضع" + +#: rulesmodel.cpp:419 +#, kde-format +msgid "Size" +msgstr "الحجم" + +#: rulesmodel.cpp:425 +#, kde-format +msgid "Maximized horizontally" +msgstr "مكبّرة أفقياً" + +#: rulesmodel.cpp:430 +#, kde-format +msgid "Maximized vertically" +msgstr "مكبّرة عمودياً" + +#: rulesmodel.cpp:438 +#, kde-format +msgid "Virtual Desktop" +msgstr "كلّ أسطح المكتب الافتراضية" + +#: rulesmodel.cpp:444 +#, kde-format +msgid "Virtual Desktops" +msgstr "أسطح المكتب الافتراضية" + +#: rulesmodel.cpp:463 +#, kde-format +msgid "Activities" +msgstr "الأنشطة" + +#: rulesmodel.cpp:479 +#, kde-format +msgid "Screen" +msgstr "الشّاشة" + +#: rulesmodel.cpp:484 +#, kde-format +msgid "Fullscreen" +msgstr "ملء الشّاشة" + +#: rulesmodel.cpp:489 +#, kde-format +msgid "Minimized" +msgstr "مصغرة" + +#: rulesmodel.cpp:494 +#, kde-format +msgid "Shaded" +msgstr "مظللة" + +#: rulesmodel.cpp:499 +#, kde-format +msgid "Initial placement" +msgstr "الموضع المبدئي" + +#: rulesmodel.cpp:506 +#, kde-format +msgid "Ignore requested geometry" +msgstr "تجاهل طلب الأبعاد" + +#: rulesmodel.cpp:508 +#, kde-format +msgid "" +"Windows can ask to appear in a certain position.\n" +"By default this overrides the placement strategy\n" +"what might be nasty if the client abuses the feature\n" +"to unconditionally popup in the middle of your screen." +msgstr "" +"يمكن أن تطلب النوافذ الظهور في موضع معين. \n" +"يؤدي هذا بشكل افتراضي إلى تجاوز استراتيجية الوضع \n" +"ما قد يكون سيئًا إذا أساء العميل استخدام الميزة \n" +"ليظهر النافذة دون قيد أو شرط في منتصف الشاشة." + +#: rulesmodel.cpp:516 +#, kde-format +msgid "Minimum Size" +msgstr "الحجم الأدنى:" + +#: rulesmodel.cpp:521 +#, kde-format +msgid "Maximum Size" +msgstr "الحجم الأقصى:" + +#: rulesmodel.cpp:526 +#, kde-format +msgid "Obey geometry restrictions" +msgstr "التزم بقيود الأبعاد" + +#: rulesmodel.cpp:528 +#, kde-format +msgid "" +"Eg. terminals or video players can ask to keep a certain aspect ratio\n" +"or only grow by values larger than one\n" +"(eg. by the dimensions of one character).\n" +"This may be pointless and the restriction prevents arbitrary dimensions\n" +"like your complete screen area." +msgstr "" +"على سبيل المثال، يمكن أن تطلب الأجهزة الطرفية أو مشغلات\n" +" الفيديو الاحتفاظ بنسبة عرض إلى ارتفاع معينة أو تكبر فقط\n" +" بقيم أكبر من واحد (على سبيل المثال بأبعاد محرف واحد). \n" +"قد يكون هذا بلا معنى ويؤدي إلى عدم إمكانية تحديد الأبعاد بشكل حر مثل مساحة " +"الشاشة الكاملة." + +#: rulesmodel.cpp:537 +#, kde-format +msgid "Keep above other windows" +msgstr "أبقها أعلى النوافذ الأخرى" + +#: rulesmodel.cpp:537 rulesmodel.cpp:542 rulesmodel.cpp:547 rulesmodel.cpp:553 +#: rulesmodel.cpp:559 rulesmodel.cpp:565 +#, kde-format +msgid "Arrangement & Access" +msgstr "الترتيب والوصول" + +#: rulesmodel.cpp:542 +#, kde-format +msgid "Keep below other windows" +msgstr "أبقها أسفل النوافذ الأخرى" + +#: rulesmodel.cpp:547 +#, kde-format +msgid "Skip taskbar" +msgstr "تخطى شريط المهام" + +#: rulesmodel.cpp:549 +#, kde-format +msgid "Window shall (not) appear in the taskbar." +msgstr "لا/يجب أن تظهر النافذة في شريط المهام." + +#: rulesmodel.cpp:553 +#, kde-format +msgid "Skip pager" +msgstr "تخطّى المنادي" + +#: rulesmodel.cpp:555 +#, kde-format +msgid "Window shall (not) appear in the manager for virtual desktops" +msgstr "لا/يجب أن تظهر النافذة في مدير أسطح المكتب الافتراضية" + +#: rulesmodel.cpp:559 +#, kde-format +msgid "Skip switcher" +msgstr "تخطّى مبدل المهام" + +#: rulesmodel.cpp:561 +#, kde-format +msgid "Window shall (not) appear in the Alt+Tab list" +msgstr "لا/يجب أن تظهر النافذة قائمة مبدل المهام Alt+Tab" + +#: rulesmodel.cpp:565 +#, kde-format +msgid "Shortcut" +msgstr "الاختصار" + +#: rulesmodel.cpp:571 +#, kde-format +msgid "No titlebar and frame" +msgstr "بدون شريط العنوان والإطار" + +#: rulesmodel.cpp:571 rulesmodel.cpp:576 rulesmodel.cpp:582 rulesmodel.cpp:587 +#: rulesmodel.cpp:592 rulesmodel.cpp:603 rulesmodel.cpp:614 rulesmodel.cpp:622 +#: rulesmodel.cpp:635 rulesmodel.cpp:640 rulesmodel.cpp:646 rulesmodel.cpp:651 +#, kde-format +msgid "Appearance & Fixes" +msgstr "المظهر والإصلاحات" + +#: rulesmodel.cpp:576 +#, kde-format +msgid "Titlebar color scheme" +msgstr "مخطط لون شريط العنوان" + +#: rulesmodel.cpp:582 +#, kde-format +msgid "Active opacity" +msgstr "التعتيم نشط" + +#: rulesmodel.cpp:587 +#, kde-format +msgid "Inactive opacity" +msgstr "التعتيم خامل" + +#: rulesmodel.cpp:592 +#, kde-format +msgid "Focus stealing prevention" +msgstr "منع سرقة التركيز" + +#: rulesmodel.cpp:594 +#, kde-format +msgid "" +"KWin tries to prevent windows from taking the focus\n" +"(\"activate\") while you're working in another window,\n" +"but this may sometimes fail or superact.\n" +"\"None\" will unconditionally allow this window to get the focus while\n" +"\"Extreme\" will completely prevent it from taking the focus." +msgstr "" +"يحاول كوين منع الإطارات من الاستيلاء على التركيز \n" +"(\"تنشيط\") أثناء عملك في نافذة أخرى ، \n" +"لكن هذا قد يفشل أحيانًا أو يتم تجاوزه. \n" +"\"بلا\" سيسمح لهذه النافذة دون قيد أو شرط بالحصول على التركيز \n" +"بينما \"القصوى\" ستمنعه تمامًا من الاستيلاء على التركيز." + +#: rulesmodel.cpp:603 +#, kde-format +msgid "Focus protection" +msgstr "حماية التركيز" + +#: rulesmodel.cpp:605 +#, kde-format +msgid "" +"This controls the focus protection of the currently active window.\n" +"None will always give the focus away,\n" +"Extreme will keep it.\n" +"Otherwise it's interleaved with the stealing prevention\n" +"assigned to the window that wants the focus." +msgstr "" +"يتحكم هذا في حماية التركيز للنافذة النشطة حاليا. \n" +"ستقوم \"بلا\" بإعطاء التركيز دائمًا، \n" +"و \"القصوى\" ستحتفظ به. \n" +"وإلا فإنه يتداخل مع منع السرقة \n" +"المخصصة للنافذة التي تريد التركيز." + +#: rulesmodel.cpp:614 +#, kde-format +msgid "Accept focus" +msgstr "أقبل التركيز" + +#: rulesmodel.cpp:616 +#, kde-format +msgid "" +"Windows may prevent to get the focus (activate) when being clicked.\n" +"On the other hand you might wish to prevent a window\n" +"from getting focused on a mouse click." +msgstr "" +"قد تمتنع النوافذ من الحصول على التركيز (التنشيط) عند النقر فوقها. \n" +"من ناحية أخرى ، قد ترغب في منع نافذة \n" +"من التركيز عند النقر بالفأرة." + +#: rulesmodel.cpp:622 +#, kde-format +msgid "Ignore global shortcuts" +msgstr "تجاهل الاختصارات العامة" + +#: rulesmodel.cpp:624 +#, kde-format +msgid "" +"When used, a window will receive\n" +"all keyboard inputs while it is active, including Alt+Tab etc.\n" +"This is especially interesting for emulators or virtual machines.\n" +"\n" +"Be warned:\n" +"you won't be able to Alt+Tab out of the window\n" +"nor use any other global shortcut (such as Alt+F2 to show KRunner)\n" +"while it's active!" +msgstr "" +"عند الاستخدام ستتلقى نافذة \n" +"كافة إدخالات لوحة المفاتيح أثناء نشاطها ، بما في ذلك Alt + Tab إلخ. \n" +"قد يكون هذا مهم بشكل خاص للمحاكيات أو الآلات التخيلية. \n" +"\n" +"كن حذرًا: \n" +"لن تتمكن من استخدام Alt + Tab خارج النافذة \n" +"أو استخدام أي اختصار عام آخر (مثل Alt + F2 لإظهار مشغلك) \n" +"عندما تكون النافذة نشطة!" + +#: rulesmodel.cpp:635 +#, kde-format +msgid "Closeable" +msgstr "قابلة للإغلاق" + +#: rulesmodel.cpp:640 +#, kde-format +msgid "Set window type" +msgstr "حدد نوع النافذة" + +#: rulesmodel.cpp:646 +#, kde-format +msgid "Desktop file name" +msgstr "اسم ملف سطح المكتب" + +#: rulesmodel.cpp:651 +#, kde-format +msgid "Block compositing" +msgstr "التركيب الكتلي" + +#: rulesmodel.cpp:727 +#, kde-format +msgid "All Window Types" +msgstr "جميع أنواع النافذة" + +#: rulesmodel.cpp:728 +#, kde-format +msgid "Normal Window" +msgstr "نافذة عادية" + +#: rulesmodel.cpp:729 +#, kde-format +msgid "Dialog Window" +msgstr "نافذة الحوار" + +#: rulesmodel.cpp:730 +#, kde-format +msgid "Utility Window" +msgstr "أداة النافذة" + +#: rulesmodel.cpp:731 +#, kde-format +msgid "Dock (panel)" +msgstr "إرساء (اللوحة)" + +#: rulesmodel.cpp:732 +#, kde-format +msgid "Toolbar" +msgstr "شريط الأدوات" + +#: rulesmodel.cpp:733 +#, kde-format +msgid "Torn-Off Menu" +msgstr "قائمة ممزقة" + +#: rulesmodel.cpp:734 +#, kde-format +msgid "Splash Screen" +msgstr "شاشة البداية" + +#: rulesmodel.cpp:735 +#, kde-format +msgid "Desktop" +msgstr "سطح المكتب" + +#. i18n("Unmanaged Window")}, deprecated +#: rulesmodel.cpp:737 +#, kde-format +msgid "Standalone Menubar" +msgstr "شريط أدوات مستقل" + +#: rulesmodel.cpp:738 +#, kde-format +msgid "On Screen Display" +msgstr "على شاشة العرض" + +#: rulesmodel.cpp:748 +#, kde-format +msgid "All Desktops" +msgstr "كلّ أسطح المكتب" + +#: rulesmodel.cpp:750 +#, kde-format +msgctxt "@info:tooltip in the virtual desktop list" +msgid "Make the window available on all desktops" +msgstr "اجعل النافذة متوفرة على كلّ أسطح المكتب" + +#: rulesmodel.cpp:769 +#, kde-format +msgid "All Activities" +msgstr "كلّ الأنشطة" + +#: rulesmodel.cpp:771 +#, kde-format +msgctxt "@info:tooltip in the activity list" +msgid "Make the window available on all activities" +msgstr "اجعل النافذة متوفرة على كلّ الأنشطة" + +#: rulesmodel.cpp:792 +#, kde-format +msgid "Default" +msgstr "افتراضي" + +#: rulesmodel.cpp:793 +#, kde-format +msgid "No Placement" +msgstr "موضع غير محدد" + +#: rulesmodel.cpp:794 +#, kde-format +msgid "Minimal Overlapping" +msgstr "الحد الأدنى من التراكب" + +#: rulesmodel.cpp:795 +#, kde-format +msgid "Maximized" +msgstr "مكبرة" + +#: rulesmodel.cpp:796 +#, kde-format +msgid "Cascaded" +msgstr "متتالي" + +#: rulesmodel.cpp:797 +#, kde-format +msgid "Centered" +msgstr "موسّطة" + +#: rulesmodel.cpp:798 +#, kde-format +msgid "Random" +msgstr "عشوائي" + +#: rulesmodel.cpp:799 +#, kde-format +msgid "In Top-Left Corner" +msgstr "في زاوِيَة أعلى اليسار" + +#: rulesmodel.cpp:800 +#, kde-format +msgid "Under Mouse" +msgstr "تحت الفأرة" + +#: rulesmodel.cpp:801 +#, kde-format +msgid "On Main Window" +msgstr "على النافذة الرئيسية" + +#: rulesmodel.cpp:808 +#, kde-format +msgid "None" +msgstr "بلا" + +#: rulesmodel.cpp:809 +#, kde-format +msgid "Low" +msgstr "منخفض" + +#: rulesmodel.cpp:810 +#, kde-format +msgid "Normal" +msgstr "عادي" + +#: rulesmodel.cpp:811 +#, kde-format +msgid "High" +msgstr "عالي" + +#: rulesmodel.cpp:812 +#, kde-format +msgid "Extreme" +msgstr "القصوى" + +#: rulesmodel.cpp:855 +#, kde-format +msgid "Could not detect window properties. The window is not managed by KWin." +msgstr "لا يمكن اكتشاف خواص النافذة. لا تدار النافذة بواسطة كوين." \ No newline at end of file diff --git a/po/ar/kcm_kwintabbox.po b/po/ar/kcm_kwintabbox.po new file mode 100644 index 0000000..ef927b5 --- /dev/null +++ b/po/ar/kcm_kwintabbox.po @@ -0,0 +1,236 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# Safa Alfulaij , 2015. +# Zayed Al-Saidi , 2021, 2022. +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2022-06-15 00:48+0000\n" +"PO-Revision-Date: 2022-06-18 19:25+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: Arabic \n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" +"X-Generator: Lokalize 21.07.70\n" + +#: kwintabboxconfigform.cpp:77 +#, kde-format +msgid "KWin" +msgstr "ك.وين" + +#: layoutpreview.cpp:133 +#, kde-format +msgid "Show Desktop" +msgstr "أظهر سطح المكتب" + +#: layoutpreview.cpp:163 +#, kde-format +msgctxt "An example Desktop Name" +msgid "Desktop 1" +msgstr "سطح المكتب 1" + +#: main.cpp:58 +#, kde-format +msgid "Main" +msgstr "الرّئيسيّ" + +#: main.cpp:59 +#, kde-format +msgid "Alternative" +msgstr "البديل" + +#: main.cpp:61 +#, kde-format +msgid "Get New Task Switchers..." +msgstr "احصل على مُبدِّل مهام جديد" + +#: main.cpp:75 +#, kde-format +msgid "" +"Focus policy settings limit the functionality of navigating through windows." +msgstr "تحد إعدادات سياسة التركيز من وظيفة التنقل عبر النوافذ." + +#. i18n: ectx: property (title), widget (QGroupBox, groupBox_3) +#: main.ui:32 +#, kde-format +msgid "Content" +msgstr "المحتوى" + +#. i18n: ectx: property (text), widget (QCheckBox, showDesktop) +#: main.ui:41 +#, kde-format +msgid "Include \"Show Desktop\" icon" +msgstr "ضمّن أيقونة \"أظهر سطح المكتب\"" + +#. i18n: ectx: property (text), item, widget (QComboBox, switchingModeCombo) +#: main.ui:55 +#, kde-format +msgid "Recently used" +msgstr "المستخدَم حديثًا" + +#. i18n: ectx: property (text), item, widget (QComboBox, switchingModeCombo) +#: main.ui:60 +#, kde-format +msgid "Stacking order" +msgstr "ترتيب التّكديس" + +#. i18n: ectx: property (text), widget (QCheckBox, oneAppWindow) +#: main.ui:68 +#, kde-format +msgid "Only one window per application" +msgstr "نافذة لكلّ تطبيق" + +#. i18n: ectx: property (text), widget (QCheckBox, orderMinimized) +#: main.ui:78 +#, kde-format +msgid "Order minimized windows after unminimized windows" +msgstr "رتب النوافذ المصغرة بعد النوافذ غير المصغرة" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: main.ui:88 +#, kde-format +msgid "Sort order:" +msgstr "ترتيب الفرز:" + +#. i18n: ectx: property (title), widget (QGroupBox, groupBox) +#: main.ui:114 +#, kde-format +msgid "Filter windows by" +msgstr "رشّح النّوافذ حسب" + +#. i18n: ectx: property (text), widget (QCheckBox, filterDesktops) +#: main.ui:123 +#, kde-format +msgid "Virtual desktops" +msgstr "أسطح المكتب الوهميّة" + +#. i18n: ectx: property (text), widget (QRadioButton, currentDesktop) +#: main.ui:167 +#, kde-format +msgid "Current desktop" +msgstr "سطح المكتب الحاليّ" + +#. i18n: ectx: property (text), widget (QRadioButton, otherDesktops) +#: main.ui:174 +#, kde-format +msgid "All other desktops" +msgstr "كلّ أسطح المكتب الأخرى" + +#. i18n: ectx: property (text), widget (QCheckBox, filterActivities) +#: main.ui:184 +#, kde-format +msgid "Activities" +msgstr "الأنشطة" + +#. i18n: ectx: property (text), widget (QRadioButton, currentActivity) +#: main.ui:228 +#, kde-format +msgid "Current activity" +msgstr "النّشاط الحاليّ" + +#. i18n: ectx: property (text), widget (QRadioButton, otherActivities) +#: main.ui:235 +#, kde-format +msgid "All other activities" +msgstr "كلّ الأنشطة الأخرى" + +#. i18n: ectx: property (text), widget (QCheckBox, filterScreens) +#: main.ui:245 +#, kde-format +msgid "Screens" +msgstr "الشّاشات" + +#. i18n: ectx: property (text), widget (QRadioButton, currentScreen) +#: main.ui:289 +#, kde-format +msgid "Current screen" +msgstr "الشّاشة الحاليّة" + +#. i18n: ectx: property (text), widget (QRadioButton, otherScreens) +#: main.ui:296 +#, kde-format +msgid "All other screens" +msgstr "كلّ الشّاشات الأخرى" + +#. i18n: ectx: property (text), widget (QCheckBox, filterMinimization) +#: main.ui:306 +#, kde-format +msgid "Minimization" +msgstr "التّصغير" + +#. i18n: ectx: property (text), widget (QRadioButton, visibleWindows) +#: main.ui:350 +#, kde-format +msgid "Visible windows" +msgstr "النوافذ المرئية" + +#. i18n: ectx: property (text), widget (QRadioButton, hiddenWindows) +#: main.ui:357 +#, kde-format +msgid "Hidden windows" +msgstr "النوافذ المخفية" + +#. i18n: ectx: property (title), widget (QGroupBox, groupBox_4) +#: main.ui:396 +#, kde-format +msgid "Shortcuts" +msgstr "الاختصارات" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: main.ui:405 main.ui:448 +#, kde-format +msgid "Forward" +msgstr "تقدّم" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: main.ui:428 +#, kde-format +msgid "All windows" +msgstr "كلّ النّوافذ" + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: main.ui:438 main.ui:458 +#, kde-format +msgid "Reverse" +msgstr "اعكس" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: main.ui:480 +#, kde-format +msgid "Current application" +msgstr "التّطبيق الحاليّ" + +#. i18n: ectx: property (title), widget (QGroupBox, groupBox_2) +#: main.ui:499 +#, kde-format +msgid "Visualization" +msgstr "مرئي" + +#. i18n: ectx: property (toolTip), widget (QComboBox, effectCombo) +#: main.ui:529 +#, kde-format +msgid "The effect to replace the list window when desktop effects are active." +msgstr "تأثير لاستبدال قائمة النوافذ عندما تكون تأثيرات سطح المكتب نشطة." + +#. i18n: ectx: property (toolTip), widget (QCheckBox, kcfg_HighlightWindows) +#: main.ui:559 +#, kde-format +msgid "" +"The currently selected window will be highlighted by fading out all other " +"windows. This option requires desktop effects to be active." +msgstr "" +"تُميز النافذة المحددة حاليًا عن طريق تلاشي جميع النوافذ الأخرى. يتطلب هذا " +"الخيار تفعيل تأثيرات سطح المكتب. " + +#. i18n: ectx: property (text), widget (QCheckBox, kcfg_HighlightWindows) +#: main.ui:562 +#, kde-format +msgid "Show selected window" +msgstr "اعرض النافذة المحددة" \ No newline at end of file diff --git a/po/ar/kcm_virtualkeyboard.po b/po/ar/kcm_virtualkeyboard.po new file mode 100644 index 0000000..4cf66e0 --- /dev/null +++ b/po/ar/kcm_virtualkeyboard.po @@ -0,0 +1,54 @@ +# Copyright (C) YEAR This file is copyright: +# This file is distributed under the same license as the kwin package. +# +# Zayed Al-Saidi , 2021. +msgid "" +msgstr "" +"Project-Id-Version: kwin\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2021-04-27 00:19+0000\n" +"PO-Revision-Date: 2021-06-28 21:56+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" +"X-Generator: Lokalize 21.07.70\n" + +#, kde-format +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "زايد السعيدي" + +#, kde-format +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "zayed.alsaidi@gmail.com" + +#: kcmvirtualkeyboard.cpp:31 +#, kde-format +msgid "Virtual Keyboard" +msgstr "لوحة المفاتيح الوهميّة" + +#: kcmvirtualkeyboard.cpp:33 +#, kde-format +msgid "Choose Virtual Keyboard" +msgstr "اختر لوحة المفاتيح الوهمية" + +#: kcmvirtualkeyboard.cpp:70 +#, kde-format +msgid "None" +msgstr "بلا" + +#: kcmvirtualkeyboard.cpp:74 +#, kde-format +msgid "Do not use any virtual keyboard" +msgstr "لا تستعمل أي لوحة مفاتيح وهمية" + +#: package/contents/ui/main.qml:16 +#, kde-format +msgid "This module lets you choose the virtual keyboard to use." +msgstr "هذه الوحدة تجعلك تختار لوحة مفاتيح وهمية." \ No newline at end of file diff --git a/po/ar/kcmkwincommon.po b/po/ar/kcmkwincommon.po new file mode 100644 index 0000000..e3a0bb2 --- /dev/null +++ b/po/ar/kcmkwincommon.po @@ -0,0 +1,82 @@ +# Copyright (C) YEAR This file is copyright: +# This file is distributed under the same license as the kwin package. +# +# Zayed Al-Saidi , 2021. +msgid "" +msgstr "" +"Project-Id-Version: kwin\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2022-03-26 00:45+0000\n" +"PO-Revision-Date: 2021-06-29 12:32+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" +"X-Generator: Lokalize 21.07.70\n" + +#, kde-format +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "زايد السعيدي" + +#, kde-format +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "zayed.alsaidi@gmail.com" + +#: effectsmodel.cpp:52 +#, kde-format +msgctxt "Category of Desktop Effects, used as section header" +msgid "Accessibility" +msgstr "الإتاحة" + +#: effectsmodel.cpp:53 +#, kde-format +msgctxt "Category of Desktop Effects, used as section header" +msgid "Appearance" +msgstr "المظهر" + +#: effectsmodel.cpp:54 +#, kde-format +msgctxt "Category of Desktop Effects, used as section header" +msgid "Focus" +msgstr "التركيز" + +#: effectsmodel.cpp:55 +#, kde-format +msgctxt "Category of Desktop Effects, used as section header" +msgid "Show Desktop Animation" +msgstr "أظهر حركات سطح المكتب" + +#: effectsmodel.cpp:56 +#, kde-format +msgctxt "Category of Desktop Effects, used as section header" +msgid "Tools" +msgstr "الأدوات" + +#: effectsmodel.cpp:57 +#, kde-format +msgctxt "Category of Desktop Effects, used as section header" +msgid "Virtual Desktop Switching Animation" +msgstr "الرسوم المتحركة لتبديل سطح المكتب الافتراضية" + +#: effectsmodel.cpp:58 +#, kde-format +msgctxt "Category of Desktop Effects, used as section header" +msgid "Window Management" +msgstr "إدارة النّوافذ" + +#: effectsmodel.cpp:59 +#, kde-format +msgctxt "Category of Desktop Effects, used as section header" +msgid "Window Open/Close Animation" +msgstr "حركات فتح/غلق النوافذ" + +#: effectsmodel.cpp:243 +#, kde-format +msgid "KWin development team" +msgstr "فريق تطوير كوين" \ No newline at end of file diff --git a/po/ar/kcmkwincompositing.po b/po/ar/kcmkwincompositing.po new file mode 100644 index 0000000..47b91e4 --- /dev/null +++ b/po/ar/kcmkwincompositing.po @@ -0,0 +1,240 @@ +# translation of kcmkwincompositing.po to Arabic +# translation of kcmkwincompositing.po to +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# +# Youssef Chahibi , 2007. +# Abdulaziz AlSharif , 2007. +# zayed , 2008. +# Zayed Al-Saidi , 2010, 2021. +# Abdalrahim G. Fakhouri , 2014. +msgid "" +msgstr "" +"Project-Id-Version: kcmkwincompositing\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2022-07-02 00:49+0000\n" +"PO-Revision-Date: 2021-12-18 21:25+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Lokalize 21.07.70\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" + +#. i18n: ectx: property (text), widget (KMessageWidget, glCrashedWarning) +#: compositing.ui:31 +#, kde-format +msgid "" +"OpenGL compositing (the default) has crashed KWin in the past.\n" +"This was most likely due to a driver bug.\n" +"If you think that you have meanwhile upgraded to a stable driver,\n" +"you can reset this protection but be aware that this might result in an " +"immediate crash!" +msgstr "" +"أدى التركيب في OpenGL (الافتراضي) إلى تعطل كوين في الماضي. \n" +"كان هذا على الأرجح بسبب خطأ في المعرف. \n" +"إذا كنت تعتقد أنك قمت في هذه الأثناء بالترقية إلى تعريف ثابت مستقر، \n" +"فيمكنك إعادة تعيين هذه الحماية ولكن يجب أن تدرك أن هذا قد يؤدي إلى انهيار " +"فوري!" + +#. i18n: ectx: property (text), widget (KMessageWidget, scaleWarning) +#: compositing.ui:44 +#, kde-format +msgid "" +"Scale method \"Accurate\" is not supported by all hardware and can cause " +"performance regressions and rendering artifacts." +msgstr "" +"طريقة التحجيم \"الدقيق\" غير مدعومة من قبل جميع الأجهزة ويمكن أن تتسبب في " +"تراجع الأداء و حدوث عيوب في العرض." + +#. i18n: ectx: property (text), widget (KMessageWidget, windowThumbnailWarning) +#: compositing.ui:67 +#, kde-format +msgid "" +"Keeping the window thumbnail always interferes with the minimized state of " +"windows. This can result in windows not suspending their work when minimized." +msgstr "" +"الحفاظ على مصغر النافذة يتعارض دائمًا مع الحالة التصغير للنوافذ. يمكن أن يؤدي " +"هذا إلى عدم تعليق النوافذ لعملهم عند تصغيره." + +#. i18n: ectx: property (text), widget (QLabel, compositingLabel) +#: compositing.ui:79 +#, kde-format +msgid "Compositing:" +msgstr "التركيب:" + +#. i18n: ectx: property (text), widget (QCheckBox, kcfg_Enabled) +#: compositing.ui:86 +#, kde-format +msgid "Enable on startup" +msgstr "مكّن عند بدء التشغيل" + +#. i18n: ectx: property (toolTip), widget (QCheckBox, kcfg_WindowsBlockCompositing) +#: compositing.ui:95 +#, kde-format +msgid "" +"Applications can set a hint to block compositing when the window is open.\n" +" This brings performance improvements for e.g. games.\n" +" The setting can be overruled by window-specific rules." +msgstr "" +"يمكن للتطبيقات تعيين تلميح لمنع التركيب عندما تكون النافذة مفتوحة. \n" +" يؤدي هذا إلى تحسينات في الأداء على سبيل المثال للألعاب. \n" +" يمكن إلغاء الإعداد من خلال القواعد الخاصة بالنافذة." + +#. i18n: ectx: property (text), widget (QCheckBox, kcfg_WindowsBlockCompositing) +#: compositing.ui:98 +#, kde-format +msgid "Allow applications to block compositing" +msgstr "اسمح للتطبيقات بمنع التركيب" + +#. i18n: ectx: property (text), widget (QLabel, animationSpeedLabel) +#: compositing.ui:105 +#, kde-format +msgid "Animation speed:" +msgstr "سرعة التحريك:" + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#: compositing.ui:142 +#, kde-format +msgid "Very slow" +msgstr "بطيء جدا" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: compositing.ui:162 +#, kde-format +msgid "Instant" +msgstr "لحظي" + +#. i18n: ectx: property (text), widget (QLabel, scaleMethodLabel) +#: compositing.ui:174 +#, kde-format +msgid "Scale method:" +msgstr "طريقة التحجيم:" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_glTextureFilter) +#: compositing.ui:184 +#, kde-format +msgid "Smooth" +msgstr "سَلِس" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_glTextureFilter) +#: compositing.ui:189 +#, kde-format +msgid "Accurate" +msgstr "دقيق" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: compositing.ui:199 +#, kde-format +msgid "Tearing prevention (\"vsync\"):" +msgstr "منع التمزيق (المزامنة العمودية vsync):" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_glPreferBufferSwap) +#: compositing.ui:207 +#, kde-format +msgid "Automatic" +msgstr "آليّ" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_glPreferBufferSwap) +#: compositing.ui:212 +#, kde-format +msgid "Only when cheap" +msgstr "فقط عندما يكون سهلا" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_glPreferBufferSwap) +#: compositing.ui:217 +#, kde-format +msgid "Full screen repaints" +msgstr "إعادة رسم كامل الشاشة" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_glPreferBufferSwap) +#: compositing.ui:222 +#, kde-format +msgid "Re-use screen content" +msgstr "إعادة استخدام محتوى الشاشة" + +#. i18n: ectx: property (text), widget (QLabel, label_HiddenPreviews) +#: compositing.ui:230 +#, kde-format +msgid "Keep window thumbnails:" +msgstr "حافظ على مصغرات النافذة:" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_HiddenPreviews) +#: compositing.ui:238 +#, kde-format +msgid "Never" +msgstr "البتة" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_HiddenPreviews) +#: compositing.ui:243 +#, kde-format +msgid "Only for Shown Windows" +msgstr "فقط للنوافذ المعروضة" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_HiddenPreviews) +#: compositing.ui:248 +#, kde-format +msgid "Always" +msgstr "دائما" + +#. i18n: ectx: property (text), widget (QLabel, latencyLabel) +#: compositing.ui:256 +#, kde-format +msgid "Latency:" +msgstr "وقت الاستجابة" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_LatencyPolicy) +#: compositing.ui:264 +#, kde-format +msgid "Force lowest latency (may cause dropped frames)" +msgstr "أجبر وقت استجابة أقل (يمكن أن يسقط إطارات)" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_LatencyPolicy) +#: compositing.ui:269 +#, kde-format +msgid "Prefer lower latency" +msgstr "فضّل وقت للاستجابة أقل" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_LatencyPolicy) +#: compositing.ui:274 +#, kde-format +msgid "Balance of latency and smoothness" +msgstr "توازن بين وقت الاستجابة و السلاسة" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_LatencyPolicy) +#: compositing.ui:279 +#, kde-format +msgid "Prefer smoother animations" +msgstr "فضل التحريك السلس" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_LatencyPolicy) +#: compositing.ui:284 +#, kde-format +msgid "Force smoothest animations" +msgstr "فرض التحريك الأكثر سلاسة" + +#: main.cpp:79 +#, kde-format +msgid "Re-enable OpenGL detection" +msgstr "أعد تفعيل اكتشاف OpenGL" + +#: main.cpp:135 +#, kde-format +msgid "" +"\"Only when cheap\" only prevents tearing for full screen changes like a " +"video." +msgstr "\"فقط عندما يكون سهلا\" يمنع تمزيق تغييرات ملء الشاشة مثل الفيديو." + +#: main.cpp:139 +#, kde-format +msgid "\"Full screen repaints\" can cause performance problems." +msgstr "\"إعادة رسم كامل الشاشة\" يمكن أن يسبب مشاكل في الأداء." + +#: main.cpp:143 +#, kde-format +msgid "" +"\"Re-use screen content\" causes severe performance problems on MESA drivers." +msgstr "\"إعادة استخدام محتوى الشاشة\" يسبب مشاكل أداء فادحة على معرفات MESA." \ No newline at end of file diff --git a/po/ar/kcmkwinscreenedges.po b/po/ar/kcmkwinscreenedges.po new file mode 100644 index 0000000..54cfc79 --- /dev/null +++ b/po/ar/kcmkwinscreenedges.po @@ -0,0 +1,234 @@ +# Copyright (C) YEAR This_file_is_part_of_KDE +# This file is distributed under the same license as the PACKAGE package. +# Zayed Al-Saidi , 2009, 2021, 2022. +# Safa Alfulaij , ٢٠١٦. +msgid "" +msgstr "" +"Project-Id-Version: kcmkwinscreenedges\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2022-07-15 00:48+0000\n" +"PO-Revision-Date: 2022-08-08 21:54+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: Arabic \n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" +"X-Generator: Lokalize 21.07.70\n" + +#, kde-format +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "صفا الفليج" + +#, kde-format +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "safa1996alfulaij@gmail.com" + +#: main.cpp:130 touch.cpp:124 +#, kde-format +msgid "No Action" +msgstr "لا إجراء" + +#: main.cpp:131 touch.cpp:125 +#, kde-format +msgid "Peek at Desktop" +msgstr "نظرة على سطح المكتب" + +#: main.cpp:132 touch.cpp:126 +#, kde-format +msgid "Lock Screen" +msgstr "اقفل الشّاشة" + +#: main.cpp:133 touch.cpp:127 +#, kde-format +msgid "Show KRunner" +msgstr "أظهر مشغل.ك" + +#: main.cpp:134 touch.cpp:128 +#, kde-format +msgid "Activity Manager" +msgstr "مدير الأنشطة" + +#: main.cpp:135 touch.cpp:129 +#, kde-format +msgid "Application Launcher" +msgstr "مُطلق التّطبيقات" + +#: main.cpp:139 touch.cpp:133 +#, kde-format +msgid "Present Windows" +msgstr "النوافذ الحاضرة" + +#: main.cpp:140 touch.cpp:134 +#, kde-format +msgid "%1 - All Desktops" +msgstr "%1 - كلّ أسطح المكتب" + +#: main.cpp:141 touch.cpp:135 +#, kde-format +msgid "%1 - Current Desktop" +msgstr "%1 - سطح المكتب الحاليّ" + +#: main.cpp:142 touch.cpp:136 +#, kde-format +msgid "%1 - Current Application" +msgstr "%1 - التّطبيق الحاليّ" + +#: main.cpp:144 touch.cpp:138 +#, kde-format +msgid "Toggle window switching" +msgstr "بدّل تنقل النوافذ" + +#: main.cpp:145 touch.cpp:139 +#, kde-format +msgid "Toggle alternative window switching" +msgstr "بدّل تنقل النوافذ البديل" + +#. i18n: ectx: property (text), widget (QLabel, infoLabel) +#: main.ui:23 +#, kde-format +msgid "" +"You can trigger an action by pushing the mouse cursor against the " +"corresponding screen edge or corner." +msgstr "" +"يمكنك تشغيل إجراء عن طريق دفع مؤشر الفأرة لحافة الشاشة أو الزاوية الحالية." + +#. i18n: ectx: property (text), widget (QLabel, quickMaximizeLabel) +#: main.ui:67 +#, kde-format +msgid "&Maximize:" +msgstr "&كبّر:" + +#. i18n: ectx: property (text), widget (QCheckBox, kcfg_ElectricBorderMaximize) +#: main.ui:77 +#, kde-format +msgid "Windows dragged to top edge" +msgstr "النوافذ المسحوبة إلى الحافة العليا" + +#. i18n: ectx: property (text), widget (QLabel, quickTileLabel) +#: main.ui:84 +#, kde-format +msgid "&Tile:" +msgstr "&صفّ:" + +#. i18n: ectx: property (text), widget (QCheckBox, kcfg_ElectricBorderTiling) +#: main.ui:94 +#, kde-format +msgid "Windows dragged to left or right edge" +msgstr "النوافذ المسحوبة إلى الحافة اليمين أو اليسار" + +#. i18n: ectx: property (text), widget (QLabel, label) +#: main.ui:101 +#, kde-format +msgid "Behavior:" +msgstr "السّلوك:" + +#. i18n: ectx: property (text), widget (QCheckBox, remainActiveOnFullscreen) +#: main.ui:108 +#, kde-format +msgid "Remain active when windows are fullscreen" +msgstr "ابق نشط عندما تكون النوافذ ملء الشاشة" + +#. i18n: ectx: property (text), widget (QLabel, electricBorderCornerRatioLabel) +#: main.ui:115 +#, kde-format +msgid "Trigger &quarter tiling in:" +msgstr "أطلق &التبليط الربعي في:" + +#. i18n: ectx: property (suffix), widget (QSpinBox, electricBorderCornerRatioSpin) +#: main.ui:130 +#, no-c-format, kde-format +msgid "%" +msgstr "٪" + +#. i18n: ectx: property (prefix), widget (QSpinBox, electricBorderCornerRatioSpin) +#: main.ui:133 +#, kde-format +msgid "Outer " +msgstr "الخارجي" + +#. i18n: ectx: property (text), widget (QLabel, label_1) +#: main.ui:149 +#, kde-format +msgid "of the screen" +msgstr "من الشّاشة" + +#. i18n: ectx: property (toolTip), widget (QLabel, desktopSwitchLabel) +#: main.ui:174 +#, kde-format +msgid "" +"Change desktop when the mouse cursor is pushed against the edge of the screen" +msgstr "غيّر سطح المكتب عند الدّفع بمؤشّر الفأرة إلى حافّة الشّاشة" + +#. i18n: ectx: property (text), widget (QLabel, desktopSwitchLabel) +#: main.ui:177 +#, kde-format +msgid "&Switch desktop on edge:" +msgstr "&بدّل سطح المكتب عند الحافّة:" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_ElectricBorders) +#: main.ui:188 +#, kde-format +msgctxt "Switch desktop on edge" +msgid "Disabled" +msgstr "معطّل" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_ElectricBorders) +#: main.ui:193 +#, kde-format +msgid "Only When Moving Windows" +msgstr "عند تحريك النّوافذ فقط" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_ElectricBorders) +#: main.ui:198 +#, kde-format +msgid "Always Enabled" +msgstr "مفعّل دائمًا" + +#. i18n: ectx: property (toolTip), widget (QLabel, activationDelayLabel) +#: main.ui:206 +#, kde-format +msgid "" +"Amount of time required for the mouse cursor to be pushed against the edge " +"of the screen before the action is triggered" +msgstr "" +"كمّ الوقت اللازم قبل تحفيز الإجراء بعد الدّفع بمؤشّر الفأرة إلى حافّة الشّاشة" + +#. i18n: ectx: property (text), widget (QLabel, activationDelayLabel) +#: main.ui:209 +#, kde-format +msgid "Activation &delay:" +msgstr "&مهلة التّنشيط:" + +#. i18n: ectx: property (suffix), widget (QSpinBox, kcfg_ElectricBorderDelay) +#. i18n: ectx: property (suffix), widget (QSpinBox, kcfg_ElectricBorderCooldown) +#: main.ui:219 main.ui:254 +#, kde-format +msgid " ms" +msgstr " م‌ث" + +#. i18n: ectx: property (toolTip), widget (QLabel, triggerCooldownLabel) +#: main.ui:238 +#, kde-format +msgid "" +"Amount of time required after triggering an action until the next trigger " +"can occur" +msgstr "كمّ الوقت اللازم حتّى تحفيز الإجراء التّالي بعد انتهاء الأوّل" + +#. i18n: ectx: property (text), widget (QLabel, triggerCooldownLabel) +#: main.ui:241 +#, kde-format +msgid "&Reactivation delay:" +msgstr "مهلة إ&عادة التّنشيط:" + +#. i18n: ectx: property (text), widget (QLabel, label_1) +#: touch.ui:17 +#, kde-format +msgid "" +"You can trigger an action by swiping from the screen edge towards the center " +"of the screen." +msgstr "يمكنك تشغيل إجراء عن طريق التمرير من حافة الشاشة باتجاه منتصف الشاشة." \ No newline at end of file diff --git a/po/ar/kcmkwm.po b/po/ar/kcmkwm.po new file mode 100644 index 0000000..b596bba --- /dev/null +++ b/po/ar/kcmkwm.po @@ -0,0 +1,1390 @@ +# translation of kcmkwm.po to Arabic +# translation of kcmkwm.po to +# Copyright (C) 2002, 2004, 2006, 2007, 2008 Free Software Foundation, Inc. +# Isam Bayazidi , 2001,2002. +# Ammar Tabbaa , 2004. +# محمد سعد Mohamed SAAD , 2006. +# AbdulAziz AlSharif , 2007. +# Youssef Chahibi , 2007. +# zayed , 2008. +# Zayed Al-Saidi , 2010, 2021, 2022. +msgid "" +msgstr "" +"Project-Id-Version: kcmkwm\n" +"Report-Msgid-Bugs-To: https://bugs.kde.org\n" +"POT-Creation-Date: 2022-09-02 00:47+0000\n" +"PO-Revision-Date: 2022-09-04 20:44+0400\n" +"Last-Translator: Zayed Al-Saidi \n" +"Language-Team: ar\n" +"Language: ar\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " +"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" +"X-Generator: Lokalize 22.08.0\n" + +#, kde-format +msgctxt "NAME OF TRANSLATORS" +msgid "Your names" +msgstr "Mohamed SAAD محمد سعد,زايد السعيدي" + +#, kde-format +msgctxt "EMAIL OF TRANSLATORS" +msgid "Your emails" +msgstr "metehyi@free.fr,zayed.alsaidi@gmail.com" + +#. i18n: ectx: property (title), widget (QGroupBox, groupBox_1) +#: actions.ui:17 +#, kde-format +msgid "Inactive Inner Window Actions" +msgstr "إجراءات نافذة داخلية خاملة" + +#. i18n: ectx: property (text), widget (QLabel, label_1) +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: actions.ui:26 mouse.ui:177 +#, kde-format +msgid "&Left click:" +msgstr "النّقر بالي&سار:" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandWindow1) +#: actions.ui:39 +#, kde-format +msgid "" +"In this row you can customize left click behavior when clicking into an " +"inactive inner window ('inner' means: not titlebar, not frame)." +msgstr "" +"في هذا الصف، يمكنك تخصيص سلوك النقر بالزر الأيسر عند النقر في نافذة داخلية " +"خاملة ('داخلية' تعني: ليس إطار النافذة ولا شريط العنوان)." + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow3) +#: actions.ui:43 actions.ui:83 actions.ui:123 +#, kde-format +msgid "Activate, raise and pass click" +msgstr "نشّط، ارفع و مرّر النقرة" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow3) +#: actions.ui:48 actions.ui:88 actions.ui:128 +#, kde-format +msgid "Activate and pass click" +msgstr "نشّط و مرّر النقرة" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar3) +#: actions.ui:53 actions.ui:93 actions.ui:133 mouse.ui:293 mouse.ui:408 +#: mouse.ui:523 +#, kde-format +msgid "Activate" +msgstr "نشّط" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindow3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar3) +#: actions.ui:58 actions.ui:98 actions.ui:138 mouse.ui:283 mouse.ui:398 +#: mouse.ui:513 +#, kde-format +msgid "Activate and raise" +msgstr "نشّط وارفع" + +#. i18n: ectx: property (text), widget (QLabel, label_2) +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: actions.ui:66 mouse.ui:200 +#, kde-format +msgid "&Middle click:" +msgstr "النّقر بالوس&ط:" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandWindow2) +#: actions.ui:79 +#, kde-format +msgid "" +"In this row you can customize middle click behavior when clicking into an " +"inactive inner window ('inner' means: not titlebar, not frame)." +msgstr "" +"في هذا الصف، يمكنك تخصيص سلوك النقر بالزر الأوسط عند النقر في نافذة داخلية " +"خاملة ('داخلية' تعني: ليس إطار النافذة ولا شريط العنوان)." + +#. i18n: ectx: property (text), widget (QLabel, label_3) +#. i18n: ectx: property (text), widget (QLabel, label_7) +#: actions.ui:106 mouse.ui:213 +#, kde-format +msgid "&Right click:" +msgstr "النقر بالي&مين:" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandWindow3) +#: actions.ui:119 +#, kde-format +msgid "" +"In this row you can customize right click behavior when clicking into an " +"inactive inner window ('inner' means: not titlebar, not frame)." +msgstr "" +"في هذا الصف، يمكنك تخصيص سلوك النقر بالزر الأيمن عند النقر في نافذة داخلية " +"خاملة ('داخلية' تعني: ليس إطار النافذة ولا شريط العنوان)." + +#. i18n: ectx: property (text), widget (QLabel, label_4) +#. i18n: ectx: property (text), widget (QLabel, label_2) +#: actions.ui:146 mouse.ui:88 +#, kde-format +msgid "Mouse &wheel:" +msgstr "&عجلة الفأرة:" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandWindowWheel) +#: actions.ui:159 +#, kde-format +msgid "" +"In this row you can customize behavior when scrolling into an inactive inner " +"window ('inner' means: not titlebar, not frame)." +msgstr "" +"في هذا الصف، يمكنك تخصيص سلوك التمرير في نافذة داخلية خاملة ('داخلية' تعني: " +"ليس إطار النافذة ولا شريط العنوان)." + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindowWheel) +#: actions.ui:163 +#, kde-format +msgid "Scroll" +msgstr "التمرير" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindowWheel) +#: actions.ui:168 +#, kde-format +msgid "Activate and scroll" +msgstr "نشّط ومرر" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandWindowWheel) +#: actions.ui:173 +#, kde-format +msgid "Activate, raise and scroll" +msgstr "نشّط وارفع ومرر" + +#. i18n: ectx: property (title), widget (QGroupBox, groupBox_2) +#: actions.ui:184 +#, kde-format +msgid "Inner Window, Titlebar and Frame Actions" +msgstr "النافذة الداخلية وشريط العنوان والإطار" + +#. i18n: ectx: property (text), widget (QLabel, label_5) +#: actions.ui:195 +#, kde-format +msgid "Mo&difier key:" +msgstr "ال&مفتاح المغيِّرة:" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandAllKey) +#: actions.ui:205 +#, kde-format +msgid "" +"Here you select whether holding the Meta key or Alt key will allow you to " +"perform the following actions." +msgstr "" +"هنا يمكنك انتقاء ما إذا كان الضغط على مفتاح Meta أو Alt سوف يسمح لك بالقيام " +"بالإجراءات التالية." + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAllKey) +#: actions.ui:209 +#, kde-format +msgid "Meta" +msgstr "Meta" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAllKey) +#: actions.ui:214 +#, kde-format +msgid "Alt" +msgstr "Alt" + +#. i18n: ectx: property (text), widget (QLabel, label_6) +#: actions.ui:236 +#, kde-format +msgid " + " +msgstr " + " + +#. i18n: ectx: property (text), widget (QLabel, label_7) +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: actions.ui:248 mouse.ui:601 +#, kde-format +msgid "L&eft click:" +msgstr "النّ&قر باليسار:" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandAll1) +#: actions.ui:261 +#, kde-format +msgid "" +"In this row you can customize left click behavior when clicking into the " +"titlebar or the frame." +msgstr "" +"في هذا الصف، يمكنك تخصيص سلوك النقر بالزر الأيسر عند النقر في شريط العنوان " +"أو الإطار." + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll3) +#: actions.ui:265 actions.ui:335 actions.ui:405 +#, kde-format +msgid "Move" +msgstr "انقل" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll3) +#: actions.ui:270 actions.ui:340 actions.ui:410 +#, kde-format +msgid "Activate, raise and move" +msgstr "نشّط و ارفع وحرّك" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar3) +#: actions.ui:275 actions.ui:345 actions.ui:415 mouse.ui:246 mouse.ui:308 +#: mouse.ui:361 mouse.ui:423 mouse.ui:476 mouse.ui:538 +#, kde-format +msgid "Toggle raise and lower" +msgstr "بدّل الرفع والخفض" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll3) +#: actions.ui:280 actions.ui:350 actions.ui:420 +#, kde-format +msgid "Resize" +msgstr "أعد التحجيم" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar3) +#: actions.ui:285 actions.ui:355 actions.ui:425 mouse.ui:236 mouse.ui:298 +#: mouse.ui:351 mouse.ui:413 mouse.ui:466 mouse.ui:528 +#, kde-format +msgid "Raise" +msgstr "ارفع" + +#. i18n: @item:inlistbox behavior on double click +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_TitlebarDoubleClickCommand) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar3) +#: actions.ui:290 actions.ui:360 actions.ui:430 mouse.ui:65 mouse.ui:241 +#: mouse.ui:303 mouse.ui:356 mouse.ui:418 mouse.ui:471 mouse.ui:533 +#, kde-format +msgid "Lower" +msgstr "أقل" + +#. i18n: @item:inlistbox behavior on double click +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_TitlebarDoubleClickCommand) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar3) +#: actions.ui:295 actions.ui:365 actions.ui:435 mouse.ui:55 mouse.ui:251 +#: mouse.ui:313 mouse.ui:366 mouse.ui:428 mouse.ui:481 mouse.ui:543 +#, kde-format +msgid "Minimize" +msgstr "صغّر" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll3) +#: actions.ui:300 actions.ui:370 actions.ui:440 +#, kde-format +msgid "Decrease opacity" +msgstr "أنقِص الشّفافيّ" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll3) +#: actions.ui:305 actions.ui:375 actions.ui:445 +#, kde-format +msgid "Increase opacity" +msgstr "زِد الشّفافيّة" + +#. i18n: @item:inlistbox behavior on double click +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAll3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAllWheel) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_TitlebarDoubleClickCommand) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandTitlebarWheel) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar1) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar2) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandActiveTitlebar3) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandInactiveTitlebar3) +#: actions.ui:310 actions.ui:380 actions.ui:450 actions.ui:505 mouse.ui:80 +#: mouse.ui:132 mouse.ui:271 mouse.ui:333 mouse.ui:386 mouse.ui:448 +#: mouse.ui:501 mouse.ui:563 +#, kde-format +msgid "Do nothing" +msgstr "لا تفعل شيئا" + +#. i18n: ectx: property (text), widget (QLabel, label_8) +#: actions.ui:318 +#, kde-format +msgid "Middle &click:" +msgstr "النّقر بالوس&ط:" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandAll2) +#: actions.ui:331 +#, kde-format +msgid "" +"In this row you can customize middle click behavior when clicking into the " +"titlebar or the frame." +msgstr "" +"في هذا الصف، يمكنك تخصيص سلوك النقر بالزر الأوسط عند النقر في شريط العنوان " +"أو الإطار." + +#. i18n: ectx: property (text), widget (QLabel, label_9) +#. i18n: ectx: property (text), widget (QLabel, label_10) +#: actions.ui:388 mouse.ui:671 +#, kde-format +msgid "Right clic&k:" +msgstr "النقر بالي&مين:" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandAll3) +#: actions.ui:401 +#, kde-format +msgid "" +"In this row you can customize right click behavior when clicking into the " +"titlebar or the frame." +msgstr "" +"في هذا الصف، يمكنك تخصيص سلوك النقر بالزر الأيمن عند النقر في شريط العنوان " +"أو الإطار." + +#. i18n: ectx: property (text), widget (QLabel, label_10) +#: actions.ui:458 +#, kde-format +msgid "Mo&use wheel:" +msgstr "&عجلة الفأرة:" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_CommandAllWheel) +#: actions.ui:471 +#, kde-format +msgid "" +"Here you can customize KDE's behavior when scrolling with the mouse wheel in " +"a window while pressing the modifier key." +msgstr "" +"هنا يمكنك تخصيص سلوك الكيدي عند التمرير بواسطة عجلة الفأرة في النافذة أثناء " +"الضغط على المفتاح المغيّر." + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAllWheel) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandTitlebarWheel) +#: actions.ui:475 mouse.ui:102 +#, kde-format +msgid "Raise/lower" +msgstr "ارفع/اخفض" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAllWheel) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandTitlebarWheel) +#: actions.ui:480 mouse.ui:107 +#, kde-format +msgid "Shade/unshade" +msgstr "ظلّل/لا تظلّل" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAllWheel) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandTitlebarWheel) +#: actions.ui:485 mouse.ui:112 +#, kde-format +msgid "Maximize/restore" +msgstr "كبّر/استعد" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAllWheel) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandTitlebarWheel) +#: actions.ui:490 mouse.ui:117 +#, kde-format +msgid "Keep above/below" +msgstr "ابقِ أعلى/أسفل" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAllWheel) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandTitlebarWheel) +#: actions.ui:495 mouse.ui:122 +#, kde-format +msgid "Move to previous/next desktop" +msgstr "انقل للسابق/سطح المكتب التالي" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandAllWheel) +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_CommandTitlebarWheel) +#: actions.ui:500 mouse.ui:127 +#, kde-format +msgid "Change opacity" +msgstr "غيّر العتمة" + +#. i18n: ectx: property (text), widget (QLabel, shadeHoverLabel) +#: advanced.ui:20 +#, kde-format +msgid "Window &unshading:" +msgstr "&تضليل النافذة:" + +#. i18n: ectx: property (whatsThis), widget (QCheckBox, kcfg_ShadeHover) +#: advanced.ui:32 +#, kde-format +msgid "" +"

    If this option is enabled, a shaded window will " +"unshade automatically when the mouse pointer has been over the titlebar for " +"some time.

    " +msgstr "" +"

    إذا مكن هذا الخيار ، فسيلغى تظليل النافذة المظللة " +"تلقائيًا عندما يكون مؤشر الفأرة فوق شريط العنوان لبعض الوقت.

    " + +#. i18n: ectx: property (text), widget (QCheckBox, kcfg_ShadeHover) +#: advanced.ui:35 +#, kde-format +msgid "On titlebar hover after:" +msgstr "بعد تحويم على شريط العنوان مدة:" + +#. i18n: ectx: property (whatsThis), widget (QSpinBox, kcfg_ShadeHoverInterval) +#: advanced.ui:42 +#, kde-format +msgid "" +"Sets the time in milliseconds before the window unshades when the mouse " +"pointer goes over the shaded window." +msgstr "" +"يعيّن الوقت الذي يمر بالجزء من الألف من الثانية قبل إزالة التظليل عن نافذة " +"عند وضع مؤشر الفأرة فوقها." + +#. i18n: ectx: property (suffix), widget (QSpinBox, kcfg_DelayFocusInterval) +#. i18n: ectx: property (suffix), widget (QSpinBox, kcfg_AutoRaiseInterval) +#. i18n: ectx: property (suffix), widget (QSpinBox, kcfg_ShadeHoverInterval) +#: advanced.ui:45 focus.ui:85 focus.ui:178 +#, kde-format +msgid " ms" +msgstr "م.ث" + +#. i18n: ectx: property (text), widget (QLabel, windowPlacementLabel) +#: advanced.ui:66 +#, kde-format +msgid "Window &placement:" +msgstr "&موضع النافذة:" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_Placement) +#: advanced.ui:76 +#, kde-format +msgid "" +"

    The placement policy determines where a new window " +"will appear on the desktop.

    • Smart will try to achieve a minimum overlap of windows
    • Maximizing will try to maximize every window to fill the " +"whole screen. It might be useful to selectively affect placement of some " +"windows using the window-specific settings.
    • Cascade will " +"cascade the windows
    • Random will use a random " +"position
    • Centered will place the window " +"centered
    • Zero-cornered will place the window in " +"the top-left corner
    • Under mouse will place the " +"window under the pointer
    " +msgstr "" +"

    تحدد سياسة الموضع مكان ظهور نافذة جديدة على سطح المكتب." +"

    • ذكي سيحاول " +"تحقيق الحد الأدنى من التداخل
    • تكبير سيحاول " +"التكبير تكبير كل نافذة لملء الشاشة بأكملها. قد يكون من المفيد تأثير على " +"تموضع بعض النوافذ باستخدام الإعدادات الخاصة بالنافذة.
    • التتالي سيجعل النوافذ متتالية
    • عشوائي سيستخدم " +"موقع عشوائي
    • توسيط سيضع النافذة في الوسط
    • زاوية سيضع النافذة في الزاوية اليسرى العلوية
    • تحت الفأرة سيضع النافذة تحت المؤشر
    " + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_Placement) +#: advanced.ui:80 +#, kde-format +msgid "Minimal Overlapping" +msgstr "الحد الأدنى من التراكب" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_Placement) +#: advanced.ui:85 +#, kde-format +msgid "Maximized" +msgstr "مكبرة" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_Placement) +#: advanced.ui:90 +#, kde-format +msgid "Cascaded" +msgstr "متتالي" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_Placement) +#: advanced.ui:95 +#, kde-format +msgid "Random" +msgstr "عشوائي" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_Placement) +#: advanced.ui:100 +#, kde-format +msgid "Centered" +msgstr "في الوسط" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_Placement) +#: advanced.ui:105 +#, kde-format +msgid "In Top-Left Corner" +msgstr "في زاوِيَة أعلى اليسار" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_Placement) +#: advanced.ui:110 +#, kde-format +msgid "Under mouse" +msgstr "تحت الفأرة" + +#. i18n: ectx: property (whatsThis), widget (QCheckBox, kcfg_AllowKDEAppsToRememberWindowPositions) +#: advanced.ui:118 +#, kde-format +msgid "" +"When turned on, apps which are able to remember the positions of their " +"windows are allowed to do so. This will override the window placement mode " +"defined above." +msgstr "" +"عند التشغيل ، يُسمح للتطبيقات التي يمكنها تذكر مواضع النوافذ الخاصة بها " +"بالقيام بذلك. سيؤدي هذا إلى تجاوز وضع وضع النافذة المحدد أعلاه." + +#. i18n: ectx: property (text), widget (QCheckBox, kcfg_AllowKDEAppsToRememberWindowPositions) +#: advanced.ui:121 +#, kde-format +msgid "" +"Allow apps to remember the positions of their own windows, if they support it" +msgstr "اسمح للتطبيقات بتذكر مواضع النوافذ الخاصة بها ، إذا كانت تدعمها" + +#. i18n: ectx: property (text), widget (QLabel, specialWindowsLabel) +#: advanced.ui:128 +#, kde-format +msgid "&Special windows:" +msgstr "ال&نوافذ الخاصة:" + +#. i18n: ectx: property (whatsThis), widget (QCheckBox, kcfg_HideUtilityWindowsForInactive) +#: advanced.ui:138 +#, kde-format +msgid "" +"When turned on, utility windows (tool windows, torn-off menus,...) of " +"inactive applications will be hidden and will be shown only when the " +"application becomes active. Note that applications have to mark the windows " +"with the proper window type for this feature to work." +msgstr "" +"عند تشغيلها ، ستخفى نوافذ الأدوات (نوافذ الأدوات ، القوائم الممزقة ، ...) " +"للتطبيقات غير النشطة ولن تظهر إلا عندما يصبح التطبيق نشطًا. لاحظ أن التطبيقات " +"يجب أن تحدد النوافذ بنوع النافذة المناسب حتى تعمل هذه الميزة." + +#. i18n: ectx: property (text), widget (QCheckBox, kcfg_HideUtilityWindowsForInactive) +#: advanced.ui:141 +#, kde-format +msgid "Hide utility windows for inactive applications" +msgstr "اخفِ نوافذ المساعدة للتطبيقات الخاملة" + +#. i18n: ectx: property (text), widget (QLabel, activationDesktopPolicyLabel) +#: advanced.ui:148 +#, kde-format +msgid "Virtual Desktop behavior:" +msgstr "سلوك سطح المكتب الافتراضي:" + +#. i18n: ectx: property (text), widget (QLabel, activationDesktopPolicyDescriptionLabel) +#: advanced.ui:158 +#, kde-format +msgid "When activating a window on a different Virtual Desktop:" +msgstr "عند تنشيط نافذة على سطح مكتب أخر:" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_ActivationDesktopPolicy) +#: advanced.ui:165 +#, kde-format +msgid "" +"

    This setting controls what happens when an open window " +"located on a Virtual Desktop other than the current one is activated.

    Switch to that Virtual Desktop will switch to the Virtual Desktop where the window is currently " +"located.

    Bring window to current " +"Virtual Desktop will cause the window to jump to the active Virtual " +"Desktop.

    " +msgstr "" +"

    هذا الإعداد يتحكم بما يحديث عند تنشيط نافذة مفتوحة على " +"سطح مكتب افتراضي أخر غير الحالي.

    بدّل إلى ذلك المكتب سيبدل إلى سطح المكتب الافتراضي حيث توجد تلك " +"النافذة.

    اجلب النافذة إلى سطح " +"المكتب الحالي سيجعل النافذة تقفز إلى سطح المكتب الافتراضي الحالي. " + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_ActivationDesktopPolicy) +#: advanced.ui:169 +#, kde-format +msgid "Switch to that Virtual Desktop" +msgstr "بدّل إلى ذلك المكتب" + +#. i18n: ectx: property (text), item, widget (QComboBox, kcfg_ActivationDesktopPolicy) +#: advanced.ui:174 +#, kde-format +msgid "Bring window to current Virtual Desktop" +msgstr "اجلب النافذة إلى سطح المكتب الحالي" + +#. i18n: ectx: property (text), widget (QLabel, windowFocusPolicyLabel) +#: focus.ui:22 +#, kde-format +msgid "Window &activation policy:" +msgstr "&سياسة تنشيط النافذة:" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, windowFocusPolicy) +#: focus.ui:32 +#, kde-format +msgid "With this option you can specify how and when windows will be focused." +msgstr "باستخدام هذا الخيار ، يمكنك تحديد كيف ومتى سيتم تركيز على النوافذ." + +#. i18n: ectx: property (text), item, widget (QComboBox, windowFocusPolicy) +#: focus.ui:36 +#, kde-format +msgctxt "sassa asas" +msgid "Click to focus" +msgstr "انقر للتركيز" + +#. i18n: ectx: property (text), item, widget (QComboBox, windowFocusPolicy) +#: focus.ui:41 +#, kde-format +msgid "Click to focus (mouse precedence)" +msgstr "انقر للتركيز (أولوية الفأرة)" + +#. i18n: ectx: property (text), item, widget (QComboBox, windowFocusPolicy) +#: focus.ui:46 +#, kde-format +msgid "Focus follows mouse" +msgstr "التركيز يتبع الفأرة" + +#. i18n: ectx: property (text), item, widget (QComboBox, windowFocusPolicy) +#: focus.ui:51 +#, kde-format +msgid "Focus follows mouse (mouse precedence)" +msgstr "التركيز يتبع الفأرة (أولوية الفأرة)" + +#. i18n: ectx: property (text), item, widget (QComboBox, windowFocusPolicy) +#: focus.ui:56 +#, kde-format +msgid "Focus under mouse" +msgstr "التركيز تحت الفأرة" + +#. i18n: ectx: property (text), item, widget (QComboBox, windowFocusPolicy) +#: focus.ui:61 +#, kde-format +msgid "Focus strictly under mouse" +msgstr "التركيز تحت الفأرة بصرامة" + +#. i18n: ectx: property (text), widget (QLabel, delayFocusOnLabel) +#: focus.ui:69 +#, kde-format +msgid "&Delay focus by:" +msgstr "&تأخير التركيز بـ:" + +#. i18n: ectx: property (whatsThis), widget (QSpinBox, kcfg_DelayFocusInterval) +#: focus.ui:82 +#, kde-format +msgid "" +"This is the delay after which the window the mouse pointer is over will " +"automatically receive focus." +msgstr "" +" هذا هو التأخير الذي ستستقبل بعده النافذة التي يوجد فوقها مؤشر الفأرة التركيز" + +#. i18n: ectx: property (text), widget (QLabel, focusStealingLabel) +#: focus.ui:101 +#, kde-format +msgid "Focus &stealing prevention:" +msgstr "&منع سرقة التركيز:" + +#. i18n: ectx: property (whatsThis), widget (QComboBox, kcfg_FocusStealingPreventionLevel) +#: focus.ui:114 +#, kde-format +msgid "" +"

    This option specifies how much KWin will try to " +"prevent unwanted focus stealing caused by unexpected activation of new " +"windows. (Note: This feature does not work with the Focus under mouse or Focus strictly under mouse focus policies.)

    • None: Prevention is turned off and new " +"windows always become activated.
    • Low: Prevention is " +"enabled; when some window does not have support for the underlying mechanism " +"and KWin cannot reliably decide whether to activate the window or not, it " +"will be activated. This setting may have both worse and better results than " +"the medium level, depending on the applications.
    • Medium: Prevention is enabled.
    • High: New windows " +"get activated only if no window is currently active or if they belong to the " +"currently active application. This setting is probably not really usable " +"when not using mouse focus policy.
    • Extreme: All " +"windows must be explicitly activated by the user.

    Windows that " +"are prevented from stealing focus are marked as demanding attention, which " +"by default means their taskbar entry will be highlighted. This can be " +"changed in the Notifications control module.

    " +msgstr "" +"

    يحدد هذا الخيار المقدار الذي سيحاول كوين منع سرقة " +"التركيز غير المرغوب فيه بسبب التنشيط غير المتوقع للنوافذ الجديدة. (ملاحظة: " +"لا تعمل هذه الميزة معالتركيز تحت الفأرة أو التركيز تحت الفأرة بصرامة مع سياسات التركز تحت الفأرة.)

    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 dmwxTz&`RZ)FU;{~t0EMR))J literal 0 HcmV?d00001 diff --git a/doc/windowspecific/kopete-chat-attribute.png b/doc/windowspecific/kopete-chat-attribute.png new file mode 100644 index 0000000000000000000000000000000000000000..4e59f2ebe8c866b43571c01c0f1a0ebbf6f2bff6 GIT binary patch literal 50398 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 z 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≀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 literal 0 HcmV?d00001 diff --git a/doc/windowspecific/kwin-detect-window.png b/doc/windowspecific/kwin-detect-window.png new file mode 100644 index 0000000000000000000000000000000000000000..b1efe5a87e618d5f3a09ebdb5773d8030add8a68 GIT binary patch literal 27744 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 j1&KYrhK2QU57$38JB@b0c5H(*OSex)z-R literal 0 HcmV?d00001 diff --git a/doc/windowspecific/kwin-rules-main-n-akregator.png b/doc/windowspecific/kwin-rules-main-n-akregator.png new file mode 100644 index 0000000000000000000000000000000000000000..509af2b7553376ffe32af290e51b6ce00fc3a2d3 GIT binary patch literal 49262 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 literal 0 HcmV?d00001 diff --git a/doc/windowspecific/kwin-window-attributes.png b/doc/windowspecific/kwin-window-attributes.png new file mode 100644 index 0000000000000000000000000000000000000000..4bc0fe3068bc5591a779562ac87af542c81caccf GIT binary patch literal 61505 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

    C!HzjxY+)nO9!V_nQ&@bg#>R|SN{3YIPgtvi z^6jBw`_ik|A^8wbWeiK7|DLSxx`@oytmRZBcvlgIAOpX+%{JyPe_-r~a>&WG9UqyE z6r0iH-r;p&b>}vMMO=qzh?S&ZOjr+#FPT3(ZW&2*iBiD9U!<(p04$pu>LPMX#+tuk z*o^DM7z#f?pE8wfszemrVRz9s#fh+M#o5bVJxzn$G(D0VMHbsJrRlo}f z{>g1f?)WTU8FbMlmpVCeT!`Dq1sz1E+j-hH*ap!8Je%z$bv&E`D6Ax!o4*m*j#3WC z>*ub;KoZYYl@!vQK|ZoHGjZFKuTsmTv^1&d9#41=^?z!L{zlh=YktcT&*%K=+-^0H zQ>4rGk(>8&t4nf>`sMi4vS_0`hVq(U>yLWF)dk4YyS*lT1d(+E@ej1BOqQtlxYQ=Y zepL6XdPIwwQC3wbJ$Ckdg?o5O09#R^*dblOlX>jA{fsKfW_#Kb7hY-^xH4(l8bx$Z zOYZtY_!1msXdiGV?vFpbvvGGh_9iYSVWzvgn)P>f&F1abO#u|5DbVVjo5H|CfM6*< zmAgLAA=n<20$ECgFtzje$9lO-1g|4Y9QR=oVco{OxPWED!pvx)pG#|-Dk`7snb=pq zm!#N44Et6y5sIBT`L3H@PO>@Tq|?2ZDlaBtM4qHfMb{s7h3{F!;J6BHawa6xy4qXe zrGbjoLK?dtW=rVF@vj8~Sj8`Pt1GbJ{c!4Po1^`Z<_zkqy$oGpS5MTQtyLXePQtKk zn&NV7jI8Q5r~1=*jD(|m86zR|yO*C1wrX>AnD*`8-_Rr%NGVk993OA34irToOwqfY z7O*;RSLdG{`Bm{(jj^a{=8^H^^kCxwj{5PF_E zZww=BZxxy!1KsBNq87448_&Z}v5uChLr|*twKH12$+1591)vz|S|hFkuD74{>w=;& z1nD%3o->#!w)~~+Da4SC^^iUr1n?Opx!z{;Y_D=oEPLSv-qH&onOn5t>;pEskX4w3 z6-@)(PqZt4E>?%xmJ@<}ii1fkj?V^mC%4?!;z&PJ4Plz(VjP?8xjkETO(fgi=i8`G zKYH^dm%lunjwUC?{4*p4P_3ELc>IEze)uZCedq!G2W6jQS#OT7ET>&cZ4mB`qPI48 zvS=69x6bQG2;49cZWYl{WE5_h-9Hy@ZKKpVAzIrGpwcM}fbhBMKY_StrK3*cqK=1| zqGw^a0Zkz0n5#yM#~W*y=D^LPXeE;lNhwx6c^<&W<;26pguS)rVg`dCTE$bNxf-sG z-s-73hu~4U^$CK9Udk({`>wK3lZ+`$v5DT@yPe^k$zA|qnoqd){4nIPnB&hFXomfW zq>0~EW?RA6S{?)4pN0k)i9z>ALpL}Rg^%L{jEy-H`$ds|(i=QK*TD|c-vMS9(B+!R zHd}2x$bD^qN*##%MCZ;!Orv*CgS0I`mDxkKnTGirE*V^@GH7t9hJPA7-nio$Na*Lu zl={Q9SSK;gX69DH4qYxMb%Hps8GD^OYx+l*`=2i}fJa8}A@q^F+WieH*~C~XkrqJb zCAhm37faJ;AG^K5w<{-2%_m>7UOcbv5sgs1p$!msmP$T-!QUWnv46soscGb%gYiUP zO+pNqPO@I^)RwR{VtAOod=1g-!#ZnLUX&D;#D>gxS?lxH$?!s?&ojPbjc~IM9exbt!0 zHYh4tw5g5lh)}){BQk~bQgwwTZv|5!o{3~(T5_TjFY#1zZHL-bdWIbD!iYfxm1xD3 z6kX`p`Sboi-Hlr-xEeNpbu*mOK0mdl5=8E0XbEpELh#=thtSSqW>SeJ}I^a`xbgz;dwGs_2#>(IuV6Fg7pRA`kTGM5%@$1_>4>vyXKX}R_Iz5(@SUQ`;4?I8fcd3m#{GyNFQJ5Y9~>Z= zC3=*!P-P%4LxXFk$N^){_(9iR!tVL==XD{k%WD$!v;ZFD&ylLyb1i#%!ot|<(KY8Q zrk0eq?;U;ZE5~%T{7l9SP<}Y0M*j7;UEY*}@A zU9viz3$3$hIbp57t)TjgoKLB`1&zcPqG&O#r>D)}Yz^)2?hY&UWb@9v$>`GWJtIJ` zMn05R0ztobx|xgJ@&JDlzNUCFo=+)>UY{mRDuOGzilM#o0w;{&TpmjHiF+9J<+XHx zgDRAKEA;|r7{4kYJpI}3U9$3l&W!S_vWEi46A?)e9UzhhumQiMcC`&MXZ#Tmil6~o z#=y#tmWu`(1u=~tevSF2Q=SZ=;?y<)4h=7Hh9yC$45xr*k40@5IByB*yWqDVOt_FA zp~I;*oDs{fyt1KlevJ*V|_6hEXrE^_W*zUoDk$+wxVoGWF$ZNl|f1)#$CR9x2 zAm-Ug1-LEJz#)Lc1V{Myw#C3W&w?8d++cxyWrCzd(WPOw{`Jp&CGax3#LTCkCuLjO zCRWjW>SOuorkPVhu57{h-PB9T4yd>UgNwA3__s7LY^A$WjOEDinCq1>nXIYC^CQz! zSS??auD)L7z_fZd@7W~C95*b_Ew#+le((N+Y;Mg&tP=7;P~B9HvS;y&lZ)Wq0x$*l zrdVX}xfND6z^=TT#{WVZm*_t8>+GbvV7ySX+ez=au2^bK9}2RLG{~Sr^hLAVA~xvC zE%#ov)R&M%@C?yv1#)!X>Eh=flwFz*toavtO)c zD{JuBsy$Z{+%2ZD1sl@i&ObG@Grl%|@)X zM)B(aO#!-g&V`s|b`}ZHEEH$XMSNNGNE?-rZt}DSlV^4unNLlT9mhWcukSTSLXEDd z+ZUmef8{-mZnR@XcVbcp2CoZL$J~;S&V&HEB$+dI2z6C4muWV|!=C#oT_TZh; zkek=Vg}naZ5M_|f+3G>vlb_mOera{Fd~{2K=LEmg!wKHk`UYsrjb;Nqf7d^3k0m}T+RrL-5;YwWsN zS>fKd|719tc=L>SrrMUEFJs$53}NyX9C3I2kqtoTkv$g;+zhy2aJuCnjEI#AW*_`7`xZme}(U;F~BX72^m+ZD0*ZpS01s}hBlN1c*Uc$SY(1ZIeu|2HHe!DWv`A{_sBBqH;1?%n@HBCth} zIUd*m%c0#u?+z=@xsujR;V-`TYU{Ks$OTT@G^HmAWSo5Z_&4GVb`D3iHB zGYLRZLY|Ehn4TD3HPa~a?!cA}1pIktCccsFqZ{PL%Rj0wS20hp193Lv^0ajp0egp2 zGzG0z&f{ZDoF4tH(=J>IZ>pPHO0#W04ZdcEhAoDQ@$Mn#{M_1PEL~T5zM}kA-j2#K zj6GrU7Rj#`L5t~+Szj9Rs~F$^aW@NLz-;}5MXpOaz)K;l-r7Viq@(_}9}Eklp6=0* z6V(9e(usXDby2~G$z1DHO^LT!@6tB|reknyvJbo0`U7w8wQTqkD5m#aM#eUp+lo}k zH@S~1lOH4-0}5O-1&Nno345IxGC6C-uWbh2qRa*?(7vzH*5Lg@2%86?cUK#h-I`k5 zBrb5{52w7buVrTUU=lx(F}d6Ib3})7)4H!t0Sg=ZC~#^-ot^I+ZvBlF583#U(eF1X zLf@W5?J|F8;+u~Zv5Ho-yHM~AM`X}^dH5nz-h*6@w-AfO@t6onEYQ<`pV^&-6WOsv z)goMpf#+co*3cK!EQ%Om!dh%aLj}bm1$z+K;*O*E6y?WVyGIOj-@X&-zO@Vyv8VH% zpIE2RNc)~(pr55v(U9d(MBw33lK2@f$6(;c4g~fbpC&STR`BGj!OH*rVqF5n1O%Z*miS^q6n=;I|(g?fcy z=>G*P&?dv=`##X?Rrqv$@&7P?Ne)%eElnt7(NEe~qf-OmS0CM$(aC>4d};MYmae~F zhCjz~zT0%wXqoUTzbPRh$*X)Sow;vn38Bih@g34zAvMQ)wWu5{O-m-+Z6 zS0Hk;XnPXO;j|btJ*a>?7r*9IZ10;94QW;f(AY*8Lu}mq`^k?Gc zWC3&vQTb;AUUcv@zKW*=LNBiNLtyFY(C}J}T0cKB@Q(d!8z@Y z>fQ)MbJ0iLxFKLh`%Y2V+;M)gHkBgEh*BhRrE22=$a|Q}l5EWWf(c~p_SvBr8pJ3G zCippfM*Ra5Mx;R@^BX24lVHNeKQMv9ux9DvEXCehtUOxqDfu7o(Oyw%mE8}bAxrYo zSntA$xr-{+gMiJz%p&1t5Zaw>&dl_VDOjjv1ORbA=vsL6!cw-Z4V3ko41nZ^c@pk( z>WNtaY<}d)cm4=EIa!L@6|E_o=MQ8HYIZ>% zu4_rL%jdkO)f}s)q0Q6>;3CWC(k+orVN#W%X`V8&5}Rm>AR5O#b`w9?Y2Zfsh_=vX z2X~?h(rF_&^swVjx+E?~4!Ifps{p>y@5`TZqa(o32+;j56RkwJ8FR^Ac+RAjowi8# z)md6AE5g8b3kGZFGEVQyGdYH+jG+$cwJZYrLS`>|6bP6_V`*E8<7#NWyG@x!xYs#*e&+tCV zH==`a_af`(2nvSgo5^nNa>z)X?T=NX&{7xjE)5%`W^vBBpNZ3K3c=I+oiQsACK_wO z57BKw<;x-YAu;_kd-N<@cKMRp66~qZg`I{g-N$l6I68-Vz z+9fN9+>nlcqKX(;peI3MvjMrCYt=t_#w*mp#!S+K1$hFK?$HiP03Kz^VU1qK!fZ*I5>Lz~e z_i9~=R6bayFM62N2o8ha;JOg>or_d@cl|RY2z>Ie+*aD$AC=eSSVa4&u z>GbsOubhJz2z_#GXlS0_bn0d)B~IbEGBSibhdA+7yGn`INGDJZ8Mvjpv*)fj;Fll~ z;-dOvnhS#Tg#lDh;F8R&f7Ia;U%uf0VAFYaiz|K}B;eev(_R|Pv@{|c=w)#}FS&>#eJCqSkVWa~7VX?QQH1UN5@cQa$>yv7<^^L0Y1Q8$us=Zh8@B_PIxP*02NHoQKnRQT(GOL{ENT-IU_t zj+MI3_MSx(ygz0tH4l;)cfLJvSBWo^S0M893T330#93H* zqh8cJ)kVkaISMf>`9W3RuE?y)ODoe(`=066;CQpZsKTq3X$xmH`wYQ1C?xQ#tuU~V z^~ssgI@VeJ0qW90Ua&Af%Mlt_P+U5%tX%46>pOEO8y%E}T} z^xkL4aCyQs+KLuY88?lvK<=@{2C)>Pv&(F`8=wu@ag|gd@TRwRJSz3%(~wh3$#8 zcSUb$$#K}KMRQD*L6(@j_1=vrS|+fMa4g1X7Hkz!cznH}mh@7ryYQ2|G$-uJ&pq@e zxJH_?F!!%N$=Mmk(m7&{Qf%@hIH5F=T2+zEW1pmkDMCJ=H7omJq?K8>KHO#ol`eSq zp+WF~LRlMV_CwwR#W>V)cx=elXoZ&Z>=UpWh~&3`S=KmIlEic)fJ%HyNgF{T4r!P* zw}&FI^RXbG0m%Y*8{2lNpA#WTabAmL*OX=EAC>pj4t{8g$Tz)J9MB~S7Tz1;{();Y z20zZPry;=29?Bm=hR4iba3Vh}TQ z9>k2;eJc+3+-h4;J7{pX&S$zlkRhpZBDKIG@4j6mcW=(&=Epr|&MRymWw@E#W}^$! zF#*~}AUyd>i?Q_FN)z{7!5OO^OiJJe5>d;`DE5H_4(ei~gsUgc5n5N?>AD1k=gJgL z@EqdkUW3bTShiBa0v5l+5+Dc}kd4~Ak4tB{$d0<8;0Ep{(#F>oIpTdLG6`3UR?T21 z3lEorAVBnhPoZJJM&!!;#Z#`9LVUAzd{`K{D6yI=q_~6*iNR314~=VWi*trO4o>|H zp#mApVY$)o<^EcWM)(m7Ne2xE$Zfec+B&GjnL!WOfS-XUcwlx6u(KxvuwZm@b3^rN zw4BGbF0xr)&HUg#qo);VAP63W_EY{qF+R}qzVn-E$l%J9pPdHzb&~QM6&uofEaJW! z*-|BgsV_kXWxTfNT;d=D6lYPT?HAL=5?4d ze6@y#0k*9OU=(^;%P8ODL^-k!qVT`iJxUyW19j9_d2!!3_ zMSRDR9=P!`=>ZI!i*(z+AP~Rdn+=w%5$0G=E~I_%>Eub0*1M<= z#FvyXk8Eyr>yfI*YZ)+hC6`AU`dMw?^()t9taX0M0y+CWC&nc#mBP+KZZhoR*+09f zruJ&PxV5U>SQ5u|jq*kmJ)d8^=x85&YzG{-N3mzW`gmgJvHE_6{kj*~rM1@6eXuTG zUI=mTGuBMV0`n^PbbI1U^Xd(`6bv!2jg6_~I9pO$+|d$#B0MrRHXkhazSPaj_VBZD zer#bA@e60qj0h}m90=U-?t>sBIbn5i3GvxFpAFn6 z6CG9*ms`wRuP_fRYwBm+S9~mCZEXrnPtDGkj|4W1P7{8-#riH;I51dN$9XH7&emC{ zn+D@uTvh623oQfQ4=|4u3!_PT*vZmw(c2Skv7hObKIhy!7WgFCBO=>}^G8^18MaXG z?a21Cj?~F~#V3|-ITF{x`2t0y>PHo2RqHrZ{luhc2mUs_*N*R=F! zU%9!n>mzu&+{5u7A}YZ@iw+4YD)TujIqVhE<1>D-A3i8Sv-yyz#vR<)m)Q@p+*(ft zoM&45EcU~6D#T1gTiTlm?NK=Se&Mq9aThcHlZ(~3(vYDxG{feG=io?w+38o-?qOzE z2{7A3mw%yV$>v*`Z$>}b=Wd?odWD>Wt4CM&T;P-{c(DMJL9ecX5Ht$Hly``Z#YU!E zyjyE#HzNI3ttSZU@eIi#D#`Oj%yTLo!_+CLZ-Hzef`XW&++*xjz7?v#Dh4iq8og zueHNGBe`F_O?Yfs?}>6k1G08T3A&a|9Ts+M?^$FtS#oKiqG4rRjHe^n4?{IYiEtx%bp1y9W%u9 zl>{P-<4l)R19Iz3R%hH1z_e`6%;x2$E>vZTJ?a*E=EPIc62p zykJyKVX0}+&MWY%%Ni~wa~}2H0H8Guqp}@neRw5$E4HF}esqOOPQ!-DQ)t$S6k9rc z(qU%+$PEV9d_SET1mZ!D z;weTlb;i8<)yHQQF5TrGT&=E5uti847%ngz(5|0o3@TwhdH;TPu*#7q)8S$=U2ocw zrC(-B?Q0u-Z0nwZvYAVsKMr0Y(s}6g2o=e!aTZY)h)SL|#tz53&Kys8q5@}wJDuj} zRP4qS`uNRmu$Tr_ID&QZ)RlS_{Ls9Bv6A1ku;@gN``Z@ZbtWp7bV1}T`qra9?-a|R zZ{>!b$KRQ(2-c$usbvu3E6)a>mU3^~+9w{~(oAC>;X7Mzz0N|m7**N@GpwvY=geo4 z(QAo^1TALP{l0d$T!Z&9CXb^xTjw-vf>017hRVus<+UpuB#O~>6}g_Xd%26edCQWF zq7Sv?qxgCmAcq}^D%>hrce?_nK=2(T%TpMzI<=l46;TnO-ME_4AQWhv5 zQauxyZsACh5d27L#q9=CuETJTNd6R2@Ys3y zqcYk0q|UqJ{epZ+Z3esBG0K?`SCt>liXw+O&Sz)Re@z{5E~pu#1cyM(<~ zH>)>vLB9qL*xu$SiEB3r`P5sQy{MoeOFUl}GP9^HvG{4PPrA6j6MWXqJN1QD@t|4d z#Wbr@Pae6?bFnf-dLFB(@HbCWo9+27=07sq=EzJ$r>BCI=_UYSdLx4Cs~aE7_}LEsYO2oCMhpHe;fT+!Zz;&tz*X_m&$|0uz9uy{2JnmW@v)< z39+*w8w{FMZDIzva;JM_q?Mo0wVV`}Hg%^s9cjEMgV5`~InHMv^Sm>dvUh8()u+pk z-SF{|wVa{-z*|?9p@%uy_=y2NWYP0D1AW9=^~suBn`ToYIQ@a=oO+0aXxm=e=D}pb z-eITf?+X0W5(IoE<5`)itx=h|BQ~N&W}hF1m%ALp5wgEI^?@qd?SGQr%o3VW_D%b0QgqF6Huk93P zws9Leln2(PZxKJD?lDATn7tQF`zp}#&Ixjdt$lTu!3#@%53OcaB0_?RRl)JpNXc&e z@@N9YxGt&cv!r{R2S+V(0p|%2cyst&7vDFbUXNDWHatO#Em`%NZkV{~Hx=rn9usif zrW@?i23b4F#=MK4oAxsfcYZ;(*XmmQ9dcaA2(=acFneb=8Qr`2jAAMt+S_>-BRn)f z2PFxqn$(?8PH9lj(Fig!p@2acZ_q}TtxTf;J@VevxSLY|-%pr!q$gW*?=*Z)9d}KJ zD^F$`teYBgB#Fm6R(k$^oio@9(bh1C@p;_HkLCpO5tTDnWUeiMThtxRoK2n5h%IWj zSWK`6o!Uwfmuau1xJ|mkzM|02DtWG<6C*h51#DSp_aGz1)Dk`F!tecJP7bi-J%))q zNU~XEkDUglfAsu$_cjN@X^M}^iTXz&dlc%A!*BPQR_}D{DZK3e9y5ad7%#AjKa16L zh0R7?e+|*9>Q~uIqr(2ykg&Yc>HF|b=XIo2YWD2jCM_Zc{k^85X1Rq)g=0GbS*Xvt=+D(-)#!XnTPCr+I~*M zgJ#-qLP$ZA^3=Z*8?dT#V0`2ZxEK;R;n)A4k{J(IQW;!C8Qw|0=V{u!ejd|k~` z>#DL_1lR9MG#fW&FdR}H3Nekqb|O=Xop%|rugB*GV(;5J z^;gpKX=AEi_0K+?vrN zEHR~$v{P#eyD~C3Uw5UN_@zB(?UppVbkqJi_tTuwE(xyCI(>{2I>929iqFrvT3?DG zDr`z!dpnN6?$ZfSo`9~G`OE#<`{UoakgkZb7J67-Wkt(C4n|4o;hdJz)$Dp#84!3c ze%;!aBwQteXA;OtbV#fOwK$>4PK+gl5)Z8Mc58DLgQkx0DTCAecy>|C$GFB#&Jotw zY!V4LB^>KK9FQ3E?yHSjozdEQ9b0kBKKKZ^R#Lq8#`N>A8}@9udt+-cd{sxG6~q(v zx$C%wr89W&E@8yLRq`V)E`cHpB2!m$lt!zxpncv@Wff{cE%&tw45ys3Pn@5;yh;tz zsLJf?NvgOE!x6suxa87ujpJG;cz;sL_n}!UZJE~aA%36BL;vR_!fs=i-Iy(N7t$Yh zy>QF2Vza$Kidg?o+XMMhK5J9&A#>~HuR7bh1p9I^(dcFT6k#d}qkdU0)v+EAqPc4o zZf-=53WZIczH00xtFL}4rir3n&^)+`^NskK#FpVN{&m!8l|hAWU&_0K?bf3dM77GTZCY%U6_*-_1x%*jQ zxfzk!Q;{xFxpQ|;>gE@S*o&3?Ym4KY)?uV78g zrxLl9Sy`FWeUm&r37>59_zF<2%2WWo&3wobr3lp z*jbPum3;Ge0$>=!ZbnOKATm^0LABIzc8Nwz#jgyW>v#Du%rFU3Jqz}SxFWzfy5nZt zg!C}ma&{X^Nw9UD9#Ok#Fcy6CqZ7?Tbl2-Y$j9p~2ZB9Nj9s1ZEM*64wrb_Y$J*Aj zl(7&~6gMf+VP3%VRp$ikCGzg{D;saMnT#(k^jERf(T5fPW^7AV{-eU6Fh`L(YxZ6n z1F#d-?_gYN`p6#4!G#1L$@Qv5q+%*n_wL=32LfgP^5V@*H_~Dfyzc}A4-HgpBouY= z6QX(ilh4)vE86eX7d_H`bbhYxK7m>m_^vhiiht2@KIAi^Hol76Rv_YAaw;tv2dgUc z)o4t%shAxbTIn&8v!y1_cR3HJQ_>eMI8n2qrwbGq)LjqO1P5#M{9tTUkRg@FV6O}= zdX3+^^;6Qg{$%U;UQq2Y(*!kog!;r#GwHDK=dS0QG&pRMViUnvFNEKu$h5tJmM$3# zLNU+z%Mp;`R!0;mxML^z>1RuiU9}(ov$cZPW~fncMoTOf0(#^YwjkF->AdE6g&t+? zHv?er8h#859*WcJPeI?3jZxv2W~h~=Dq0Q`%vxO!g&zz@Qt}|7vtbj}6+qAPvF)Jy z1lrrZ=H+gd(Gc*GG&;J0TLBg+tFw=%KXv!N3f*`ocw+>KTI6}qlW=o^TXP4TLp~7L+1j~7NG#D zI*IsvXc3#))^h!-dtM#;M5|LhsqK{Rez6D?JcXGqpM~v#{<1mrZ+}@Aa^<$F(L`~* z8^`CiybM94X&>Sk6?}9=(P9h5(rjN&$oM>0uJzrCS&=#3It0@&wfNXmH+;|Fl~yg8 zEhMw~0T@}&tcujZ{O8E}R?&I1hP>85 z+nn-;LjH!OP)!twFr?W-j^MNCi$e)NF4;#=7R~e~{4S8ApQe${R6Lz)7B?44<(byQ zxr-i14hT{)QZgG0wnW zf?>HchM=H%b|vQPQanE{2cw`Z#RvW#Xbz9iu3%02B7O}GUGsh$nmJ?$E_E5KqfWhI z6*m88(ekVL?|Pr){QhGP(`r?D{GCRJOm>s}Ws)v2{PaKhx=&q?GqgTvgF5G5X>^*e znqV58Ix@mv--Gygc&9TU^0CNOqU2xtFhVuLsDnvT84jgnSMOi~TcsC|7i&&2Nlfhd zQY8lY3}J{D{AF>}3KNcnACUe_JqSAavD?)N73_ddwG$_2*Of6L3?4FmrK*newO z#X(unZ5A*)iG(li*5jYvpM2!G-WdbHb0eam)*!PtBFQPh=8WPZ`EN<&k!)ONmpf@< z>XN{sl(i2r+ns#od4ytG<%uV0C>TSMHg$H(wfm}|qYSPUqc}8@786#_%FJG3egjl8 z6QHWq0?rwk8`kExwUW$PA(+Th*y%0w6!keytU@x5ErltYU+#z^3I&=Ys4ii&F4)W8 z^r^LS(%EA*yM9BA40*Lrn-#nmGeLSnK~L`Y@j0n!q5c+ef+O@Ff}!1XOZQp?alRY% zJWuN@{vp!>9#uh%Zk<{X@;T ziKa>CG<-80TUDBt0k(r@Q!`e?pDG{wy@oFvgh-Q$VJ(5DWGRqz2% zfAv{Ui$fP$Ea$@29gR&b)c+NJKHL0P__^H;fZ8 z+imU64?O>5;91*dfo4Cci%1Uo~zC1PTyliszB zc;N6FK{$W9X|ACyIEQ40iZ_KB+7lS228p7isuz)7AApN+WteOG*@W z9?QLQF_l&uI$SJ3(=yWt?o3r!v9OZFvpRXBsp%;;4rH|1*~6XAH2dAg4Kmnj58u$E z-jT3-c-vHuAY+mrigB82EzQxYD#OcjEL!H)TmUu0BU9*$lmU-b=tya<_-D&!vg%hn z8!G!-`J=Ac5E_pk$bmdU6}(OWiivmiw<{OA`@!s@U3=j>En!1(UpP2t1#vejmS*~s zOVSjnv3E|w=BTBOIKl|__9;o@G`>W$5yq^17KF{^_l^R<2yoyADJGyN93?=NT=G{Z z?{G2(>?m~tDb;N6vwGdm8o?=itWTPYFXUEQt%cCg+JH;%kl)whhjk+6F64a$Rm zYW|FW8hX=St)3)A)Ei>^AYp4g>(MUt!1|~H_YBz5>$bM+RoK0lszei5$E0;7*&~vJ zFcsb{?Yo@tE$Qj$QLm&kH|$E{U&$1}hHB7gw%NN$jtB@gWDZ{Y9?K|HD0~-Cd2}CZ zw)f+$L4(YxaO>XrL@;IUP?%u;>GY?LM}CkCG~w`ZUb)KH2PcO@A+W0q47w)$UxKdP z6ntROMR8>`+ryqc)ukvy;;W?aYe$qnMKm&uI#r(aerF6KNLQ9-zDqlOI7O`RA^ zwt`HS(g*SSW{I9*IC`7tAaq;q`P^w`zfxns(<1npMRU*yii9AZldg11XK^tldEVT8 zC*k2xfDHdSc?x{uiq3{kMQbuoBZG1hQnPNC<>ZRJf_QC zO5lb54hM2;R(W5EH9tp+&mfn}e;s>+{{GvOga9_Nik97SIsf$xx!|AVh_55s!t4yr z=;0~5U=I&qW%Ie2f(u3}W#D$rD?a-MxxQQrS|g=k9T>yUPwNTipYmi%2vB{}={-Yg z{lu~$@I@R&ni3%FI=97B%UPrQBqc-rLAezoiuWc&wbl=hbRTB4cYF^~8CZ5z%d}s>dJ30#Qet_}v z>TiWmDV+t`?j5i-F~B}OTQG3m)?_>NVxCaDv0$@Lp8gh?G3So|s+W5JhB5U7YhOf6 z*KF$_Eodlhr?%AL91`I_lDiR45?gyhGzLNfNc~#LLmV0TtxJ%Uj#uQxQIfOrsmP8y z{Iq6uD;vy|>lJ)hZO~*Sqgb0h+1aJ`?~BH8Vg4WT&N{B?y?_751dCJ==`jSP6cijO z&FD}>K@b=qQo;nJB@~bzAxJ5u9t9*E(t^}T38i~aSSmkeCza#5-0-CR4pDU)++M~!Ivmxzo)i+i zUgCPn+sSi4^(jBSex(MlIn5X&qEgej7Nps>B7MMz^437lB-ld=TfqKc0`YZ+ho2hM z6);x0wnNULs5#U24iDXpl!%iH?c=t>`X1{AvOa-O4APyr1Q@71r5voJPpEP~r&$4R z)L!%q_#W%YvO^2V_R|Ta4r;^1HhONwh=pdA z2H`MN33G=JF&fG>Dg;aWB6(G!s=#71^AmF)_^}EYap-sn4raTb=42Atw)dj0IKs)8 zpu@+v&37^zZj(*s4=A8VxM-i;jp{)jFJLBt*72`ONrj4|Mu+=TGkbe7a9qLn_+6Xp)ZY$-)1J92B9xT&esX_!jK2QekYbw*E!&;8*x=F zulyzkPffmiOVE-Z`V)u{vj6qEDUL#@$aKZpbQk+x7cOJxpb!%7*NOMojXO7?9m0hv^}eQ`A$| z?s8mPFxE*gABtGU)bTIWnOgxrocrpWp9j%6Ku-?1~^wKMhkpZv~og3lcdC%FYf zJM!P0d*AC!bHBDS=Z}tAAGQ#xz06yliSb?|pLdoEJK>u|`?y{%hq+&*oXoaP5bKL$ zZ=UuqPRpQX%X;&`5QJZa!3Ut&W?N`gTF^T^S1xe9Mz$Ws6ud~Nb}n{J1P+@s3xgGP z#+~)uX>uec6^AL(w5*Cd^)wXBG&=#KnXFZH>u9p3Yg;6aT-+X;EQ-S@JtPa;~VEL z2cx_qY^p zdDl&gZ20_oPL_bq$&%Zg$D8PHU=l$Lm(ow8-VY37W7A{jWrGeJ;c2{n&Bv{=sCfC= zf?$q@=*ntPz#QX8zEVN`z&iQ)VT(?sn})zFEQc8(!r*y|*X+O8L~M;UsH{b(nK*DG zd%(itI^Z6m+pj9rma^2;f{xpfr4phQ(EQ5>87(1`WOHZh%<{X%N6|4;6J16%mIdWE z?eCd)2`_am3_}}dmKw_o(9&qad&_lo?!~lg`VHq;hlOs^f-2%It^(sI8uDV`edyYz zUvG}Do0WUC><|cdKtc0FZbtIw9DliqM|Ijt+OkiyS;rk;7KPkCwYQeP%JP<*951G> zpqFsQCLjNooqm4a^@{Z*X+{c8Qm^wFD%%xK8-;T=8a(926$%Wg^%-wTYF?t(Vr<(V zS!dE4`cya-77=+jrGPXge?{pf8-I)CQM>(Cm1#EHkE}ca+#{6!eMo?fE-*^YtvB3f zkdxHN5~^eiLdk^4^YpH~N0SKMeH+*sz2-6L)VZ`w`hKP}CAHJR4e3VQBfI*0l#wO| zk;7+&BY4r z4m=x(L8;WS35X_T-@EgcqYl_W-%p6Gjt&lU$-1@^xo~RT4-g;{)61{#vToRC8Rl74 z0rH-AM7I~wy=|havxYg!DB%6kd~e$C7U z7XEuM{_k0WRaDF!G}8$ie_^t38~>-@H~w}~p1Y^_>saNE{f3vFQ#oP0NE3u@!r15f zQ(8P1W0<@VU2VpCVIL1?y4;ii*EtCeyzv<23+3FA+{M{*OP2%VFZCBtY1gfm!4pO) z`>VGu*3*hi#$}~|%B)Z9FL>19?+wn2B20~*+Ew=-y2L;1#EmqL4X%6yXI_ZuJmjOb z2FNreNAZzAWc~;gn`x)%aQKd3SZG&M$MS0!uC+J2sbG0ns7qw3b36H3?>P|aOdL}e zAT#R?V1Z@^=Y}!osZ{%JpYukr`V&__V*^NKVJ$`5=J0)Ath)T~X~eC_VJi2wYpD4? zKyjs~z7}wzyZ&5pe>z9UpqcCym`KK(DFa_!frNhu=biYx(QVSIPERY(^jMxTuWt3q8F% zrXc8;;jqcm04vZ%t?DJ|?pMg1A(!lO*m9D^jJ0OoYE)lnrP8zR+yO~aWY$}YB->g0 zylo#HGK&=9;t+_-gZdA-JXGu{Iz>4iI=8D&COAl?vomu^S+f&KTzO*6R3YWLC+d!= z#S6M%R6-dxa1(~pjUD9+kt^D-H5w$Ekg@Qy$g1<$f11>ZZO zZf5L{?YMXTuTWh(J?Xbl9ToC>s7?+F)v5k%s4kOiL=*Dt?;MoFgtW`jyEWxZei52J z#DY8NIyDoJ)62IaJnN(F23}EhAkQDRaON`O;jP{gz0zdd2kX#8MH2LM^kEiU@_tAQ zh^pd|@VlZeE1xMv!`{D^mkW=&a9|uPvB|G<;Hu8;&=gs4_`+KS9#N?6&q>HTcpb}_ zgMaZT0u|?z@!Hwy){cL(48~6h!}O(-NdJqGs}~04C8YUbXve)I$(NNvKTfGZCu7h4 zZ#W%gFT40cOa#MnTWRlmfr}@W+ng~+MdtQjI|uZ5DV{NrqAADY;Eaz_Q^Y8u0%(TK zUGHkt=r=J)48hTRSz9BAoLAEd7If295^B0|N>05W7{`w9Q9mLT7tdH;I^2^%Kkv-X zx~5=*xjq~_nV%>E*bBoyjzEi#b&T>!5KnZzoH@WGf;oRoiL2^pNY0z>)mtwDG9Nl? z!Mr|f8voDfH^NvLpx?y6(LIu6>?8X+L4r7VAvy=%dHjG=!$Dj6Bi_68n-t^4vcVK* zJ?Xo>{U9L7+_zbIQWGhUXK|)uo@nZj!7LSQTffZB$_iGG zb*|AiV%~e31C>&2S)9FwfHp>NUzvdt0Z^Y?2o*_KlX>wU(2D#7IEjVz7*0_xZS7BHy)8`!qS}S^!@+e< zUZbP_!KcZ*fT^g!<6x2qfO~X>;et%>E}T|z_@i3)&%J72N>EKH$XZ?~Ar=9DLe}-i z%8%2W*qZr|vz#RhqT%njHHec&pEC}C;nKcA5_vKv=_|VC+lWVe*{2lVUV6+-^YoZ! zam!R<+vBiOz##diCqQG>oWgu8VRYL~mxWyRf}CGp2`sx4AOG&02QU-gCO#k?ST7B* zK;LKDbYhI(w`dQ&Hj5XjD?TAhsCuw=aW^tn9iD>(3;tvruoML`%pS3qSREaGoTrAb z9eufd%?kvA!8E0IW41~X?sJ<-0`Bz?QPTAZP;1!q2|~=*rVau~W_O^|hHOusUq>zN zlU%yrWseaY`La899LCWwDlXDq` z%VwK@TqqQk1M7V@5whnkcPso==*defKi#b;qjX7L@pJk*YKoWLb+$PR4aLCV1RlUH zrY2e1UZu-lN$~(||AJ@y%P%{R$$()h=PMluyr6dH)VU+bxT8`~t`Az6lO^0^ad74( zgGQe)ER=+6h9>&~JW>P7(pOJdjgB6y@8vK<2+DPk!fXBI@1eAMvARGqqL>%!$UAK!bjYv*akv;237PsuE} z;yc=+$6a5){0O`<$6`yZo2!MCBsI|D8y#1RT7|)o6y~Pkx>M<=B;2UrKo5g;i=5%( zSF>3pC5eg!7S8}}taWbJP0ijmbAH4Uh>S$KqaGWvkS;Le*Mb-5D3FNbu_2bSrB08T z#-G}n1Fbiuo7(NBA)l)8^*xnm0CtleU^k`Qzydxciv#ny!kfcl#%(oZ3#W$DGi{TNiH43phBqrH}ScmML_NqfTGF&b9^p%?XYnpbwOHbxX!+(;Z8nSdDlJG zo_UMWp@m*!iG%KpIAX4$FlM*LxcBY-QhN3?sppuZ&PR+K5OY_;pQN5*kX!F~1NR;q zxvTBn?$&p!e(7{HX>_;KGWq%XJBHU~ujO20HOR97T+W9S!q|52&FgGQBH3_R%EA=Z zlP_YZws1_%-OYx9^hZwWCS;|N?IgIkO^J`#TE1|eo@1aH6n|vmWm`%Mw3E6{Y;<15 z8Az~fAcfc)g z^x7)2Chdkn8W{D~#-pw30F(jIzyd;xzfxmTQWI-ZDQ!ir^vYd0y2qFXCGb`ZPK`4T zoaxAFXnjPyv}KTwttSpKv)41HwB8>(G%TbH>8w2u$T$~BYZxt(M_1CB z@j9lszmu^5zK}#@iAs#2N;EVq*yigYk~n-C?LRMPeEikoyi1T!V>j4h-Y<;esVd63 zX|?lUqsRhmoL8PKdiS|_iSaj`1?E}VB%#F&CR}4&q>z>RSU*VNEbuQxENm{{99q@_ zmovXyR-9DK&wSe84r-RQI3oHCM755i%!3! z!p#WDt5vkMEsOSJ{=6(X1Zu?vtO_^Wg;HEcE>Uq7R7Ag++1iR9u6JU%Fc=y-oeUzL z&fYHdhRBnbFcl7H;g22*_e1P) z5{TbyW#FD45}{mkZ4NQ6mU>Oj*jX_rhfxs!4~D!IKbE1}!ulGVFXO}UaHDxuy7Q*9 z7JiDfcWx({ONY&80|vEy{7nUiyKE2p<|DZ~Eg_0X<6!`B1n?yv;%|m8=Mw2)m#GrN zXrX(R2868C-w$~{uo^kTK9||4Coj>4=p#nzcfN609>7OLf8wwV?s8a^f8ekvUVw1N zc?mVR)eP476hP$d%qG&?xQ9`^@+5O;Qz5PIW7li9P17ic{_xg)z_JP*{9(8r5YOJ; zV&FCqJBg1Qy)Z?bKf_B;ZkFVztB?tgdTGVzw0xCvHRj>6l^0}VnE$_=*7eIFiAW32A^T}=eUOZbxs=_uR2?f^q4pa-)h#WmwBdQ zQ_tM(Av`Q$)K0I>G$r9F>%F3#oe*KqCNSyz`}>OFb*h9Q`Uw>pd#!m8h!O1iq^5=KxR!_pylsG+rv1((FGD%REGTXh2|^Jq&i-*)XtIQTE9tdl=LSdAVTB1GdaQyWKDzz=a{N-@2J_4f1zY<>KWJ zL;fy*)7y8%jeQD?mMV#aEW)_(A1Ro;fE6`@iKPYE@GS6dzQKPNX8LWM1F0qtM)mq5 zSRg|)jtE#GvFwMFuC(dsejn%#9{Nvlrl$7@PjMKkuOc5rv#5s<%sAmC58JVZ(!{s7 z2b1dfg}VnJjHb0+4<-NV0|zkCcMBWGSCizHpyy@Jf5ekIp2Fj1l zvc=Zi8TPUl;72b@e8Z25VE_0LKWf$cE?O>YYcC@>*VzGiO>Hm42mu1XS4PN&w5v>T z^}REO2Z@MGcCL-dkF^ob_WvuRny|ybkFZhK`F8n!TMCb`ed^#IBy`K$#HqDj5v&^= zT0MVk{)@x$(<+XPtTBxHO^Kz+jJPn6Fu-~EQWOCOavFkxP(Uyc@&{xOkw3vePOv{@ zh~6C(z;}QdVRvRz%l=uIgXB6UDRy~YvB#kEB8!LcbBb@3ug;Apa-aM>q} zl_zG{tdcgd537tC`wPSAkK)~2-Sbs{dr2c*Pmr0u-kSPvCbPZJo!OnIeg=s$A-Rw4 z3T1!)70?|TXc=oh17Ktc8D3PDP~M$$%Y8#xmD!>8O zW@o>P_FM``!KRg5$iX6-==cC&;f;p>@g1-g0Dh|f89h|`ejL{}L9hC;)#QlU>3?W7 zp@v#b6fg=37`Hm?fssDQQK8{9n&Z^qwmVm8e*)zJH%*{CJzpBLR8oE=s^W}%;C2a9 z7J7v~%esc2`$>MyI8AijnHNP4og{q0iWzV4`mSEW3=~GvFm~?*zNRU;`zq(9Ze|5q z7}!*23k{~5nIEj?kj8EIr1Uy@n@CrZ9NS1x1U{^jp0RdrC;ne}pU*+_ z$XDJc*Z;72B;h^FH$Pdk%(-AZh==k*)-N5Ka#+^D7a>~QrA@z=ReQ4L>z(R6q!sF} z`la*fIWGmY+Wj!_3}9WB>T-s`I&TpkRb$8Cy7?-fw4)awH1t}AKa@xsL+vb3Cj?}f z&h`fHKi~p}0H~Jcrb}GRO?mnYn$2Gthram!3rBN7+UDPOc7&dEmwPKrR#7IpMl&2S~ar&oAY z9Q$PWs~ye@@>fiV4Z1KMQ>vS$O%%pZOP?+LP-Z;)DMw69<%rWo$_<|t|B zENo-8$qIsID`jp-DR`#g zsmU<6O-jyvCvq7>p0j^`%$KWS+_sYl(-K0I@Gk7dfmc1#a+E{sDJL~FoT^#_Z<7IO zMwbX)^931w>!?q9xU|%nUyCwmprQ;ds3?Q&cSRYrV%DV&JlLloSS;1ZS{lA#7K3nX z6PZy$Y{)*-`1mnN?ryoq!mDu84t8(WzP892APLxl$_$Ku?Lzy9@S}J!1ZtFUwKg!Sp?);4YjLN?-u%H@R9QXe-Vt{u`ZailY>k?grEzIao6;wE33&ySu*i> zd9{N)Tuz14EMF_3HDFXNu!54m+++PZQd#{B>1Xm#kaDRid;UnKT2qQ$ML~MWHSVTD zl+!^n9r|XY&Qy@z5V*mF(=AL*q$Gbn>Au@w5yr8+n5@xDg&6HWa-X3sooBqOtxF;*?DRN)_jA?Bj1!Ew+@Ju5 z*wKlc(uTGy0-3XV<*NGf0i9({O*QaQD7^BinM=;C?JwJ0D5{B`?c*&Q;>P63*lY%e zO(eQuU(oxIJ~5qph>(LF!KS?|7ZSceI-oFe8-$}+>PowbICV1pi*>`+a4&Y`+XtH) zS^;E%33+CmrYg=QTs{ zHR1~?*Df*=%rZ$|$Smp{iiR%EOwGKo)Zn=vKQ;G235}p}7sXMs|AmbFM|BH>&-J6< zvjX@H6xT`etQ%NyF>i2GXZGIe^p(*@$~e9zZRy~jZ*va818x7_dfE}%kLv-^$bEBM z8p@f(8>iC#S=5sKy{HuqL@n`k^`A7Y=a8m_V1RXH?B(7eJ$gn-ID~k~+;RADkU^q( zG6hoR8Tt@Ed56ogypC&|;SsmNo|Y?Zh6_ku;9}J&nU%#*7ACE%VkgufpF_D$D2m%@ zE)FwNF(hDFoewxR33m`Izz!Ck3$Wn2y0mJyVda{bUJ&cB^ok0d#WlaEgD5PkXiPDj zHA=KF)OMx4ich6|b+G(M@M&&oo4wK>Sa2|^f1QdVi*=ZoewMf*pJc0HgGm*<$kq=Y zy#6Rml~u%3uypFNLxS4_K||P$y6wG{g+`qu*yrZ@!i)MF!W=kqXie~%Rl~F_*5`c= zLzxYmh+@Ghul}E9nP>u z`Mgo88}+SJS}S_oSM-rVdsX(sS`gP8c&8!kUD=b+TB-&qD~8HJ{}Hx@)`L2PofQ*q zD)#2^aOVs^m98C&dbN@ReqVEh1_t3Pwz63>+{8h(%T0WG{1E)3P_7t2OK0bsp3vLb z5F5@L%2|!G6u)sG7aFHTi#Du1XHZUCN*vu8-7B9Gp)I3#DB-j3YXas!CoJxiyXM2x z(k;$K!K!Pv-yfZt@S~aZu_T&`=gIm^5_S_g4N`&}xS}N_Gt-ji);W@ugXPaJSO}K} zMpu1v5rSGOnr^(G?CR@Ny=KlyQymBDn4Nypw zLL-fK0gc>D0LiZ}%C0SW2&M=lWBLWAnC$-pOi^{e+7}pt z9}EEr4??L~IhALh>$@DvWIK->W~baktQHB^ey?9A&+Ib$B+uozc8EFwSpU`Z1~i>r zl}1_t*m97`ve1~YI~qWiw*>e?;wg9H^BaV(0~SH(od-%2@a?k1Ou(RircvO7;cKti zk|3RXcCrpJQGcmE<-ZrTc5jX4K#;JMhtElvl!R6!W$iJMlNvs-%iu-`($!ZFA(%!8 ze}!y#U)8GJ(F&yBi$(r!%KDkH2P_M2!nqs`$h6H zhKHU9ndMir2snt=?J8d~tCtmiZgDW-O-y{)!1SDuq6c&4ZovPm{)OsC%iaDZ;J<4} zJVN=AF5nCvg3>r=}bz zC#LK-%1_6x0$bwai- zzz{>d10~`=^bREC{M9?KW{}`srT*LAfi`HCVMA%s)ClI~6$*x=TYy+JcVu5iooJ&L z?cqB)nS4&@sukU>I~^TDAkACkFkuH`Uuq{uTnSIC1Gnn$vqlnOUc2d%z6*pLJ0V>{ z6OyQmno)I01f`Fx-)(OmSDaqbtIfS*!nqW;rKR#ov@~{8bxP6Z8aU)LD*$<<2}bhe z;!o2+u6xy)!Q>##5iKomqn3%&(bu0?oMN5G{k`!m5MhuMGHYQF3|pB)#D`XbxnYtx*rl%Y>&Jw04E zNihBznZ@*?v2AYIB zri)I2Q4GqKpPi~Ax5bCv=GvvpW0bzr<*jnO7wWHsCW z`fF<)afF+>G@~#=s5RY1bskI{enSf>V1c$dN~0Q66q8aj2*T9o(Zoy;W7MkCj4zccr04bJCZWC zTbChaYx1hsgO#(ysAyr6>dBh1oa$EP?!XezKQ-(bjv5~Tsx8RHJH#;}d)}F(=iZsK z9d-jz9^tiGJ;|lko_3=FU)^-&Ks<;D?U};h-;#}`zx*?0a{{=yLXQ0G;zF~>{Bq8n z{7iZYo!iw;WJI&;;}X{F#e(|U5T@#`GU?+_Y#8N82X~LUErg@4E_BpABw1S^*r;Ul zVvXV!h<5u_P=xJUnPTYdO3Y`KnZBr6|E>r;LQ!wI*th9fTq-NN*3uqw7e>?*>ffl~ z+jVhm+G;}#o*FrZmz)9G{`gae#&|_Fs+}6brN@9?8si|8~nHmT|3 z0dpQC7Sy%8Ed-r{#YH^g`!6z-FwOgYf`zw)L~$Y|ZroSCLxLQ(5wI;g5b_S=f0VC* zUAsz%#p>dZ?Plqh-hf&N_yNYZZWX4?xbibNvWY3ra}&H`ml)!j5oNT*kv!mygMMUz zOn*C7(5i`9vw2s^gDaT9;1QRg7`AW_V3N#MPYizyx)$ZVy=amJS{dp9*%8J&V79Z` z%zI6B#${IRnkJQQ*3o|H2#frj?O*#)eX#g26g-kxz?8hD zBZ&l!9%d&wZ&ce&Hcw38$C`)Z zq!37PpbNEoKC7V^AGibsVQQ?3>beFvX9Ab+Z&3e61SVd3YV%?v`I?Re_xnY8dw*aH zAg?(h8FM1M@$#EK)iXx!wn)TfiAlk#Loc*vFGU5-@3$|NZ-v-g}%Blpe)tZN$PPf zj6P*@+6`#ve`UkzZfe@qcZ2erJHr>5yDOZg23@wovU)f=$h0>^DXto)og;k}DpX`T z!5=ETAxgev;v)3u;}o3^dD79^LtcTI0AND6DvZcscz_RbW6$MS*@2jmBtsB?bH*sH z0fJ`Lp8;@_canx^KhR#2_pNqRnhlxKd)4Umt9)MI$xw-I?R6AT#Td@*9~5>nacqH# zIHFN!hrWnyIUm~_@b-NvuwyP%lv~3DF5c>N2 z6RKxOsh&$|{uDBIc)Dj}Y=U7if*dEE!heb4BXq{@tYFUnmehF&C+{Zd5YxN#nvRWG z7hlu?j@V&b{}R**rTTir_R?xcjG%pD42B=M@X9Up^LOm+<-nR0S=hXM+0lRnoLkXG zssW|2+$ML6+k**Kl~dBvd_-`KC)B zMzbc*OQBF*=0YlRXByPAs-Msj1AT_fXIm^5KR*UNgJ=g@Un7R32b=u~HIT_1)@ZaN z>a!^-{&b(=?EBIAhi5t{7uLmW*RylBA;iKOn5zl|2aRc}L78^l3>`AX5=<+{Luk{- ztT=HAiF^`wE<-7lOAIGPy}gD(3K^`SwJ;%FcQMYBt8>&W)Z#@@O>i0hxNV$c$P@U= z5~(UtLP^p&lr)CC1#B1}haKtoL)@r<|IgYXWVNWSL+MXXIamq)-TZG z%EN5hITe6h44i9O=BcykSt8Et*523ArrHHtFq=8+FU_?|Af7rbU_TtvdzrY z0$u4c-@*>E4TWHj7GkQ-=`276YtYwi2I&IfxE{zQ2cTql zOc*Mz1{X^6-q)X%?>G!6Ty(Tu>Wc5?dPlBW!0{uNHwF)#=vtI`EO`ofM#+Q5uxi6- zqquPKtiUHDBquw0gUAl=1ldKqg-7wu>*+oBL(FZ0aC<~|1`Z?{-rvv{hN_lz?ze~I zg+gEGyo58)(+a8NJQIt1AYO0%vNcTjc3htIoQE{z5Hg7Z(*R_^d2$>YXfQhawU8np zDdu3oy1yY>X?GSXS$q3^3J`wrGx-1(F3pZRu&D)7Yzv8p!6982;v_^KgH~>{K0qXi z{0Wrr#|Yzq?6_dOg8sw(hx<7S@?nOHoNv;@4yeiBlg?90uzH{uNLfl$3~OtryXcAg zd5QpHR0?xb9Z%Px{zOo3caZd#0ff9XoKm#bXMHv8PvVl8TbAc!38R5bjM?8yPTaIV z&sNZQ2TewQ4NAWirP!nz@=yF5QEKSIe?gQw_W!*o^%+}T!lC@t;}>1Y4{Pc+IN@pz-gj?O28wfzA4BxuR znEggT6# zlTI|;*V&UU+2^#n3~B;`c80EZ2;aZS;50wx@vqslScgx)RpdG*fAlE&(BvBbbal$>dB7yf0R87vWWPn>=_@rcAEzlu3I!?JyKx%U)JMN;pqBX zJ@ac>F4uBJPJ*j0ng%%!+R7%YYgX||h-qTJ`K9~??nUH0#M$vQQOS%cYd|+$1l)f@ zZ$0+KblhsI%N7rMYCbx|#LB5`s&fV$vm~lGn~?F-H(N|-=Qb;4U)Of8hzWyC{%{o= zIf>NL!n+BwOXF#i-B4k`po*A%D+4v%#StN1Zyt8rty@1?BJvR1lM7;kfAGb)z z#~p(5adJ>TZj_LZLoDRslDxfN5tTaF4o0McWj}@5qVL_I?b9ZK^maQ$gnZnqe*Nu+ zT!WIelIjanVUiTi=Cvz2m|8*opa3)_FM}WJYJ%lZFVJ-CFPUV-iPY2O*C-WSThRmd z_AVLV{5x(sD^0hdSlO@X^6}1he;dd5_0XFlKe9t%CTW0-!(k&Ik3gt zR%F!5soh`WG~|i#VKqwlvb@UaFO~$S%wS3wH-nHG(?&BOh?IN31eP$R8OEea;){n) z^7B%Zx=s@}QQ^GlAv3lkp4NiMGl#7bGb1Jik2Ug; zkHBo?&WCU3_>iucN`QnYndwgq+9`Ji3}*hON4FRDXg>qM4)=S^zZWk3GlR4eo~-pp zB+hv$Wju4$#Ds?7ByG!*N^-b(tzSM5$vmM}-K?%w7!Trj!$;0~)EMTO=drHKc~SoYqLoxT27W>ViIfR8YTX&4gHZ*h5x<(Yc&^Rln2Gn2fDJ?j#O zyyV&?!k&;W^-cLoiIx~Stj|G{hLrq1miEIr4{1n|G|!F&+if?=Kw9ON0E_GT+EZ$l zsL`Bo{AWF9B)mg_KOtLF4)Nv1&bkN9P1lhvx7mJ5xOW#a&}V)9`rF&D)*0G&6+3qDdI&y9ELIcaD@D094+b!;q}xxYaOLj?V)k)p6*=25$^k@YPQsbHH`$ACM2KHI#g5N8# z-{OU;yYWJ0LcEY5#fI$03*FMV%PD=&HKdrNtxlh~d^QBH6LdW>mM4GXu+trX5$vqM zw2~4yn8m~QvFiaHR{AF$)^86W+o}OEFg}<~xdNXB!pBmkP#+bEucgVDN@*bH)Ab@DgQcy-s` zrVHhkP6lmSz+N>Yc{63>&Y4oQATNqLlgFy2vm!dB%*eGX<^d&zuc2_3*J4fH2E4%U z(|Pnt3fbRhxYpyqvm|keBR|ny3z?!=y?2755O%FS?W6E;Cja;C)~+d5CxMV~96o&a zl6rlZe)f0JY3H1a+DIeQxjz{$T8f)i>~crFAyyXXUi}&Rnv5CLZB%=U^o= zJEoNk_b#L_f7+=^$>RP(?8@~DqN9}!8jPooI5g{W3xFr80-Fv2fSXA7vP6+a0Grmp zrR(ui_wNv4XvdfE_46;{o!m1rcr5Gjo>^qbdTVgZ`*;*n+dW&HleuFC4tE!bbt!w% z7@WTJ-sX?gR&>?Qk$kLVKcQ=W%2wLg;M=XP!N=bnJEI}x(9ZDWxr>XnCtc&zvn_P_ ziw^fp4qPlb;4Zf3h?>HG;Vw!yVM9nTX!CM8EhM&4sp!5}@niCSg-1%VY*HA&m8r4{#BZ_H?x-b&z~|>6KbMDPxN!X5^U8vA#p}VSXoy6hn3b-tHU*vjnBY z{@{2xb4rMu0Q+e~tMu9CD1T~N+L$T5WRBEbW8}{}iUbFTp?fe6ozpo_wyfTpvRHsU z_1+K`8~a?i!s0`Yy-8thFRz?0)fv3T;1EW;BryiXGZ`V~JBVj)21);OJah9yC99E{ z&*2g2DCR2A_lnPY*hBTCCfI;G8qFSmVPD)J0D}wN-FGZEKk3{DkjLs%cW9(19YZ8# z3eUA<;T^Ny-1>cI;Jbg3xi1l9?hoH(ZjLI6us`I;RHDdW5B@32`Blfg(IJ?pcvb`^ z#aK)FEo{i*3y)$bCxbzXb5_uIS(dN_DVuB_5-q*^pi*;<%!46!bTV-M65kI%nGnTC z3}Ss$)6PKk2h7JRz7pzV5H+>n4dV2AeoAUHAYg? z#Y44u-UJQ`J@i<=Zur2OkIdjCVE!pVHINQPyF&VJjm@JMkF!Yr#{a9K4T`^h;oYcv z1FuA!3SqH{Qv1Cayg<+Ne_-wfOjPigAZLQlVdkobmO=o+dOhqQ=6@GUQwp#&ZwUBC zJJ3KVMX(S63-xF0HtEAsAS>1V6jn!6e4 z=V(H+d@z2uS>Bqtb<%IQS-zWGJ0e5Wl$>bdv=%+Fp+Wp+z{;}I;LushAMr0>)s;Vy zd2A3RQb885;^;z3lB{H8d(6WBp*5bqle2M(csB6n(B`q;gV5>~a#4WNkIh*_m8U@a zeLgn%81-(hM6MGs9;CXX_PwY}n=cHL1buBln;A-YUYJ!=Mk&a2l4Cc9SF7>&_S5X& z*-urz+fVtw+fR#Ju7T5|_K5A}YKvVqqGP7dAFQ!MExsw&gbZ&Lw%mP0JN(Ho+C`0o zwQAXR?H!0*V$OfwAbsEH1lyPb`N`GJ?aYd6pu~^vqw>horTZ`A7T%(+5uR1hD(wI{ngx0t z#9mK9?TZ|ig@9DeMghkex=+4(c@Mq@_}?9Qj#)XG9b60LZ~uZO%CrNfM>)zqoSrWHk-r$6oztB|Qk)k@)w1mMlE)@yUQ8ce_@r7LBka(ms6=O-uz3~8 zGl|Ep8|PN~QnD(7I`1;U+hWdh38cOJ*wkXT58cbEE|L-OmF1=A5`1u{Sm%X{4E#hS z^q5KLx`M$=05u?WtyHJrRtRF-@4{ix{U`Nux(b%R5R{M2+jpF8yodA4E z(}j-Q<4RHalRNX1y|o-x1s0a~%AEQy*3^QUApV^-{X%GwR`;&Pia$tf%|A*o+G*?C zsx6GG<+`q%n<$v;OcokcMEM|s1b5-UohlI+uz|8dE;r1x=X$4i1t{gXWbkKC%sod7fh>`VTO$_>ixJC(A-V9yl%>3hP~`7ZODW_jLD8(d?`Mb#ZJ)JF0J zX2$_GTu!>J-VVnTI8{?TDYpN}hmgd|F2{uQ21P|>BhUczHFf(jDMk^b&S=rOpA|TA z@sFh;mt|h0h|jZO=5O)7`AC+IXsqSPS{{PsXMPLO?*^RG6kdQNSQrLr#}EVr~+y?>c`#zmRA;^7ktxvfV}iCRGYD3re6Nq5v2jKu>$8 z`XkUiwE&njyr34VJK1~ue{m;?fwo*EY((mDD%;0w3+(6D6# zhsJ%#8V!2|0qN^u&D*f@No!3z*?k!p#i+0YUs#470J@9vO-V}ift0U=%ZDq`Esx%8 zI}RM`aIUHHW;uK0ZxNTL1=e2vTu=V<^b^di8`i$F-Qj5&TSV!yRbt3tBbet}2xmap zlwG;ndM>_dBUvnh=&-qM-Ja;sNl!Fc(qUyuL&^|S zTpEG6$bB5i+t>$4sXn85m|39FH^(VpEI`kQ3)o4CxD$e~jG2ZLRqPnSRJho>7YFX) z;uGm>_ASC&tyEpavqCdFA7g}V&#Gb&Ux3L%0p6}%(acj{e1A`+iFkrg}fr!Y70v*I>_KiHRS|&%DuKYHS?aHGMJL?~%!4;lWgI%4CEi`{}_&nHQRT8Rc z^-qFqe3Gn;b7yF=qTu1ii#B06_hE9!%0Pw}H)~;_wpD$f7qQpc92g1>44Rajs!+U| zmyvS=2SqmS9f{K^4H9cKeqJX*g1!}aPQA`9Y|O1}PsqWzU~#E<;FwU;rhdGRP=B@Mn2z z6c9Z`XDdYF0>$HLy~~H`(1>s!A-%J58vPZOb2wy28K(q<9+lYNxXr;ry-sw{XFPAj zW4>@haiL!m6-H|S!@6sU(@|54XYNa^t+GSc=vUWkc_UkI-E8WWGs{wGsba@*)!#oA z?V`ExJntZ13Y!hdE{qgJ>cAypw=rE|K9?v1RIX#xAz9nc`Ik|UxCL-){183>OLfHZ zkEC7O>HGs$6=!wqqnCGe!w?>|0rdFpaogj`mv`;JUc?@2=UE|&JBUmkCvo^7<%KBm zeze3=l?;jtzs^h`-X{9XVOyeBv}-CJl&L=%bS5rIC$_Vbt-HkwFq(wlFM(=f-cBH! zj!60hrtKyx<)Iq^#}R09!1pnWOC2GHF>%%&8a{{yXV2Y-3&iOVA}~YniHfo#j&oB| zQr5?uYT9{3FzJ#>Tw^9PO7NCota6lZ*&*C1NX`Cx`Fqy|oB*EhzxNaSAj<#p2Qx?j zBo6Qg_j@x*>_mdFSbb{i@HIL1mL!J4aAWD^Etu)c{14@?qCis2VfQfcY^TDZC?eP{DSCZX`321Q;3=wAm;;FsZF7#5l_dDrc-^U)aeK5HRQ zmV;mJFCcz0P*49KVED@pA!>eM)ldp!ud|-^lN6tNsF10rJ9x;R4*hqB=<^$AUn^#_ z%Ftxi+5}Mo4-4Qr_}KQjVl8`-3`G{^+HN}!@~57!KjK7APfdWjFs2+gtf|d#ox7(u zJpcQ3{s*89od>CVc`KIyMBP>)a8%KNq*e<7{*45B23#Lx7UT^-$TvsbYe@y}r}r*c zdpqmWGq9=d%d3E~79AwE)dkTRqG>E|m`3Opmje0**dK1>0oG_O#z^3)n{#$`=Lo@# zH6QJ!v3v=z)PZH)WY5-GRAdYA&`-8i)4Vs2imt?Bf(zHDIb!;&wBKu-2SEK^qa=Z$@xfpkI@ zu54hNdYU&BFrPS@rk>#cSfvL1*|Wc%wr{=re%gjcm#I*causuwTF&Owir7=Lh}7AR zp8Fdj3m`}K6x8fZF6S6`U*jYVLnXB=L(Oe@Q(|v=<+=+SX90u+R+0;yBAlJq*KRB< z>gk;WNeW-qebTScQOxx_IHDmpgj+i?OrB9UVG-R3d8U# zxsOhLq046@lIOJmK>&gA*57sx=<&0w98s?uLRT*-aiydz2dBGifMRVR@=ZJl>l8Gl z^xreLgd*RgELii3QYqc8f>2b3sC{0^zmY&IA3QZN|C$-s-AKRUhvY?|fYDzro$(IM zdv1!Z&Q_{683rjY&yF*!tlbFd?8w*qH)7YKzMj%6S3&WUzn zaBjIN;}v$>^y?YDIYk?gx+v7%*;>{z=P622sY#uF5stq`x0Z6$yR)NiCf3zlh^$e% z2Q6pAw6)f^?=~o*qI3UKD(pIcB78x5U_DyXQEdh4BPsyl*$z9YSi{)QtdAjDX}^f2 zIU?p=0XD5?($?!CFL`s??FOEX>)7s)mQzLy8B#n8CBKa2m%fhW12*5r^3w1SfTM<{ za+?DJ7ljQvMUdF9X7x3__Kf_POd^j4{(U$mo7dzhJ*U~WVUq5QQ|h&PdJc~)Tq%*F zHh7;`f2LU{WQ99hKL>~QL!8&z%#|~P+{Fd*6gvGkGE7g-NqM+1H9QA(StHJ}+~{8q z?H&r*;TH~-%|Yy%ZdRP!$T&~Yg%y#-m${%Ih)l)nQ){oAt5yTAXv-^r`C&*syU?-r zhe>Z37qB~%*$=Dm;HY?R*;NPhD7|Z`f|x`T|!K*`t+n{X25Zc zGpFUaFBsL7tjj>PX8nOst(ng_FLznw?Q@LpxW2U|yk;&2((>Q6B~&r0*5_`2J;l5J z_fGMVq++^8^!M@BW#>WF7#?JP8#Mp)|AGwHSPS%*$05(k@(9pF#h?&00|@)$cMRokK0f z6qE*c?vBAIKHa45LVV&rUG4B5z~?{fTU7Ho9HMnTv5Hgeeal&}TU zGlFwonw8+3R|U>_*MRTig6;B64X016X;(MQQUZENlGTk{r$yG}IX0y0!MmUFgqP3! z>gpxi-Ov6bmoffun}yTlSH1WZFuiMik_)S$4Z$)cDn_B6lcA`N9>00o*5E(G47`@GZP$1TO8{*8m>a?l zC7^(TJYs>-M)d5mY_|t(`@ChfMnJ7j@Qd2{cAzWrp58wb`V%N;{w}$ES%ih-#=*TC z6TZ2an0K2XW@InsvgO&s`~O<|&akGst=k|7C;>zeM5G#}N|)Y2=^zTyr1y^W-W3F- zgpQDiNSEFWT|i^#9qCd+6%Yc1p1aZaeeXHXIrskfo^SuilPB5PNmf?YoMVkS$6!Ga z9}y}#{=>d0fln4+Ntl>Sn<{#U+u}Y%X^TJh0-CZgHhIfn8AW89u&(r!Q3&#&1giD7sG6a*H~Wql~&Sl9)&V5$ElSbri0@ z?bW?DZ#MKmFOA1Z0{dM`>rMfrUS!eC@j$;fGV4eppREewdjPl|oGrl8o&LhLSJRf< z$<5!Y6x_|b$hfJGe*K&Kgx^pY8?BhwoezY>6a*Lx4HbSn>S>VDA8fRQ#}0-kmojr? z(OAVXx#vdq=4Njb|2CWeP@k0IKM?Au>E9q!f!YNl0bIj|@q)$qjkA8=5HnlD0sq@1 zm3U&nyIKAp{PQnd)qlrbr=R40ml0!_3H~l4UW#=u4Cb}_iev>WyR6L3!{FuPM>@Ze z7M1X6vWwz}ws7Mr3N_c5 zueY}Y#l=cK5p4e-v75i!EB~}~D$HLHP5)k3Of45Rf&RZ@H{{I!PqCZBYvz9(l}`Yv zQGlZZ2s$mXYkariS=Rx!2WiS*gu4M?Jm5D|up6AA6t|{QZo=8bDM4tPx0gw@Lnj$XWPr?#X+%qM^)xxhIF222lYMMyQN+(7|AG zvKNR605@csX*hLzEiMDVB4g|Z(Z_#8mAb}03IO?ZZ(^<@24-0zW$2^<7k%0m1mJKI z<=w{|PGq->g$MPfA(2nEd=Hn3p56oEulehUq8F2X3$GDwu+d61Feez#rwpM>z64rk zoZntL{=@ZDUSSUVW(Z9xO16`l?iy{P9e3D{PFM0yW~|PU>R$b>@@G%=)S>Q7-R&2( ziGO&Jmla9cUNl(%<;J?1fD!`&fd+QKQ#Zx$q68}Z`=tat_iP8_-V_c1Y>Vj00*3;? zQMl+^dG==Y>iqoQM{`$MQA=ykSEjiibIjgqi%B~9*J7j|9CgykW_zr30~^MLaQU$K zQs9qbPd0X0&hWcMc<~w0;ZN76LZ`SiEhOVDHo!rSn*o9XRPi#Ujhos60Ew6_bUhGx z0h?;$>I>cUFS>JwkfX=V8g8zD@MAAvwE&RK65Ggw@H?SAt>@I}_iZ;C{-@p%N0$^j zMM~aS(HqSeXGg8o0r@cvpd4ULziR_HtW`j7tM6sP^s?NH_gEvx^<@Hi-XCv?c2la2IR{KvKNs^qUJf$<`0*-kc5kG=iN+!W%4pw=4p*;jf2& z31MjAx#hTcTc6DwvGOdJhK0AZ)+X76}Xt&DU;e2;5Rg1B3p5#n(d z+VXp(KA1Ow;>e1w3pm@jjlSp#ytQ6?A=x(m9Sg%5{3mCM39j;{X-V8LxQ)c+BYXZi zYGTt|G7vNOC6}@1p~k_FBy%AunI6wd>I_uHZn1iSKbOY`AH>FKPA+?@XMcX{+X1Su zXmfT7VxqMar)kyvWe|%mr}}d75o^9rS<0xmH3we9nMKm@fwCqw9^}?WxklHXc z@^a+IQLJv}@J7~|#|=LngshC)Tv8M3~7${%M3Smfyz zXPoCtOHRr3Z}+BWnuu_#r`w|B5!xt8>VEn{aPzkGFeZ>WvzS6R@Wfx&NM}jTIF1rK;b4^0;8@^g{j3k1N z3k!!r(+o-#cQw3Q$$6eI#)4+yLj&*T8~XEg1VBX|3nqJ2x?2HEvbR?aG|-{K*lv42 z+Bs2lHKKV&6^lzYHb!b@*BMpC2kr{AmFs?$rsQos31rl64Ot>sGtni2kSo0uX@n|!AZVKu>xd3Umw&kXbhG8|US z_Bijr5BF9)z@6+9eMN@`N`wiMIf0WEy8dw7 zLfU4)cBC}5S6P7Ntqnr$Y`QfRB*g0m6pih}Hks&)T`hZi)|SqS(DKU-M`zk4kvC%s zI`Ck*jw^h-m_cu8C7w4GCp=PF-MWQvczWgp4Mf0Isf-EC7Hav8%Eu*me`=0oT(0WV ze(z?sNuERC#y34u2!8$LUlT-z4*LfSx?_&vMEx1AxuN#ibn|pBv?|w&HdJ?H!dXS@ zvWXIIOj%N{g97lWLwgJ+UET_xITnX|qzTvAri+hnhR7l~nUKC~mr$v4bhcG{t>+l{ z9(b;*x@AxVy6CGuzMv}(^c9$dm|uy|gp0$s2F?0gSFXtvY*fylS$4KJ@|iXmZH>+f zVf{HFs>8KTOtd}yy*#e`1gA926DP=%Ck?hXVK|GmBRwkkpE-aJ{e0!I{Vw>|Pv61I zH+P(=#+_RhXSOyk&td##&$+Q&Yua;|P;#sUyq(7%?Vv2&9&_oPgN=&xQeymNuJ(B= zcG=xK446)+)EE&(AyI7L=~MW9G&aA!*?nageqt`e<-_@9*o5XTq^Yjyb9>0n9Y&mX zEq9;J9Y|7PNZ*3!f(`>M-L_LObE)Uq`gm*63v){u9tVeQ|KX#{`?-sh6s@cf%zK*Q z8%!BJU-x6Q!YecqxB?DNFV6?DM$x>bk?!&e*l$^QKqJKed83SsA{d=;iZmGJ!)F>j zqZ#(!R0z9ahCN*Bab#yGRO{`#lIF-glO`tDE`T9nuB>P#K{>R06kbtt=Ab9JSJt{_ zJrB~Fe?%&>BC4&r6S6i}eW2>=8yy2LWAX%> zlD4i?f7ni7QqAOT`=-iC?}Ex@1pb3JuLbT=!plm@SC=VDw@8$pF;<1M;X5APz_zEg zxUGn%(aWHuga zYA>=jjXMw`y{fu{b<2c%61$a}5SpExX%eU>^(|)FirI~G^2*@3gn-D#*>0i=4tBg3 zZ>+>&iYn=f`Zf{u8C;5y8!F5h3U}Hqah3rRQR7wC|?j8%~>k>lST{AX7$~NZn^1 zr8uWq8<987WO&13xWESQN z8f~sP?HPAak%0K=_YSvdr0Oh-l;Gu3oFYs#RG4azsXE?CcX4#e!}>8oJ+H+^B}_NCsJ-<_`jqwt*RAE2cAzA(#&D8#jq5L<6LWU7+$Xq4L(pFL|iuhcm~O5^kifC0{Ez}zE$6FcN^BI0FeUY^%{ zxxbXW$RW@8BN?=|lme=J+Xt$wU=QK-m*d|KrZ^oX`d;GwbOH~hBYAuxbx1M0!x^!( zb|%B$&-3nbN?mq0hi&-vjg2}v{*18A`N}K(Dl+^xy;@X|uw~_zon;NCtFD^alH{o! zZTAE1ZItR*UX7~R@9Osd0$5@tYxfw{k53J-iZn`u)9(0ST=~bcJe#1L?#aEa6!U^> z+F_RcmYk-T0a&b7exN~3DW+5MP6Ykbvk1%O6H|iRRpkQ>$TkJp+g{j`BQ2CX*Gfr< zz=?m{y9L*g4S44AsPp2juBxhy67;1q^#^qG zd!0DJbt}EM1ZI-8Yxx9)rmt2?GvOHSvQP00sgVg8ZMKYTmFh4vI#+zmOZ1JY^-jam zrb#~>e`uq{)sqoLhQt+ah~~opMyQ4V=B}9Q3PrqIh}ESFf;ait<24YU1T+1P<>T_uDa}k1SIO{BvY8Qi&0>NkGlo5khCKiilz2@P+0#EQOK?xU`j>KJaxyCvn8 z)xd%5?BfJPOmyFqfh}$r^23nNBAALvOetgLg$;DkdvMelKPl8H0d&c^>Z)n#( zm{F6PslF@9jkUn~_(h3BnxtWu0NALfn?StZ-028rXly*s0MS%?_*}+~U6S_7TBO@Z zuCVORP=ko-_r!+}Z;L89%*4SwBbQbK)_pt}zoZYLh(+6(62MG$qw-|A;c~C$MHfY9 zjV2cAo66QEPTl=Q>8JAXrHl^+D9lcPpMwmQ+BlrLzD_-9+KRoS>Fxol`n`OcG9Q7! zA~%-kvcZD*qCDDDilZP}%}pI1PCDYMNrh6Y?m9*#!u+RChX)g9hh@-yyL)A2WelgS zLq@7j-Kor?@@{qg!8;}R*(ZjtUJXTGH6bUR2+c6tA8(rPO^tFGO$xK?*pAEL3wpi_ zWk5S9!pz<1$!AA-c%}|JsqAEcH&{OZ9zPuN)6CxQ>j$;qe6!t59fovQlj64ShK4$7 z{@39(({+yH)DRn`KIk2B%`UHgy~I*Oz8+i@u9B`x>KFFn0$kcb%T+PzM{eTvK*G|f}D~A5$wtBln)Hw#)4m(FZ9hcBWYg_cDpRF%&;Gt)oh*}RPeg6VBj3YXdeusL ztHw9Yjo+Lf5Owf=!ktBATpSiR3r`$A^{A*75Y~n?ArEj7AEE=Dn4owH{G``VN1(*# zfuN+}YC9Wm#uj#CMi$y)O^7Z76Lj%OY`HAG0{`zbS!l1JQb4>X{d{3`FC0g0n79$? z875JSJsd}w1PX5Zct06$ccO`2bj;Y8`gzU6;Vg`|8J_)ma3yMK9Y5+0`JJ$Zf|nEu zj51C47HYNpk>;0JXh9$aeh?_i5(K)20)Y}_A^4yWB|;D=5ex$Tes;HWM0;{-E*QfH z0-1UTORKA?kDEYD=BD#^iq_ZyKp>x|lu1tq-=@4-`bzZIYyRbmzy}A({KrNA%L!Q~ zMg@tLJ0ruwZHzY==rzR~8`qAP50K&}&+}VCb4JDn_lZ_F8Bag=u^hd@15tcaL?Vl{ zxp8=8xLIc(53bFv%`?}Pme2Dwi0Y&*Mg|2U!kQlWscv1OW(q(FTEdtgr`poR)=b=1J1Y4bx#zL*mRu|ucCnrHcI zYhGJ|-pMtv0FQz_&YnQLeVVPJNgwiq9_J`+slSwC#;?p#qDFya!XNHBJr0Kw5YbW#q}o@uhJQx z_?Xn!3~l4|1_pjbc_^0&B|!wh9lYR<5fbI|)84~9yDWb9tA+1A^_YH?%XufS7^fwp zL~v$drkd)dkqk|gCufv=7NgqRlhb@cuF-cwMEH$|P0iD3vM3FLs(qxJuz3CH7k?o^ zqhUQp2HLz)2VuG7ySm9dI@(V?0{Hd6iw$zoDPFxA|44ctFi)wKZvE@+pPnCbv3mX zGe4W`ohhIaB7$5;FuTO1BNA#AV(Xi)%x=EAi+D{H-EX~;yF0;7i%2%q3-clg-;tCI z+*<*2HoU;peVvRR-B5U`{Xnz#TaM%jImg}Sk5G2{HI_&ANi8G?t_^o-!(thp$1;Cw zP(ZvdrmpmJ-#=*13%=@Wx{ zN0%d$3S5}z#dhK8smY;a22GziCxp19|-gfCG?V{PGH(eQes=YBY#vRyF^O5w@#)J?L7QLiznEJr=#LOwOv@ADtQ( zW=9=xGgz+t)IePRaxd*%j2-&istHo>O>8&H*+ri{LwMI0mvd(om3sO!tvqurKKTA9 z1QD<$S9_CH*oFD?%|_o7hrre<1c@ywdbAjk;Ch#$FO@~S zc54!fmY1pdfHvs7h-5GaN49*`>nj#+MI0!DapxG7ssyo~mymwLPZ6_CfvRL>#vVTg zw4Mvo4nYuvquQt_ub5(kHya|(Wbb2x`4qwC`)IlD@RaQ8K2PmnVV4=%%GX6y^zg6% zIa8gDyJ(Ey8!h3aXw1G_a5ezmf z#TUH0hj;}kAG?F&(xZ#2CH!l|0>(#1<)P?%=%ux4xgnJ<=5JN(EBbYA zqzIqGC>#cGW!~RA7}{zkvdyo>KZfeOFo7JkEd>CQ+3%5FrlxI>ZCza#Jtz+yJ4yt> z!p-;QS?R<(on+>3A`E1FQ4f+`2CGuuUk_dw(H}%kW0ydXH z5YtQ3Pr3#_sU7Hu4!jRNH^BXHJ+?_D1y-YIa?rMf@WqPy&SUIfr_6c?VFla52sVYb`T)$jm(`9TI|jT~a|oA);T zCPUrhy$k6Q0hTLkbP6jx&!U8`j5dEkyECKgkC;#*7PO1?zyh{_E>Upk1#|k}RxOC8 zL|l@)^MYFdVgxvA(g*e@ES-nS7M+Aj3daAvFv~$%mwI#`b1Fki-lSTlOfyLA1|MwQ z>Mvr8TeN)^R(157cs^dZ^5ifUwh)_ttTVSX@kK;a zgnNKw$NlvFIZb)SeRKIl2`wPFk3X%R=uvb`r$9Z;03xP>TUJ~`%+?)IsiR(GClKjm z@(kh|)v09iCPkq{58cedDQ^I&2AVNZLIV%&%;M{#y-H0Z(% zI^IPV1Rv}TaHXv)S>!-$RZDlf%Yu+?`CehsL>jf!PI#^eDK}`YTO`2)edVa#s~vn7 zQVCp82}1LLSDOo(#%%~wVbz4KAIgD3fOY5tG9I2+ZbE12v$v$&;B{u*fcs%KmCd=| zy>>9;zY`(K-|01aoH8eFqhD=Z3Vf9Y%+DhXV~)hLBIx`ic5`94cZJ8;V_KBO#ncvp z-^zGKFHRG^LDDV|Z9j|>E=?u((}2`1QmMM^ge6bud5dzHDZIo%{aV3q<>jr@@J zu03`3Tpe=~xUmAScD;N?@x@`-ueOgis+=2o2KpU6(=7LFmU;OkV_xRnaD{bjjJqt& z1!_@!@fmytEG}%tliJHiQjd_VW>a;^E7Ylv*;2*ESm}pIJ1X+uD&^TXZTLaQNyC?1 z5J{;8q?A?` zgH%L#eEolXXU?2CbIm#PZRXtfef=iQ(%guSnvkiYycx7BEb<65hEzc|0yCR zeJ}q_q&~W4x45~#a`B~ zhR@dWTgkW+kNOJSg6U9~2^BActh>F3D<`{Srn)DUZi ze?9;gW$E98*DUV)Vfo8s_)~I5f$&uLysoI!0Y03$&}GUKnf@cK>4yQg$W|QzBanNg zs}ZPXZDgv^Ew=hd08k`|Z)%)oj*kjq-1+j;OD+Z%1HQ%ncM;cBiL{5Pv5NLeEq9VK z0g4>NKT&xt%Rm_4vT+ zA=Vu;8O)zeAq5}^g>>bO7k(CWHCTla;_eXCHjVD9u; zOuHl|@3eb*M zZ!&$->R<_A*C2FR*_}4>YZ~MaN;0_C&WPdfnyO%w=M6Q|>ktvesWLSiWPVgU zeBbEo^;eVe9B4!4n`eEM7JC1orA$+wu^fH3V6yRlsG%e9}@UqWb=#qpZ#7JUkyI&iMR3~A>_ z0Lp7}P`z>-x$(%ZdxTk*uOTMpyGbz7jl=fj;5S>O957wyKs)Jx7%@h^Gt zq5>Tv*KE0s<-*Yo_vyII^gJi+j3+odlB+YH>c0Y`l^*aIP0E`@!-iODD>#LL%OLDx zgMMQaIqD2U$(W30;^VW3z2mUF>jMea0_sWDE9ByDqRJ`X`KncgI~wg-J$5n9vOlO) zGb{-2Y{?2SEMD&t!sM<+r-pPQ@A}IHEg!$-DuiuW$aZEh5F4qfoF3+;zQZw$S!AUu zBRx%_x@)G{Q>%e3JQ8>%i6pyTzFQNaatP^`x1 z5t+!z_hi=}CW5vNB>8no^*GbUh9S(Ci9eNJf#Frrdi8}ybq5hcA#Ymry4aEQ3dxkD zeBUVD5U30Ob!i4-9FNR>w(#&bxC0p#A{P={si(0#A{I$61}w3@fh$u8$GeRvGjQ-z zQgwQjART*o|G{=wXZF~>8U-HP*5v=dM6 z$<7MXxXC%glmcT5y)6>yjd~B~>%a<8OqMCE(AS}(#F+sjoTw_q8t8QRRWr3*38JLh z#T66y86@lMwGcR=ELfN|qOkEqh6SSvQDoRo91s=|ZvAZaGmf)4<;c<2$5Z7)R)nXJ z?y?Y0z$z=gIM=iZ(m+ok)P+&3uw)Za)66qizeD9d%QSZ(H+0w&t!G)}-637b!IW2u zL|Kl0sfT=$DCRV+=qsXPPHuv02+8oPJM3HBcfYTue<@DahWf0o@Q7kA>cop`PI?ED zlBxk^M2l(rcBdAAnfOUuCygcQ)b$!5kcG&U)DH!r4)sU8ymcK#1g-%d3@uwb=%@fO z7_D0tp4-=xHN|_Gu)b^Muy|>4HxVzPB9tZWka6ZeZ!DAx%q1n+=>op6jqHXvt9Y$r zbUN6(^Yk*)>Sfa~rdv&r159ULN(vYBqMe9)2{WHZ~d65fytV(>DcqJlL7)|PRZQ{{FGGrppm&mYM7VTVxy zEf-2j>+u4@>F;{itrwUbE(o^JCXHOsqA+|!bZ;=ry6A|zPR(FLP;2rEI{U_*1h;YD zuQdIEx%F$;u?Hj_)M!M{n$QK6jL5)|*^?$2a|6O&%*WMhnT_Vd&VrwrdK2Llz{y7} z?^vbe%1!F7)7trJvz|8b0bynDEk50@P;ZGlOjC(+hlt;Ch?tt^AR00!yzncvke_fH zsP_MBqwapD@Z~EtC1Hy7j)U_{xm*|XN^0gt9Usw{{5P>(V3AnexFqIBcz|$Mx!wE% zl{IvJnW}m9%{D)tOJ+QQUNqKK-0%3I3$pYWVsA3O-sZo%@bmmS_qDgDNePvQ4HuPy zCx5-avid+PF-oFVG+0x-mgU2IWeKuWsoRM$$hA`kb{T$is7tfax5klLKP>s4IIq*O zu(lNUaV2Qu`YWzqLCD4-rk)A1LJzz|%h<|7-&xYu$o)|_dl286jsjCXjC05&d`PyOMZ149tEjhtip`R(D&0sX@^igs0kHr%wjZXrx&X70x zOh)8qTA~30cPIgQ%iNRYXTob0%kRt<)j(aJQu)0NZBOD)Fz>_!qdW3AAHZz3@`vTc z@@m9GBU=mI1DRSV?<_(f08bG^SjRo*rB8=>9}F>}*7?+!5ju@Wfx6B6?$MN(nTG(> zzN5%g?&Pi_D8;1_rPElXdh}|_lu(U(Y)IpNw@NJnTA(+H#1J=&h{4VWEM0=~LZ!v6 z(l(Spjl<+m`dIndkN>L7t(K}{%lPlxL-?BVK{ zGO|8oyV~BDPGHM-lbiPzfh?)ssNU4YTI1xUG7>69yt<)a>w+X`?^6OY5|Y={@P~ih zIMpW3I_>BiW0t112_%feY`vsly2g}@0gv6m-WR17ocYFXqEL@uYMI;HsoQV+IOQ;E z5Mk-h(di@94ms2pNd3%L5|ezPTwC*})5DX}tdHi5x{S-bpn8+U9K_$8`@baXH>FCJHY~>v;CZz4KViMZR4G>{sd_5E;r>hYep#C$ zkYhz-?WQjvh(`R%inHkv#6OpUf!s{K;m3)b=UzNFnL3;F?aND&&5WFF4`EEcQ~3St zE6u&a&OzJxGez8QHVD0e2$isDWI}T{Olc^@o#8UZWAfDM@Pa ztxw$k2&ji(@JjZ;Nj;JFy1V*aM)y4=F|u4L~v8ouVT$ zzoBOAnzyuK4?NY(^=!S;Ij8`jp~ej-HmvGoI42WU({Ji9KReZ z#~ESYMVw6GzKGwp#-dQNZIm%j@$i+z2;AVS4ERGH_WsnSibkQ_Ij;dM!7uU z_K@*wB=v$}M1Iw`W%=7upkJ_`xx{yR678H(PeR*Zrssd2c_qT@KgYV)%$#DA z{GC=WJV0rU-Tw27|AG~K(gm zt0p=Nu(U@VSd97X@Z4CT`>O?ja|8*nr0guJ7mVyg-PZv<7NTmk8of&ubu)H^Hr3~a zOd+?@g{^f5@DGGXKAT{xuxdM-txTeY)~X|PU3@wXl+SIw2c-1ku4f!+9c<>|b0Xty zc!*0r3pJ{NC)db!DZTFj5cu5G5TCCk>4WT7xMKmW$~hrFtWt3T0>DKd-quvZk$a+MJ{rAox>=<2rz+sa>>1loU@tC?AC znsm&-NOeQR>%y8x5)7#b{t2<@kPgOWgu|{$e2Ae9l)+rd$$<3XJN;f_iDL5PqU4mP z=gnOUI#X(ay-q;pRK!B&U~amJNAI8EmZ0XxMs7Viw!g{J4Oc&DU}ts82eEsVQ1(ILCFe zDYd={eT2O}==4G{C6L+(|5;>cb+D>kt2T2f5}03~#@63l{+<WhApTg zeC30~(1=b*LA=)DhAvuM%cFBV#Pw-HmsS_TO>7jrYy1`+oZc?t1Cto^d^GDr$n^{` zJ3{8UM0yoJt3fx7iLtKKH;cQoE=;Kz&=n_#b*Xt0!dk1$8Et8t-!F@-j)=&#EVut zW?IOwX}oJ71qG37?2F9}8i-^n!6}>{8lsWoH^X8NRMv*kRlS z0LGcCd)cT4>o#}6p~L~vjns*fz}4!vxmzRFi<$8Myb$OLV`yPSQ$i?qU)xz~lvAoR z$&y}K)eu1U85h81aGUq)qI@Wgah6Z@U%p^}-jgo;d_=}8NtYYI;dn+2c6-yr%xsjM z!>*uizKZJP^t!9MM$K4mC;97%nC`$`&#bR9wrA6Yfd&uf(N7*;0Xx_7B#jj4E-!St zIGbjr9GQ&mjw9>`h4`OaH>4yN;oh1!UV2ZDbpg_(^#gm}zJgOW30BupTZ>XGF55H{ z6KkzMc_9@})#7}<$okCdzG_=DY+?3hMJAK~)-#Z%L^;^)U^tHy^Dh54caiiN^ZORZVSPZB&Yc1SG4)j!BC?9j&Y{wxmf5pcoc9oxv2rrZ-d}|BFE=hh z=8GbMFV7>U@I**P6E8D()-`5h>MzVM}6X=yG0$#My7Q$Jc#))KInF6Ta zV;b1Vd%qRBu;9u_Krx9jn)A@JsS>X_Mm#r#EA=M#s^J5Zdi1huEriRgTxDV(OA>yG zXVkMicV_#&6x}zd>QA`OaVh>{AAb&2z?ih`%;zTP3TsyuKcx*{dnKxg?BK0@2Tf;W zke~>Y>{tcXNiEKO=rDsQEnhr(L{MmIw0QE??^?vq8GaW~FQ7PS9&3Q(qOp1mQqq4J zSDiEmCH4}Bhw2IC(o#r8bYL4Bt+WGP=q!F~6ta-}{Ca+>S~GCHAuV3XO8JJV+pFOD zPCz`HpB^t{)F}(k;*dDo#H-%~Yh}`s%*>O)GIi`%rhNr}=HL1dU@v2PyklslC$p4V zWfx)k_WbocrK`a%-jHszd8jZRij6Wnrn#K)L<)-OrZL&z3Pyx$kOzt!pVAPG1e`&{ zX#qeGdvifIiy9mEnx)MUtL%GGhc?bTf(a0>s+k-2&vI2j9v?eeCxokLhGDM!ZoaS1 zHHsft!rf*GV`F<797YwiG`gA!S8S_UPs0BsxT@3&(!Dk5)0m8)4^6AXS2mlPka-nL z8Rw|E1X!lwUoz8uny*X<8f`Fhamz$HwMi_8)!@m3$dLYTxW>QI%Zej-6R3aucHx%} zyIAC)^zUWjKoAHCtS0UUXHPJ#4y?wuqNd3Z>*|M@BF?jdpP&v1krsr?JF2s@Q6q`2 znvbYm~N&7FEElzXP0q=W;Mvh(S#XGM{JyWBYuXYk00%vtq^|NRWP~_8k~jL^o!c_1`jG za`GF06dDfD3T5c4OcTOk;EyG=OnK=cNI|vyQo$_amLC~xhf@Ql?Kja{iE8w`iPZ5l zNB&-!8y_eH0P685h&9C#0lTMSnJ-*4O*Io7VD1`6+ci&Tww2?wpn{L1uqZmMZw@Tw zFbw$sivMEpHm{ymc6uHL^6*Gj(h`ix^b@yi19w&@80kk}MX&eT=b>W_KEcvaMIREaO!>EV54X6T}9fJr>lWZr_R^!OeNusicDQru(iZ38zm+~xT7d@kHOuwaSO+QD<}W;sO1Uv0mo1ui|Xs29cW)+6GlX* z{L7<3MwNulyP{o_{B{4OX4Hf~nIF&9Wrr!F*#mkeFK(|Inaz`u>CkZ3DVa`v3L8`b z9;_KOjR|R>Ba+{p{bw)SKl06W(cr0n&(`=U@F?u_4qM&PM6gPg8nE4XTjHw)Q2+0{ zDSoE47aw+ci)5WXavH_GHH;G^a%#HJtE0`*&Fy1QPtoyWaQJVT-6i=~AHU_xZvd`x zlW$*>>obVa2(F}q40lhC2Gge>(}1VF>zQvRE;Kv>`VxPJjfCOu?1-o{cD0W&nDfh4 zstC0E_QyuJ@kHZk?vX^P-Ep7AxO24$U`fEjtTpA2MhS(52_pty$T~^js-2{F+q4^Z>ccQNcB3TyplLTFz zd_fbna;6T{FWUQFkhDWB{5FQ}{*)^z(*pYQuv^w(AziZJn_t9ePA)SvE(Z=?R_?be zlJWZBLukjm*3AZ#dn!AN2bNJPq1WuIepy3DIir^6UKr|f>RB3D7UHtD_~n=St7aiU z?yB#yiVZXNCO&q0FuT~jn1&asx0f&Q`sj5^2-0(BmFBj-!9VX_-EA}vAb^8%2Zc7v zNe>wX_x1Ujit|DYXE*q2HpN0?a+Q8HcUtzyj5He3SwyeLEGcN3tvL8y21fAV@BW|` z2uX92qKug3&aV}-iW3u6fWH<5YZP9crt3A*<`H+X|JXFCfmq`U(?>|SkG6NkkugHg z%^z-f3(S8UVVMfSh(9!G1v`k;sr9x(|1M%2iQ;{oCTpHcTHs$kunGlWEs$qRD;(4i z7;I)z@JJ$#I@|zgW^+{dR3}x=m=PCBP=BCX0yE4l@MwS+7v7kPYVR!)lCOYCt1^Fd6d-A_+a7(q&@EK3I0Y>D zp6*)*{&okz^%Q-D{p`?f+8mh~bYbE;PhF#3Wz{s6^P&`T5%BK5b(7*!_bPEgh%P*j zA(N-3Q3Cd(;$APj@Y%vt9j`+&Wu!P!1L25-0Oq4z{_q0d-#_TAZJWuyc!k8~2{fm>&|cB>V12*2T!d zi#9DKFT}lpfFe059rFF0L|+gT0A}(*pqS6Bk`)=8HmN#&Y7L^6RIQPugg0?@33^^R zG&V+M#FrOl5erH*2tNqMN=*L zx7;2>QkKTZ_}83~9)(FwZ!`u`vXjJ#jjj?L7bOvn9rdMjKH_D@DS;3q^eR+28NxJy zjw8uaTv0cnf-W}VeYfdW0QO66ACsoLY=kYJrlumd z9V}l;qTi*`f5sywkhsfGHMjPfo=!Tq)-cF7@g;^DYtth`wNcI3$HO?7`fiX= zBpe|EHyE`Rx1n*=`gv45#*DS4#i$5ok{hOsyuq4IUzc9~ zpxVNC(I~B{!B{Sz`E{Q2GY?}HaT5QtSR?N$Jlr9iF}VYmF+el+n~bo$@Y{dDo?hTc zNBlE=5|cL8=TTdIMekSS`%Xpf|y{KB2d2U>2 zxfN}m`tU*`{cudaukTJZecgpR%+4Mv)PpV2wqc@35ohI2%*x(&YBW{ix-bJ62NV11 zi(bml*naAiF+nE5qWoy3VPOy=cE(^BuJNnj1}RKT(0HTSnOzRSVNO*>2xiQMH>!ub z?~$UGpP3IU2y>;|Cd{PqHRfqhv`PRrxSB}Y>`U!Qc)6b}Sp73YvGT&2_iNybpE2dM zp@fRG8dS!6>~) z+14mbyhLN%<>?kN5tj}Yni=bjT-BahFPD}ZNA5}3LISs=cmMZ(;ADX)0B#RiY~TKWC8k6 zf@vmD1tQ0~CDt@actb@Po9-Zyg$kaBeJyC9h>(|C=PzRxHa9=)3_;;X9-R0cxSTLYVH0Y8#Zt8sc!(j&UYqAu-6 z`0)i}vyK82-+?}b9Ja&-=KT~5e|u)L{j33o5_zWlLzr#$Elw{e#GpH}~d{$00BF z`snST3*TkD^9m!S_=2jBU;H6F!DD?Y{J)2@Lg3jEh|9sTN{R>pE-YK1&Ttv*X?J;7O0;z%`I*?6Eg;gqH#hd`qOlDDD)9pX*e!TJk&%-6|n{@Xb3D3&qyn$KAS~#Is9k4{UxqU z-_mhnibH9c&AV&o5xRJDLegjo=2cLnV6KBiAFn$leakx+qsS1^AE0@MF?}m3_`pBH z@YUK@ zJCMS>^z^eK4+XHUTcvwegh>%v-ZlcXHCD;7~;hG|uq5?cUj zNUFjeK|SmMiz)j$#lh7&7dXb}MGbS;JvnB1ft90!eyJqdXcyx?ul+Qo6LD!w-!n6I zIZb~|8^wV&%ag+Q3%6nEc%Hz@{8<-9#1LU6wcyNHU{q`6p$e!B>T;|Q5(2V@T9@Eo zJwp7mPUV=vjL5C7VJ?QWR2`9SCnqOB#bY>?W6u^-pI2zo(`vm6^2$gKsV*=8I_rUT z?XQNqoa?EOYmY4eASL3D`)TFBW>94{rRJtm8<^7CO!gZIVm2WqwX-65u|q=sr57`7 zTk=xC(8m;)krRV_p7uXbm}VG~PrFD4S}nj;bFm@IqAPZzgw%-mDpJ-b*H^suD5vtx zg<(FzQ*=F7P9qE}$!S)!jl$RvM;z66Nho}>fIR-<&*H@>Q1=ts{pwl+d;Vm->{4_w}g^kvS4HIt2`>Uju1;BN=NsgfRzdm)`+BWdA-1Zc?p!0is<^O4mI0&~;f zQe3U{51Lf}UgAAbY8($5X`}=57fgUh?3@p)u0X7XH1CrIft)LWa^<8GuG?TKsLS2{e=mz-aIJS~jiu%^C8QVbdQm@+jp=X<;cAUT*drKoJJZdYHu^wMI+^=wNv)3Dmx4nt#^VovlTjIGuvZq2muaQhZA0NSI7t~}?Lzjl9rvJ>%yF6YWW9V< zkqA4MWRt~dYe42>pX=F>kA^G|&h)!pGMvYxoYNg$=({n6BtraE=Ala&p)b z0*poI9-8tLgp2E?djgP=!us|;C5SJg?u*oE&{C$i6Un_4W?B?ndX_YjhYdDp!E2yg z+=m65x~Mb$`=a!FDhZeeIzLaG;zUf4%mAF--g(nt_=zXgr>N80&=b4Mz|vCot~-)T zfvgoK`DjbJxp%B-)0JP5;03*0kVP#-g@irr(JOMTdtFM)E_i~6=-wfiqcRlSBH=VjUmP#^k?W*HE`h^II{) z?a199=T$T?3qk)5+mK`{+Bubl92!I7T@e*FyvOj3L#x=X* z2qbYmnUZH!47w*wJ5=n6Z#CDO7->&v(91&K1GP372igk91(XUeWwdlEI7fBYDo5^2 zZtj=RkX$A6*n<)yEocL%T87ZD1On$w&;7!6Jk{V8f~P~;f36+GdTaK6t9j2=h#Nsp z_#v1%&pD*c~c&D`pWhCY=t7w_1-O7t7E5ruI7+_ zB_Ja;F`SM&nAia%bs8yn-l4 z#hYehTTKS6-NOih9~gx+iyLcMBlIJUUADn>x@V+;$NU%{Pr>I%rzsTbR7@f^B*)Wz{MF@v0J!+u+ zZOVCegnY(+>wOwDEJBM>^6M~hzYLVHE?$Iv2=AlYfiX)B3MuU{Nz0CO7a<1P z{vkT>vr~-Qn;#zW_f3jcMC6@*V7`OJ2kYNx<6Nn;K&sJ$^ClIcR3wQ7KShBeaDj8; zM4rpOfNj+0?aWcx*d0SQ@E-TecoPBsAQ(9s&snFS*DZ2Nd1~Y3e$aQgE)e;+?tL)g zUP(#c#cwCk1;n5nVCz~=3rtISTR(p7GeuHZ+JXKJ!Tqj3?8ZD2wxVZG&2oED8cWfH z<`|WOsk}JY#ow^D`IrslSHPDRF$OqMuj@%WE;(^qe%Uq(xJ{3H3T8R`C8_PCdi`93 zR<|GVGqe)Utj6Gh{#5zAfvB@GK(kkuIN_L4*<9wsK7+9hkGhEVRF53PlT2FkFI?70 z?frfZ;RhcR`0Pb*nggQ!+KjXnf^2|l@&Tn=%;HUsrP+hRl7+t-;O~MvLT&ZA#N54G z3&}@~Dk+7UTNSW_oa0eQ2O{Un! zJ@@d{R2c&-SjO5ZFTY&X8PYBdzq|ZcqOWl3%UfotGw}*u=7zhnk-L-!xUqbxryUtS zoMkUMm6B=pPV}!WW9<|Zssel~esTN_$(?Xmu4XH~0Rc}cE zjvh%+9_?8*^1fC~gFkx-%P=5ES5liQs6@v@dyED2<*lzV`IsuX@q<5Dc$ag#eR}CZ zGU1eX>jmBE%a);5xNF1Ff20=C3aNM%u%0+r?l8Po5;Y&TOztR0El2LXdrU|=3BZLw z^!G6myQENfE^MCij~&N&kKYT>ZlplQ40l~{ut*5Zhz0$5eVOI#QaKzirNvuG$_TKB zQ9?C{VX(&)Fz@Mx{C>QAz3E@%Cn4nIu+@Kqfv=gk6@d7^=K@NrC5J`AfsngdNt4K>rHeNJdSS_wFg*o^nXSH=r z^eq#oBfE!34Zq!}kmGN1u=qN=cOM8Q_gr>apkwd%$ zQ?C!2lXt7EC)?*b$n(*1>HGd%2LXBT<0RQbLEjyQ!WtK-2f;hT62g=m{3teiiFZ?oI5HP%acL9YsK$^Wy=>T1?V zPG-WI?h7tsJ~mexMbu+OpnraV8fO5u8#_scu~$SbE^8Sl>oCb`e{d&fK7s1L%c zdyi19DzvDg$3{sec0C;VB^`;@Xwzqm#;|D{K9HMni}?EI+d%OEzegJ-6U}~O6n~W;U0RlL0au4t%0=fRTAJ3t^)@z->;7YvUC0=>@&w6$ z3{S0kjXJ?(ahBj8Orl&6&ePUU7XDJvNg&FpZlvfWE-MM^_>k?8{>8D&X2_0A#MMQksMO|977KDtt znCI_@v*K?GPul|Bgq@25#Y%u@4Rn%vpb{>eZ1Zhxk=9n^Af<*N&5%Y@V1_jgZ?jdV^Q%d+74 zJzu#aIkLx_U^UqXgb0d(8DtEn_J(p^)F!AoAP7wu#xuChuyD22EB% zp_hSFnUMdzXC^&#b@|Tv`$bbW489dbngdN3=fiz>ObMThx4GqDOBl4U@*HTI2f!5J*m&TL=h+MvbZ8= zBNE|`){+qRge^WNn~Cntz2|=axR4r>HVT#O_7o78BhlNY8E)De`4=B1>^2RixC=@h=D7F%+d6;)a@$zzFZy;&~n&p_qo>J+1|l+|3h@SydONlu!K z+LT+Nf-9Z=j_jMpDU=({4yMwf2E%u-oA$=YJE^0Vw)DH-7G1%VTj95Frd40YpYIl2 zE(al_@oeolsWExYLnHZMZRV32y5=l^B-VCzduN^LL7>k5^fi=T0G7FX1y$EXac z=p=>uVoYT2rC5^}p$8)K@~g~y^6BcwdOCm3V9iuy2X)gY5fc zLR^_4_C_i0XS9>g9&D@Cwp{F~F)=JR@nM)W?@x;c^Q~#%6RQaSJ?n^qi<7~Yp=5op z-PYvnH7Nxz-^hhDmTAg5$a&=^D3n{xzI5U8IDYDqpNmhu*nd^3YOw%~#%?i?QSH-l z8!3;wd2lCB&?nRgDGT+DJ128@l-luj#Vr%Xkm$vAPnNU!_if}$y}r2J=EjN6f^(Lp z0VORuj{%*#=f0#&U{%jI(qjr1lFo{O@dCQwUNlKSe!C!nkq?Bz1R>G-Y4vhz`68uI3pxmH|w zD`IlYeX_@j&+aOd?M{%8h7Xolf$j+C*Af<= zjQJU;WKUJ_YUZbu^sEuphqe=OUZ^|RR}F_KjHYqz1DyzS%CPcUnvEYb`PaZKg zw}{+sS6_!`y>slhw9E8DNg;Q03AsWtIDEBk5^gx!yVI}W)d-G$N6frJE#h59(bpFX zBFkS(z8^~ynptj_cE!3fY>$ox^ZI_+FgEPGdtio~CH$A&N?^ZN9@n3%LH?~JTQ2Y- z4Ze^m!^l4Z4hCuTZl&5fUAx$&+mU+Z!DOHE1TLx&+jJp(o0s1#r*#A}2Wy}Zyvavu zet&%9XI5tOwNOP*n16fQH-u*xje=flS^m)%^0LUJ^_x?vssALStj&FVss=T*coh3f z@Xw8CeXCHpy<|BwBG zr~puxOoMV0eqG(nhc2r?+@icVcUX-52|kOvMq`din%gOj@KWsub-e%__)N;mw!}$R zgRSC}1qhKZ7cKu1ld63Gg05ujgT1G(MEq)oPS#L9y%TpbN3A)@a+cLT{k^6RT`#*O zZhWEQ?!&|@O7KDtwTsV4u34;`!FkaSnmYc)H@~uST5Bk0N5yswbU)yocxJnVzi`~W zq*fr;v)N8K6I}AmOPbP%EzPa)RA z2jVXpGTiw@DTtLn49_rqEB`sgunP2e-s;fxFq`?xnMYZQzoprhtg7(xU$&6L6@8vL z4lZpv%$jVF2+@D~j7@NMkjYNo5FYERLje5heT?DM4h+Fu*<+mbLC(z z(YEZ8j)~p&jwA(|4e<%V4!P7~VJuL3AacT;u@n+dbG+!St7r4WHaX&zgsDXcqm^ys zv&-w`qcqbGUt2>Op3CZ@skm~jq+m4q$Yj6fkF)EFm-Z8EBVS@TC|_qzfj}lzu$J)& z0;fKS*5wH}?@Avmh}}`epY$UwoaJKX8>8%*PLxxu{=v+t#SD5PxKv~n#`Wm1W4gf0 zsp5HqKR|k`4xE2VYpoZ)9Bn19h|;GkMqhZ8+T&uwEx2o4EH~@bxf3v*N!W! zth{bgE}tK1WA3Y>+M1tu6Ag>a&Nk*aLZCi3cC5JPD?wbMkCe-DEc6w`rm2A%pNmUh z$^8MR^*yI$kBA^-%A^+~o&aWEqts_$gfJ+R656oIK(rpvXn(t`#>41YsNlO& zf>9e1=aLbVGHb$1HAYW4S#b}BglXJuSmma))#jR@CxU)3CxqlsCuf_Wb5gCE}Iu!*^+O8I7gRB@fLavKG@Z==y(krpm4N zkC~526*Pyu($WW=t20S$1a%EA9L^alKT++BJGi~-LU9E%c%{{qHfzZg7RF9dy34I# z&Ym|Y4I7*`rNvHASiAVbGYigD)_pUS89 zYxAT)WF#bsUQZK7Q&Sz)^FA9I{pR_;3fBAYsxFhcE~~Q^$(}m`XZThqK)S7iP0?YE zCc3dYiOba*|E>BT!k~v{_4epbtHc)`X^-2F^<3t29=`wPa%{D5%#dcsmXPlP=q{?Ubd5Z0m=sPSWZ1I3qiY4p z+q_q|lr$M;OA{Cp`_?}ED6ZF+A0vq%3hHC@q0U!!2}hSA9sSI$MT+7~vdBjzj5xTT z#Jcq;{CtW)s~RYVTgXSk@wC=0VPsgx2PRvY@}lv#Ktf&`Z+d%q(ZpAMN4l z8SoD`Khem?Vm_j?8PSYGERRS>TTOff;qDT@!_7FnHap$Lo#})u8^=6?#WM&?4%U1o zvl7<7HZ9QzYAR%ix#EkHGRYTGjOm9)jE*a`AMXrS&1*3ar?Yvwh-u%edG)x1-;CMr zts0IcIyy7es^3z)k=aC%4|wTSqOEwWC2ajUx9}B1(_)91Q>PnzW6BgJC@$0meyn5N zAH7iJKBi}8^VqyLo&nnrq_S0Lz$4VrZZt-eQ|S30&sV-t@8s>Aed-A_?*s|r@9nxR z01c+RnY;TY^WXEESOb?ss*?A1=BrJGy+X`|>mc$5dsbm7K$gF6u5oHeS3tR0mQB1O ztn3xALP;p!VJvAViE=X%-h>J&>^M9Jf0vGk6hma;esp^(P*?<5^2zTQR#9aQh~^0_ zFc)~KD)I{Zxo%CqHhGN2?b9Fp&h=D!0KWd6!4#@T)tti)y}PWzB*y^|A&FQr9xEja z5<7nXBjw*Gj`b^9EBl(2g?Z)0&IzmW=BJc%^VE+%nP8qnIfe7&Hk763_nW8%l;Ed7Q}(^n z!QMOilE`Y}7U)_{-ydMLJ??!Sz@e;l;$HfT^7s$h>Q7VT*U;(vi1SiP2}w--7$Y%M zDwB(58M+Gl7v$X*Arh%O=zpfi+};SF!Qec00KL-FLkw<{0$5uqb2XEz?54 z82mHXeVULB9;SEsei`8&3{9M^?GAHIW=@yZcYTHUM^UUnS>?2jOTm_+5I^%#I6c;N z0;1W~04ri&t!@0qjL)3)pokd!-bsRcith;KSp~={UF_au>-&L%QHgpj;!oqNY=VM3b z>M8hl#eot#%a8n!O~yAvoH#7~yO5z2cakfp6NnYsg`+*U$^36fS7j+`Aocggn!buf zS4tjd4f2P-#Wd};-hmp6&?t4vsw?^wpIAiBWtlCAVhx12oeID=CV!*C{R*j&y_E)> zjl3xPLQsoz;{>a5PVQz->3NiiK{mrVCdl|n=-7J(4 zp!r9F?0A0bZ1g5KXjc@`(sm-dU1j z6$5dcc_ z3LaQXwtlNY681f?Tn+6$=j<{@q>^6w{LXZz`|Q-#ha7g1_N=%QIFVN>KcSp`5a19Q zR7Q>JbI1XICN1tvoLw5FA^&CqqqGrs9cN}Qt+s+Izanodm45LIDx5m;p>Rv*QObA2(2lG0+2_7N3J{&JnnqY`6IN>~ z{@$M7;C2V}pji3u9M7t)gEk5TSOp3IHU!K!-=39I4=Fj!YeIJ`Z|4Es$$Tem)Wju^WJTU(gDID1b zE!}4>p0_;g-G;Fh*k{q}m{!X^XRg_I!<a`8*_N#h%i zTzcgK0;^ScYh`dp`xcPo05 zCHOfAd6*C4C<8x4{W{N%)f4}n%_>2o=|}037vM2e8pIGkxGcKDq@16q#xWBbgZ#~) z_(LRRe77@#`0*F9orVB&zj6sK!5#5IpWK+^lOXIJi=^HY&ZuNWw%U5lxAE3B-Qz3D zDi;&MA=Lu%K^bVS!&29qlV{m$WW(!wsj6S?OE!!$i9cO8iqg>6$x`kIs|kO<)S*H} zwP4{PVN~VY2;#fO&Mgzal`D!kcke^3JL>Z@oh1Is5FI)5emy{V1Vtf~W~3Q&p>Br5 zKdza=hcPXhX#SQUidHGjuPr*KE>W)BIotcQb?4?BangX;TQZ zd3KPl9mVfI;cRWh)uspem)XT1KbhkSuhuzK7Z(eXr_zON(Z{M{^8-75+%T0W-6cTG zGX@K%yuU^3Uc~xdRQ!{&@>i7)Ite*nIkQtOqtWB7Km)~J+?$Ufck3(?p*ErSIoqx! zvId<9SY-+`1f51t>7sw%!F3}fq&9y;(r-y9Q7HMgCxMh*I%f1ECPfyh);|P7{+5ld zjY*!0Mz*Mbn8Jw8s7A6mFUsc=SVa4mco*;!liOj%K4ItYVJa0Uh=B6u(goTa2Q zuOh0WhVacLu|(pN_)gv3rYYsSzLy#}I1&9I);8Ua;ql?Ssz%Rr9-pQBN^i6cHcvg| zBJ+vN<>yuky_tB*|F9Waach{Q0xw$*t1zzHOwsJ2Bvuu5M&0mZ#m@`(jcOc)I4cbf za~z$rEll93x_~S%|1T9(wfnN~#oTxXlVUU(ilBr?%(tMEV$+WMP!{!-!)JBrU5#u= zzSePJc@+~q4tAb3On0=8NKP~v!ZjY@g^S~>fhQWXOB_1n)Tj= z;%ArS9X{}k7500P|2X7JM8@D(8#X}`&-(QB$8)a+HbYq@&L?%*J6b!5w3`!)>oFQv zV@{;YCOINlWl9)~Jxut0juqD9V|Z^c^*&W+$4~4%SS$1dI^SMjdOe1be|TTbAtUlp`FH2$@SPWc~*H( zykU(bhsaB(I1TUkq?bFIl+S38J@_+6xyX@vT}n@it}UBpYp*%fzn7ZmuvmGLmV?zn zm=%}m5XE>opVl4;vx+g4#o>!~a1w%fa_h1@-p}hH7ktJ`E2FRr%EYel*YS+Or^^+1 zs0&nax;Y;Q7VuR`dVdGk>H zH7oih!RMTB3v%!E=`C1SZgA?G0#`e~>zyM0u>Sbp7Mh6Jy)CmNy%fJ_cN*aD^xcrb z-r<9UjJooIjc?>7%=uoz+ibxNM?W3|SpI-(GlgsY&}#$f8$|vvgIQ~CE{bWNpXxn^ zpmsH%rYbSBv^om1Qd2dp8P(ufVC#{i!B7BQj{kpKFImrI(bDNHjgvfHz~B_gW*;0C zX|GKv90Ju{_!^ULPfgkfr9R0P)4c>^zods+pAm?8sw8iVk%*oW)6 z`F`dKb5<#$u@ZZ88a7ukOLE?t3%}r~#cM17w>b;z6)q00QGC-zzttf~r%GlL{wps& zR@#)umwg~Cr1bp{iEBrZ7%q$)Byhy$l+4IquB$RaPn|OdH@z#IM|#>+QI1AwZ;lh6 z(OX$o^pGa18a+Nf4s-U)^k9OQJ@Y0a=D%76ihW|1AYTsAJ?60ZXx?qK-!X zIrysS`_1V}j3~PlJDy(ob7CPGSzCzZRVYd% z_uVuH{5%I0eg0eJ*BJ1tRC^lnw^10t_BjW9gv8%=O+7`gB7%Np9)3t8^rV9SZ`Gy^gO!l}8OJ>F)qZJEZ-V59ssiQ5-9Op(m47L-ume1C6AwZ3x68K&f7hIq2IFK_^ ze1(ZNv&U5`w4sr0s;8%SM&uXS0@W&2YYb_B+)R&q+#SaW)c)Z3%TAt~*@(yf8K6!#q#W!T@o00Sv zN?M!^wHd!`Ww+jr7|aSa9iqschQJuIT4R$S#GB!vI;C=3=UaI15vC>$hiEWMD$slb zns%G>9K6*wEcC|EPpu%aOVWpiHFNYRnUTfe`^)p+yto|~l7U~B^i4>^Q=NJ9mC6}p zLOq=*&5h@V=Tw*Ms1=!OV1c*IOTG1z&XVI{*}Tnzg~o7cQKj6+*q;@DvAwde^wx01 zEy9IZ3?v*>*}M&Exsa$@*X%#R!7~PLZy67%%Wu)-S&4e0hc+I!HD8eY7I>gu3L^rW z2`FfF)fCE1RoByP$JvASQpLdCI@es`jrEG{Q1z{1;oRI3z~|cJ#;N>gMi>MeHI2Zn z*Sft!%115T*Ibu{Kh@}~zd;$2nj?8O=m}j7XvfX); z$-AB+BE5aF_cTPh)O= zR0H(o=JDgQ9oQG&@$@y<<$xxQ5WQ36{{GI#Om<&9tF~}F%cl5P-c!OI?pm+ox|NjH zo8|CKt!vJc#v6~->z&PcD+o7bBkc{(Eqx;NjBfg{(@ih%xPE(uoNzoB9?f~Dao6Q^ zT{O97dq>lHknR=R^&T898C{0~>_vndYc=sr;zn$q^r#zuE8@ z>A4QR;T=dwwdxZmZ;AiGn9oShHoyyX>G0VFO+f;QM^~qA!4xSpk#;-0AHTO9;ue(& zy+4;;S$+uuc=$86!Lsg0#G3Z|hRe{u-F@_KM@=6KJhnh-Zk?ouwS-{(IW4xUZpdcZ zN7{F5(~&I|^k6RY<{6b_Gq>Y5jgZneI2*-3o6G7=%V;^+nO8!8HwVdcSKh86cXlwo z);msmAFmy(h#5Rt?4`g=-+qal(LSs|8V^?=lDTZYX0mVUBOoH$3==qgi9V`Y8UvQu@NWAfqUlu%2o|uu zmak{0`jK|)dF@;JL@vBo+V{f$+!OfdEXvtVFZb~qd7a~9vQ5Dcf=Xjr`ACo&xvr$ulf~#@hXA0jU*L6@Z zR!;USk8yamKMQU#Ybl|zSRCA#E)oR62(o$idbFHF&QTy#&P8H&e^_2nV&R_LgfkOf z2XHMJHY}S?%c&Sn+Jh}bp6_MZ>|2j(!XnZ!i-Yrct4CXo`aNELj5j@u#*)V32GJTJ zoub${MK(9B$ku!O59E5UMA_UAE+ZLkg30fel;`qJxX-5D%bU+I-@^tL%^cvncX&KL z9US6d4=m$q5$AGryS_I@s z%k%3xqK7J?2Vmpl1EG7;M_PEX&6DOMR2?uVQR{YIbanh);$sZ(lymyjxFk6p*eDax zKjC8N`%^3jnnaI-?Ow7-Ny}?&bvUz3|B3jC@BMyl>@omZ%Y|8t2D6c(myT1MfB_{t z`GtT;p5}-{)!U;`XD7?;46t`qJCao-%A{)ub6QtuCGAl8ot9Lk5ipDj! zi#j#8DLM>?qeT8II^zTF9x|{VN&W~tc&-NjAfi$%ZKqRjdnX5*F{d^pzC2AMX9K_X z=S^I9Ff4o^kt<5X=IP&VyD7T{zh23N{Br3u`V8cj2cgx6v3i)bCZvpA-#Fmt&*35E z5`}fuGIi;>5t%5|IUBZi=9;OOuMShwAqA7J(O$p;~ z{py}$gj;>7dh~r$efry5Wg1U&_wM;8^kc{t8U**o)cSYlpasT(gH{q$BH1(5UjY zW+GwvMd{J1T+O|Fr)?jJye{vD@swLfo@vjW0N{9(EqAC_|BK-Y(OJHxf zZ&9!`&+T}w&LZEOc3;r#v|W8JdR6P1PNa2;280S{y*6GB=ugut5y^`aG}+N{6y+If z1tBTv_X*QX>M5Jj#!Awjso32#2Aa&+u7tQ|S^6L1+(<2jb2S$l;yc!26cKZ)!u38c zO2*@gtH@1Oe3dH&H*z<@L;Mer;bW(j zZor$cUl(@Y_1Df(zg=u|dI5c)h8yodwuhEbZ!f-bx{Rb7CCPO{+RxP-C(kz^04jJF zHINAuIG|xYAojH2P_rZNS=~JpgxlJSFM&JaczuoNjT9@*9Co* zJQ+jCW?|b2x7hE+)BUz*V*(o3-biars#dfjx^&r<*am^MIz^s;0Csr9 z9Dyr6>x!$Jk#^hmb+hvlFpwL!@Poqe(55>_$7K&rp6jfeizhp}u!@p1sK<lu~^KCM-<;l9b!_Fj~? zN0|4K7RJ|C^SQtMWfuDOp;tt6r&W#{)MfYo-N1K1a_fcZ-|7H?jrxWr{K|at+q*>3@gbjS#NbnXVV z8>-_p;qO`h7UU!468?djVpRDtk5=Gk5y>P{yCMP``%FiaLjUwzL^sqE z>icEfIQPpBmwvDs`-clr{Ep5GL-#mUIO{05FV-mEZicv+HEa9gUh*03-g*6cE$5>% zavU52!`TrW{6A2h#|>81c_-hOFIa*Sibc2nu&dc&JkC{SSj_<#4DwiORBveFpG>{z z8yR2rZ1k1M&ALLQqsWd^)IwywU4+7Gi8P5tNU?R+GE6qG?c_W_Y^?m}c@A#;7$t?5 zd1!Rqtc0ISf}o%{88bl1F$Cue+vZ@bobZgV?+*8!YkR`SCTI;6r}P&$b@SenpKqC9 zRd07UIe!27l4hFrX2G6}Qxdt;Z7U>uck-9FuIns2jJKvXsr|!;q{2mxr}qEQfnpZ8 zpSGH}AG7%DWhULA^WNQ6s3+v^2(Xruq{6CBFxZ4oUjLqe70g+a_BP#fnz4!hP1`__ zRh1W2Ii&7vb(O;7GLn~aP;qkK`!@MbK~q=XL9lSvU3p^bzVI$Z4waur%(Nsh*VMg@%1RR=1qs582W;eVkhyOG%?5NpLNS!9IY~1%W%7N_`;zD{cXB#t zYVR#e{mb`J3qIoaLW<#NV47z-K3D#@n){Ak85N5$&pOtemm%ozL7l(7w%k}T)c}~L z|7~X>-nJ8Tmt58U{VuGKEGE&EqAxA>=QBS|Q$ZXbO5vTz zvs^k$PwipJklAE~wyJOMzvwtjXpxE0m1@k_S{!EG5r#o2LhMNzxH%yEe?J}g8$j<2 zEDgyCg)fIIzYRf63|QW{6mrH?_L}MmvCg2d7yKmYK>p>C^Rms3m9OjH*QH~8oWtn# z;g+6U{IIQn$duPr5qYKo|4ct^>P=P2Dy|@qSXdrvWS)VS%#f9pJJ4ps8EkfBucZXK zq7OKNUN=QJ6jr|YuK#Jh!+43J&l0IW?!!Ne-zt5KkKWJXwdrd@ z@jlK^^@}%boi|EB#M98}gsRnXhsXsM`2P>yO9a3Y@Urfp z3HB;=^ilg57G*X~5%W>xk7S+Qsy#PJIoSvGY!voVW}ZMQ6cIu3`#a@iSv8HT0z=6&Fa8>(R^tBFr z_Q$IfF>%Fjl9*GSN`y3gXE~FNk>%ixhJQrdTF51cG_*><( zq*Np8Y{E$tck1BWdZv+c2t5n&=s>0KBV!LCos^&Im9i$XD;3 z5l`PTy>;ffedCUkac|#2^Ktn*YQcwEkEr{`8At!1vf|qhG!6U~Y*EanCmPn?#l94r zFZYEY&qQU_pOGd_qzPh4<+%NHH+NBrGaTNnmPn&B1`lyZ&dKRa=n;oJy}DU}c8Cm` z32xS8Lce-K`;}iU+h3HqKZoipP76z{Z>y1oew((rv=u4Y+~?iLvBVScx z?IY`SYI9=YedskzZNmP~n!!Vq)QM}kP21`%B?>6zr~F{ep!U8ptu$J!$>0^9!@IEBQHJcZg&JEr$1&PTX!vi`}%pW<%?$yT=Uw$oHh@8j+62}2p=r2x*ZQX zwcTCi+c>Sdi1vl!;X&K(W|!vO#<}+n4i+g4TQAC+*&jrCcS6A00h=UEi^Hm=Yq>Y> zUOG=3lTyjrW7q@)+fD!*lWNr(PAZw8Koy_FZ1*1GHr_n%*$!eAPo6dAYQeX8_Q$eY zB5*oi8ulPmC!!1B34o&YO~vF%vO|1AU2daAPq{LGFdCmmBZItJZx(Bg@2{_}7;mcj z*>s^#8}vl?YY#~^+X<;nDHfYfiO>wu9iBec_UHKNt56SHN)YJD{kff|wJ7vIQKM>5 z9?v`4l`j+bjR*n7V8>CEukAfrkeG@nM!vN73GKG_?1-GtRdfRjd?vPHNa-Di&rko2 zH)hSk$r$CtqbKIJpo$$Af8|5`OkruUhA32bB3zy@CN-V3^Jrw7t~0A}PtFKm6rVXl z*HhPm)~rehkK09fg$KKGp9;NA*IP1~q*q>XF&A_#Nf+Z!>mtO(4@a$78M`)6806}? z47Z*+tJ~f7d7D6jBpwTO&GJM}5fKr#r`XJ_=8{lZkB!Zdkuh!tq}VI=Ll@34mlW%=gm^y53J zOrEk|T#6v8#$%QY_wyoj`4vwvh{XZppS11UHx^W~>y9l{dk#4aIkG?DpjCF4U?_z(D9|8+?3E3JxU|GdHjRL31Xjz7UU41xg#`mhn}$ye@8b2qkU99%=^YguO2Ur#lsF zkKf;ZMMEPFJBQ)2sVA*Nv8R$NH0#xHIc=sbJFp-sUe-J5GL zH@nk{JOX(NuS5DL`nsf9!9y$>R-`Co9E)F4u+IA~fBk2#=HO8YGtnJM{f=E0; zSXi_pVe1bko}ZaBvY*u|bzjGu#xwFfSB>N=*3`}aRr_P|9pc`No;v%gl-FQ2Uc^$~yPO3P;cdH9hN%R>7J9N6+ zL-Et54l?fjXD#dV`Wsx((*oX$0$!;9dX^p%$LTBlIlZN_`kinqwgfz=16tzKz0>8@ zjXZ54``K}S|M*G1z$$*$X3AmtF2JYf7BmO$W_Wsgd)MN-^sKk+YC+t^`)dAiuw`Dq!O(69DMuEJk0{gk*X5-rUR<0}YI5I%#r z2S_BHFUp?3c1E7yrKKBrZk_&Myg={!Ohdt($~CSVQ@KO_skx`CI-M5GYZf%mr!p@*!S}SgX-}<*E#I7$U9eOipJgA{+`vYmqpX`vc+Y@) zbYcm_x7VdgJ8W{lIMi8hU?m!N+0AI}gf9-siQr0;_1LjlrVz{lUR(_sr`l;I8UM&kD4gtP_Lv^_F=T!l(5n2mEcJY;y%<0cU_jB*b<(DftD<-VWos5Y?LU{K-t z!Lv)6`Ojx_&t>aFv2<1X8_e&*V5nCpW2`o!jwwm`7f zvzy0ht5waNQBC7Xfam!N1R7;A&Xz8c(^=o{#Db-Bs$aMK({A?i>N^)xi+n#HBl_qk z{eC0mHmmkc+Og-=`UQ(QEwxrx#DerQaih_cLA^l2YVpJW0L!Yefip5Hv1wQ30@_y% zU#h+d&Lws99t>^EmAs=h&qd(XJB?c)63)QRWF;jj!%cP&s4B;c(V0^osPL*1B2gE~ zJUueHJe!s+WWWO*0U0=Skq(;XVCKU!Qr$~|gWPVPuOeKlUCOdiry|%Znqlnxy$*FDgIL&R7lzw+~9bpM#3A8`M#Pb*Tbf*%%b@U=asqeoJh(YzFjdPZ=j?k;qm`>G9x0dHAO zim`pw3A*7$;Z>d3t?+DTXwzX5O`pN|j|S|@*IPQ)J+#m}EYm9~(=B1I+{Ucj-l<$LPvS;1lKMDSFOved>85vt zy>|jX8N9`sfn3XhfYjOXxpdzbqeOR~LG2H^wk87ITS;UIi#aA)Y)ZTG?Y=>Au|Fuq z0|wRiOR@ADf}^6&ck;6s1sRWes{k4)GLuZ89Yt(6zq8$0u_3Qt^>A}9Yi>?eE>X2& z%AtKYZkFv^gX;hUf_ckk3xzqQ;OYD>c~QbK2iC{fZk-!-0926i+$785bkk_GrS0lC zN5`%{c;7Cbp29tWp3K`8%=^L#hBsxo29KRqdtXeay!mJZh^swrt5-6FKzM>)?aNyJ z;RwTF4A>r4?P-r>d!)YxTj`WHuV*gX>}tI#RjcwDM4LM1HXPbgY6*Wm<(%{|zxgWd zu`05E&a@WS?WB$K)o^0P8W1GLBk3C8;FM&De_iHdi+Z-7Q_YqWto1m-DvV4_;I3$C zwcMS zVTi-q#(K5npoJU0#sw`_E?KZ#vezjAHn-kBK~9%|8f3}>kWaefvNxj)bY6sewsLrk z^UQx7KupKg;KziC=#CB734Fb-qzu9`n!XENq@w>MHO|6&4w*#kKr4VxQgqTV6OGCeA?9phUF_R5=^p3LZFK zbN_Qm&l8jLEpZ?O)VbgKLOcgAX20t_I;)hbB{wCIFMue=O$c0W^&TG++c(CVPhN|q zt15qYnIx%OHAUimo=JXf48qMMHP z34X@3*BLsM`S=z<32r;uoSPz_*k`5x2xJJ<(lzLv*652 z2P-SOf^=5~qUW`ThEwvy^HD3$NTR2;X1p@)p=|~_zj4zgdx+y`fl) zHXFlB^9lbz3E?k7v9h6$-_!}&@;cOFAg>7<@o8cwA?9U0Ps$1ho0?yu@@OX4GjF4L7;Cs(PEOw%bvUmv>JW2b6_vX!#DLm zNnq%@Y|C?D;grddddO~;wyhD@_gic91V;Wypx6E)&+FJA%nA9rw-Vfz12Y~*$yI+vGo^oip zRvS2fQS?vRT%pTET)ij?hhk~%p&Tckiaf>{uz=(=JxOT@!40FV)e~X!|0-^xNX})U zjxoD3K`|$E-_Al^wO)B4!+BoBD7*XOU^aR~DOHE?E5RNuMi0j<>KTC9T19Z}gr7kM ztakWe2|Iw)ZD6;}8k3lq7~gsA8Q-C2y>UOy6_Bx3kv;qln;!R$AePg1L83$sZ^4h( zH*5!`kLO|g`|EDtXEH3!OElW1TLpJYpe`iAeX~R-8;ET$>#oA{VyWeVz$$X{s2$v< z2_45{zw0JiHZct3ySnnR^XviU1_OGrT~TQyO+-LIAeGFO{NkZs^+ga21Bto=0AIg) zxIYU6RT7)h{RG2R{rIi&!wG?OptB22=d-*Ph#)y516NlZrQ+=+Id3Om2JHJiXs-s1 zd2Zg#mhS6I$s$8scR7J%8q*>&=c@Mw^XW&rfOhUDLIfMd!)8AA^aG&7lrVah~B|hzGAg2Yi2fJg^FNV`n@6VX1z$ zMvHP?_?Q{1NZ-89&2h*5!X3+$rnlDRuu{)J2{DakD&B#Wh?v?qaVyvQ@ z-aT%Abk=-R&OAQvxA9RFzfc=@uu=eghugbzr#P>9T3XSk2wIJxPT)TNO@kW|AI*;2vy3|N0<8vBWFyk+OAkK+@4*uFH(+K zfn#~$Sazb{{|9SG`>V*xq>z@E?Y;S!7SmDmhEy82#CZ8lrjjpH)x8;t9t&sl;>c;V zc*vquIxAG*f@1XiAHv%tWMYv5>v>Bd19|+jY%+s?P1`9LZ>GB9KO9=GYsUz+B9BZ? z`}dhpNN9o|C^}6J;QQXSe>ubR!fHYbANnM)Qv{1wUJsmyXW%OArvyLyhvm@q1kiWQ zZ3=X?E^2_!amLWU|MM$tF8kdIlnHP`#H_(z82{7rL`1)H)h~6&`Rj3ScuPHqm0%-& zqv&lLpZ7+oQnpQm{E@F^)U?%s-G8}$=h{wtB;{SXnfGb z!hEW`7Nj9LID+bt--zEI*n8~`2{onI$tau5Nt$LKl9^0TdoQsAlduwIvafKF@b9nS zjL~V5$ReAK`(%$=y{NtMj{jW)hPO3oHDNJK$uga2R1!me9Q6;2Azh8iwWU&l!=F+` z%0?^2>i>tkr+zDpWE8eOCzztHhV)uS{4+r?g*U6jXRYuLzt(;Qf6v>nKCXv5jE(|s zy+EGGsFLz;Lfus4HKWBo#lib6B_BR@>UVHh1~gIS65uphF{P<73kI$l(v4pnIL)uA zrl(h=Q_H6Q5dNFqub}_)`+n)t1Mf$&RS@4Sg}2Oak~1?t;+6_L#i88dWWIZRO5pPB#9A_G!se3(PkYI8Y{l7=+rJ+6ln^_%s#0SN{Kr?K>;`X2<0 z-sb$MUfl3j%py@TkP`or<6qpCZ$Isp&-6ch)r)%hO?#C7$LolObPq`0c{46>o27^z zWUU3E$(gM%(~w8_P@+{9}**c?eB> ztxlSjl53}rtw=SZ?PdM8>s0ixdNdB9| zj{l4qQ@m9Z++*Kr=+bptTU+=hm-uHuiprD41afh7X}PMFt*B4s9UcQRZ>2^|s#M+ZUdzZi|GOO^PVYA7YEX8$9E0DsP?ghTDc z{@^_4{r*9^IkWezD7f;2SUM#ll!deux6?xV0>65(8m!d)MR{1`U&Ey>jlxbeaGs+Q zPT6W8#4MZ0n&=KBrY%RHFMboM))I$0(%^Kuv#vpf$Q;?SV$m4a-}8t#KxF zeF+QorP1Jo2&%9IpD&_aO<&Bu*_IEoOujghS1RRd-iJ-*HCMAroE#_u^b8DL#mdw! zKt7(t^b1vEEZKLHEw!l-vjVl0dO=es=AX$s%Y=E2fW*HL7~@x5S7E7eA$_N@(qZ@u zE6*JDef46%D+TO|mba)QM#UX?lXoS()Ngbi`dD2qQdb9nfVZsD5(w14ywCvdccpZe z2UKb;LP_jqDm&^ZDZL}t1|qCb_jG!2UW9*7^Bb|FQIRmUfzD}P!w z(ECE&uE_UqxUA;WN_=?f!tFg2Sx6{U(VDi=*m}Ms@Olqk|Bo{~GrmXuM*>JnEQi-zTvJJT(3fINuRB993 zVHloOQ!)m%s%7<~YMJ&ZBI|0y;JK&Gy1+@p;fZvDXGz0;!qNtWRoh8fgGdoK_00=O z`FO6{zL0Aucb3U8*Rps36be*5@0|p!+Weh)m*Lk|Tj}m#?KpFTAv~412^&i^L*_Sa zLQp*Tl%0B>8GT z@1IZ3!FGe=9??y8H`;FJ=~f;f-<_JOszX}Ni|v}Wj=8oonl|@YtdwgVAq5Tik96%G zq1G?Jr(Ngied5z2Do{gNCG!g(w47QrcUWY$n{LO#3Bb;9g`GrP0A7}AR&`N`k9N;$ zSatg%b9MaLpVHu{S!{Qtc_FWXJoOh7uD!i|W$#XE)i$Pv`N4s)^;PZM`h&;8qW1$t z)XEAX83RpWHfwkqYgU*8xRa!uNK@P!e=HpXN4%$7+dd!#1_+Y7GsxFq)9A~~<%==n zdM(HNdZ)BA4<;uq_s+-#qr)mgB| z9suAmKd*$it`j}qLg$^pt*jr`7hX8XKGpVR6&LLYllam)Nd@AT<$J_6B2{gmF!5x7 zx4b1FGUGAJL1y>S&U2t{$(#V{sO6MC<=UtR@Q)UA1?puC`a1{-G+ILmc01?+S~W(9 zPAeByb@T3wz_tX9;u$KS)G4)T(Vxv4`h?9&`-MfI<&PEpoCEv)0P$i7BOBX*&pX-n zpFs!gygZrAX7{fOXGE4%w##Njy!}owEn+OI?shaCu4>cnf~OSbDq8MS!dY08|4vMF zf}xLrw>Og9CoJgKOHM?9AbkdI?4EJE}O{{PD7U29t%}FhVke7?*&A#kxJSzucBP zABD)B|Lg8$O}5dYwM+l!}$dZg2bNJsvDXa)o^XXi5`L-g} zgoz7@$J$eddz)B^s&mQ)0=lv56ix(~0^7O;S89OSy$GNCWpN2^ikVK-Z>Rau6xW}b z)c+#qWZoa`-krjNMT?Y7RS@k~@W~bHjX;mnk9YqNfDP7P6vON8s=5+H2k*!hLtEw zy=)Q;9v|3)YujZs9!oH1adX7?T+bH%rgq9C*xd~{z3T-tI)J0yJA@dY=tq8MV-pe; zh2=as#U%r_dMK>;p@_s(T2Fl48Q0+}(=KpYW^-KpCCb6c;p6yiz{NnU zB>aEW7}Y7gCjc0Pv>WtLOgfQ!9>oG_LttNmG4MJ!NPA`$aGo@v6moEIu<5pg&On(3 zJLAUd*}Z^*_>xIJKb*k49Y4&m+pTsURCx6wC;}@8Wj9rI&b4){JCbMUV8-kelzDPu zdA({E>AY-3vFvV^UH$Nk42lERn4Y%VUMEfO)0}j@^;sToxF^_t*uuT>^P^q`w;%80 zKGf!b5*QN1gR{qvbd~ORAFfx;#+BxW0d}^$X-ED2EE2uZ2sWTtMOKfm@+Sdo>sp?u4}&%bL=(pKXwhBU)puBSW5{j8MyaDa zfVHJTA7-7{caI=82ZKaAov=h3xAm?5#K@imugvDkw_hY8MV?3gh}&6KY-}}2U;~ed zGxqoeV1GP`Pe-W0W~#dq@Y;*`_oDID4PCw{RyD@s8zErL7vlQvHaXL1wlw)gF?u`+ z0bl&>jy#S1IG(fB^!dFQ@kr=ttu zzA>kUBCdGiRkv#+Jvjp8m-X$T_V7;XsL?PD%WlGw<*q{ox4mzzzB~~!64H4iU0N?r zL1rHyt8}G1fxCa`Jma?C_vVEWa3)l^KcxZ^YsCNK>n(t)>e~KcLP1(uQb}n9q)U+Q z?gl}+yA%PXr5mK-(A|xMlyrA@OT)L0&wW4d`_6pxcjh?8u+KSbuXV*QuI;sztZ#NO z3)^zFQQEa5aMzm!J^a@*Irt+JGkqb?UR zI><4H`YC)!4&X~DVt z#`wl5j!}E4`Ep^{5`*7gs=i6?pvvOHg4A>09tvWEL+kFa$VcjROa}hClzRtz8r$SyU!iPKp%Q_MdkfwvWrucsHq?m~$mRsNd#}uQPCb{9OO;&J4hh2Bemz z-^_2$&8_Nx;>|nkSrfa9yusIXpI|5U98hG?ZD7E4=^?4v%BMg#>+nCh+bH$jN;wV-Q)EjIbDbF~3f;<7+&u zU<0*Gio@*dD`-$y$1GiYyuU65;4yA%Gb-7Wv$Uio6_OUcQpclvjN|#U;Y#T%Ia-Kx zs&3 zs-U!<#iWs%UWQvN>hx42Pi*#>TrGA#0rk8HW2b&QD*dz}D?Cgvg8$*kl#VCAIEg)1 zvTX~2@Uz8-n%dLeEXSR_JDb7|{jlLSO~w8HNprcvK+CeT5^Q+`fU%{frZ%0R^*GEOxFURLytUz2vf?P^ zKR57y4(%a`J&uKlnj&U*TG51*^yTh1-AUE;J$1d9cI%Awhe09Zkvg|$oR>~8wYR;t zh>CN00=T-tn7(M^(Xz*u+uw6@{qUf2$P^7pY&C#=dDG@e8!H8}NdJkgw`0l_0R4dT zW_h=;^utHD1duIQbMwrM(Vl*HNL4}2iIFus(2_9W#UU@s%FBCQvNX+NybT`cLP{EZ zB%jI?J2WH*(|vxfP@wVxRrM=AlTPoxCK~{S;e?-gcz8S)E_2#p1>rP-#oJ>(H&!Yj zmXT1Q;UJv1{&d%}4*I&8-GgyE`57>f!p$~h+|O8JFtIx^Zc*KF7Z?}_O@EEnwe9xA zkO^e%wtPw4tEjS4^lm(!}@2=)@0lBoB_}NIQ;cg48}|)r_mF ztIHK0%_BcQGt;-PS@|5wUVzlj2mb|Lc5ilJVK8aJQlo+4l^rDbyt{*m_h>*A@XVNB zUv_UjZb_%yXE_>I8$28L^gZr$ygsUI8Hf~b3J)0)T7b%@(nPZ1KEih|8Co*Dz^>69PuIU2Ri8G#TyRrwufLCs zj2tJvxn}l3ns##x3oj{Qf->(qQ#pW-gK|s2!UIz_V_NCHU;esoZK(0PQUxfd)^i;% z`5!mIy&l+GI>sB~eI#!=8!Tlr>t{^*o280DmI@FRVWNjV#}WJWJ6HU1qOt~N1;j&r zuPtj_W8<6v~kFNCtPgiv6cx4o{S-G?k^=yGJ^W zIsEU-D;dkmL;)Togrq<;$IA5-(kkx+M-1kva}CpK)zeO$v`DGpsv0E5%h8iWCZ|5x zp?KT*iU^rVn$;DW$4Fiir#Api&O@-f>wI5v?GmZ}-r=DsxYMCToE*{J6&wADJRclv zNTaFcLR;gds0hQvY^JxH!h-7~_|eZL1{=@>(zw=MF3Hs>Kq zu0L*aWJRgW6wm`q^lvv2>Be#uL^P{f;2 zoYbD{CI9vNm?=f9X;iSK!Cg$~vdWc^4SB=EswD9aR?09@Tuj_C#|Bhen?D0kPWWEJ zhE7@(Tr(*B41ML_hGBLK-lsdXFFb1{5)%OtvHm1CdCgKUOQ={a4;M;C6TorsvC8P{ z3rgb{Y0$;KASuS(@EdiXo}D6O&`A!9%1h2SK2?m{C+| zR+g45@Qora#mnyTfNZ7Hv$12AW(catO7QrK9F=lTJka)`^|Yjc6qBI;uv_c39i#jg z_rE&WXx-}V#Mr7xf5@g<)1z+s^Zg4{bEjGN5fcRE;XiZWS&fp|%7%QyYo|+(k++Aw zFiV#pJ+=pify%e?tbggj9=GMF9Bh94xD3E!6h8cUWS!!i)X7@wd%bn?Rq!6#yp#Jc zgN_qCH6ES!{5jnQa_i$-Iu|hCx*s*l|6>1M1y#rmmih*Onk^1Al%RQ_?qLhwRT^(O zm;_$rRn=$F(J|kj#m%?T+2z1ZAfI1${(cjB@EQGw0M?a3G3USzB=2ps(}(>DQ5w3g zaH4!1UOBr3dw5lol_$Qb$EAA+FIU=3RE(t%G86QTseS~{SC(jv%Q$6tqm6%dbhHp% znASifP;k@l=w2y!7gq#Fq~nca@W0qrPR=hi8p&MtaQ$)2!?raSZbgr4cDD6=XDAv=}08xf9ek00K9! z3!0X`ntGCyVV=0`@xF;N_EB?$G9E{J52p!egTh$)aBzk(^d)Hs1 zp`!sYm=emVR&mYHhiUh`RMsbSrHMQcRk1*E;Ghj9^YUrLo4_^L9jBE^|d~vs}`$q@Be;Zx4UZzN+;!}i$U98 z{`Gg4zUx2=w#vJ7!viWfHy{eVYWpfU`}W`q#(a8h^97^V>VSMDVCpKAKhrfK5jmCw zDU?oK4{Wuxp&WN;qm@m3S;f^aVPplM+no3 zrT(?jSOZS=kw4O!cFErwAA2H5;lK|aAA+v(G^R6x;tO_yDpIO(h8Z6!+Piw<@6#5z zJiG>jv!B{>pQp~p6i8oxFW##VD_!lYD!xgULRh2wVO>Izq$j;WiJXbs9{(AhtTil7 z3nSme+4#H4C*yE|VEWRAz#dXhVp^&TkJO%YH`W$GpF20u(ZM`X3cSBu_zI+ZtAJ0+ z8~JFG4Zjyct4v)vSJG(LE-GmFkw!sTr^1OA(6p3$|I)N6TeF{G(|k5HVgAgSF{ioz zA+TvaaLJD0yPQ9x?IdGncUkzb6=oV{uY5iStp-4yOYC?a2^4$+4^Y)u-Jcs@Q=P#g z#|7jYx5xX-4;*BgiQUIqZ#{U)jW|${CoLZcz2S?8z)Ai-e^5KrIQDs6aUvkkef#%q`zy zuxP1(GIVd_tLtQuCf{R6NReJ8!n5pi%Z?+_YiCzymfXCr?(+NrF;QC0Lyp7zt#Tir z{oPrbNASwZGNV@MlqQg+!GQL7WJFO++_D_UqC1p;ui5~%lY+t?Jba`CT%Kj*aOW^r zhiUDaxmkaA&~$ipZt03}>7jdSGC5m}>o6hfx+hls4BE9WwP@~a0a)#YU`u@3p$*We zJRULoTGB2^MpFYIF8QoHB5h#m@o0|m^1Xi;`&9l5;HLuCT}?K4PCY1e=Z(SlVky0K z*?iKU2vTV}m~AG50cvE~{)hI<)`(kDw>N*3byeas|E+-KR9++T~Rk@u*IBAw^V37T;k^qLYJ zat2^;4|*(xX$L*!cp`0mE^l>QI|aE9#+A8FkEc$GZM|ZlB5Of`M{*!=xLSWLFf^Hz z-gMC(-*V;7)O4k^c$pDRKtO8vD~8q!8YF+k^kqmTt+S=y!dXt9LvNgG^BD=)W+>1i z`!Vf8l&NJs&uh!mtnvV0Nq=fv0FnytWh{unl~GiKYe7q9+#PB-UuQp%Adm{g_t3H(UEml7 zilN=P3qYl;7r#A@d@?4MoQo}iCv)EO@!l;hVvh%^5~q!RU8`^tF9`kA)beRvKDUR) z#IJ3A=d`bHYFj$B<+*~&jXk)4Hep?#b79osribL#gVgPqtmR_i|8DxR zu}*QpQ!>!z|Ipz*s-pncNIq$LM`hVQChs`;=o70|ESICS`E811QYDtaZ?Gm>&Wjhi zm6)2Z?m9q(E`6T4(E#RkH1S{-E;W2^D5m*JS0i_9FLI0tcgN%QUO_q6a0)3KvVnE( z)FFXJC~)QTuFwmZ0e|R>RFkhlqz%J9sNq`w^?*LCg)WQJ#iT*fetB@}`pgCpZ0V>n zi+PN-yv)Cnq6$SF%AcG>b-Wk%DQ}4_yTTA-D~uofU4rpGC(j+IDHB*65wN@E ze`Hl+(^6$Yxr1utDSO~?{CBoelge`%x@ysv?{1c-Dv(SxlqsK}hLLhJ5>v7lJdqat z{=$1Ir_6j}39snU)KRm?)Bd@xb=`z=y+y`^zNWEc$Mg3Z6@W4xc@n->HZiHqW(|c# zbbmFupZPf!3PXYHI{P)&NeCkY2lb>nb{CZ1U7ZLLPNRcmj>)buvQzUzRrMj^lx!_N zrE&SX%KGt5nxgftshD&TcdM?v(zKsP*>Cvdr*$)SP-RfBA))1hiODNo=JIZ|QALU830 zSoY{EFHNF9iU$7Z@MKj&(ZlfU`QOy_qoP7{9?eUntFYy)(*_k{9J<_7Mxe~Yf^B8h zI8u!WB{a{KGKhsS&4eJv$ig8d0!w13nWk3sC`U+kytv`r=bU$%GNzrqgB-YjKjJ^X z>EV87RoGBHxLxl>&+(9NUV_=d`mQQYQONjWhkZtdF2`|DvaG*!<W*A993cOOB|lV@Ex$AwAhVP+gPhm5qm6X$= zf+F9~L95`ku+Js_R#NR*8)m(k`&r43W)cK*pIT$&|5DBkWK}zSc ziN9>pJjzhVLvr9dp7tnqY-F1&+z-}P&8eWy4m?7n(3KCZstm}hHno40B-1mhVh?OA z{?EY!pRp+z6LSA6F!KhbUbt7{6%E>D($BD!uVzLRt%Wb{N&3LEs7* zqf4B+7f=T&vrWH9{`^ou`M;GC6Jg)%r1Z}+2s4lj0~B)N{ptd*o?{s!ia0%=c(>{B z%|d8Li(6zaik>JkIIkgzWr7(!uZGV3$%GvT$GN_-`TzN*qz%{Lx!SB_~j873Qor5bisZSg(-TUmFDLcr%AzgGAC-=?d_ni3Q$h?GMv ziLrrRF+%X+iI1|lF!Wp%ciKNGoq1r7k;=J{ANc298+nrnzl7+)Dl)R>X3r1vk3oF; zqy;Nb_n%u#Bh)$!0hU)I(Lxwtm}!GygsDoB7pfEp*w9 zz8K&6pYT1dqI7$<990^$!vzL@<#cBGM-%Y?4}#Rs6vnOPu1&{eFnbkyLjU(r&@Z^v z*<7-mn7L%~mKOwC$el5)ITmyzj6WtspSp@z%@k-lWYUDb`6_8<`)7f0MD_cDf&-M^ z3k%Lm_es*MG-LTNn*GnnH1N-lFbvu?DNn&vBH;gNJ`Cz@V-H(;GmnkXf*U!-M?N9* z6TfmKI}~^2cbResPSY#p=%aW1)v&qoSBvqFyyfJV_`0Af zWb{3W?6*7xu2ienHf+Mun2Oe>D}7GV&-~}K98RjKAB>bvLIdN;oeef}S?a<32j~|0 z_gCpFoNEPK$}Qy(ul(JPHa|}uTWLks5wQj`DN#=FV9)1m(_3MgdqpH{*5oAYbt!!z z__sprp3<@{yGm+_k*wMs|63*{CCb)9!Ee+lS`Rm*R&?j5XasTSsL3uSlNyx%&iL`y z&kzer$v%1koMf{!4cb_m-q+U0oQ&+&7T4iMP&kvz$~oF!HZaJjFGqqrXq=?eUG>5&c%53#OoUF?k;E|! zyVlU_{{&P)+!lxwr0^cj$4!hqaHtP8_tRvL1{NhT)0B9jpVFpH_-FE<%+0;;Q5)S@ z<4wXSVYz1gk!uh!!ZBqe8;)VPke1-+nRs49{?!ZHWwUslUeii5+ucm)b}`!bZp*L^ zc)TRxwB04w{?E;}GU^oze}WBHp@G5p&`qw!7}|#&A~lSBm&4I$)>OQaz9z0p8w;cV zDfBL*;S&Y?DJ}B3E!)%YyNt>xGS0 z{k*Eb>2&mJ0oT`Hr9+fi(+3_N{%*5Y{rAVW$en|p76QJvJ*vqK?*B=9XZ4q;-XEVzI845@(5j(VMQ0c+z`@cS zd{WUXVcr=xMpJ*7Y3eNk$@5x&pJI2Xy|xs-R^({}-BHZg3_4W_o=45;v4v_Y4NL&Q zT>kzA!)PEOO;)!GEfA=U;+S;1HRsRyc9YodC#pR~nnF1; zheTa{N)jR>%xDff!j|pa7VVOWF{s9RbSPssxZ%(1Y$h>=(KvzS_;J(W=JpmU*S;Q4 z%wq%IMwtJ9>iv_!iqqjWzQdNTt1?|I_sUhR5}=rzZ11LIb^h!vjZ7_x17$~7?aSd4 zThD%vap+83L9}AXSxq4|afVu<G*+dHWTSDhJ23;|A*Qwl+8-$;1!va00 zxb#MuyFBQQfVWr1@Bf<$jJ{Cd8BDej!7Y*DDML!kor)*MBgk>}4ty3_6f{L-P$V=h zP`Ka3QJ$z;w!jD{hw#Ru(s8$d*xkhm%6aE~Uj@lfGxHN;O5a@@W?eZ#AJX zZKk~R`b!E>3Ep!*UKSrY49zUTFF&?i=$a0v{lsmp5B<;|cXq-iI9Q~~HUltuAa3ya zi2}6T9|RlyEvkS0haP(a)t=k>9$7K9EcXCvY_n)*&)DswTc`TpZ;zmV-(7okXC^2r zF5ZJ6?dawPP>pE-(b2`S#!7LEE(6l8{yw}>qr!wX$00};3@Rc4%^{4pw>R**@I#Ao zS}Gs{0J3#r|23`wuz#iF7;=EU4UyFA8xR$>wi8S>mc@E@Tqco!X$v$ZW+SUqz`^C=*>f`c9>a7$fu%^7w+(8h z1I==eb?s8n8V{7@1ia=g;O?RLAm9(CSJ+FzQ$HRfF?^4`xx&93-2zG0mjV5l0uQ9{HZ-B7Z}Z(78Zw8=t>J!)9UQ}L8T~bOHfhNKUAGtlk_9h4+j+s+}zv{6o)wg*Ys(t znK$N?b0QFiBffNlsa^0v^xSVD;#_i|Pv<*x0!R;h+wI~R(}VrQgb-q^6lii&1s^wT zp*~X+fJcA{X|<#|wPj^j@;!njP?XNs&(?dbkVuA*%Vl2!Sc$t@ZCU~VD;r`z%jdWz zTl;&CH{3P#9324x@p^CRp}nr$IFZ{CAvINZzv3($n5)A)vf9%h?>M1m1}HfN1E^ZQ z9ps_LduHJyJ@!D4KfiaP_ zF}yPlYz!g9!-%~GC{ymf-2N5n(1ihT+WP0Oq?-T|loU664e+fEdbKxNh4#Hdh;U^t z2k+qGw zoY-}Lgb#=g?(TmT67ae(0Dl<~IFbei2Ed?;=eWM|b%hJvxF^&Q(38vcTj^$-0|a2I z6JX(C-&HFa&Q=xB!fkUw?bdIov!KL!0t-LXmj#n}mI2l5fDyK^z(et9c2u;Mo&PBK zkTSElmr0b3Df$!^mjQMEPZSBA@5}v>B!XtoS{nx48ipI4Dd+C+8E#3ZJm|Ev zP*V0NzdJ7&WR&Qw63xoE`FY}-^Bt0fFZ~-Po!t&n(o)B_@C-mS^)P2Omg^`@i4`O= znx<0noJOv<+;0w9j)<$spjJ2>yC^u)iuT2uZhEQ0k74}dWekcG+#yDfXzBEQo|7Wk zenDoI)FBX?5M4HIJa+nii;nBnD4w61Y|)m339nbe@>Qrd1e6Y?CE1yxY$UDXL?=Qk zDwGh=w}-Qp<>ht@i;9F*Rnxq0B#F^Im3$Qy$tKv@N0|B1(89hY+^T43^m3cYnvNE{ zKADb|hfaoYd~9~DKEsuujPfOy;_YUgw&HMkQO-YS%bD__U;TInU!TSBh`&70_;ktZ zc41pXc5Hk0=kvT+RaJ&NYs|q#QykHZmX@^kFK3y?XQ!vD47swhvg@0`6~64#*UNTaCd?^S zENl=MQ(y%9-dJQ7NBLX$xv5U9w@in&M3z3qJiw!mf}QW8R%W&T8fHTmd&FAY!J+sn zI6=ssz*RZXb&S%4m5nx!$sD`*DoMiQZhs>VLdog0Gzjxq7^Y(IQ`aYR=D zcPLxoW0BM?{?hkXNn>b^?Nv_=b8EQ#T?|dG<2-lKX^Eu!gVoPV&LtmtLm>g|%Q-|< z)obIEbO!wvR-a-Hy~h57+`he>>};-$gp?FwDXCy?u9WA@kx@}bf98$(^h)EITW$jN zx0nsSV$RtF5pmjdnbxwHZlw~uwS$3~()Ff_;6Hi$I+cS! zKO_R3gvYK=bZZPOZnPYKA|k1WKejygLhr8Ux}nqFBrc5XSR&7XyIQf{mqCjh$1OP> zhmofTExth_r^734LAUllbMUJz_l>=q5lg3}&CQY23S%J<3pzSFg12RY_4V9NJEMu* zf_GiLsV-eMwIkEh{+Tq>R%(9j3RV8H&dz9iQ?@_y-&9jodS2QLB(Z&^l8zs#wSDRL z`Q1F>%;@N-@D!7-f@fT;h6pxqy$;jq*=Z)b7XE$ti%;!e z5JuZ=ivN+#Ey7^IZXo4xSxkx$U{H8Z7!#oDjdv7e*6CPix_V zNmP>G1&wRr7FVlf!EvnU^PlQidKc{(hS+FqcZW$oZ)%WhxXmM-c7AtNU5-$((SIWBDR zH9J%B)?d-W_Fk6X+xbj7FM2;xfcQf4>lc^7YKy%wF*@Sy0q7jpwQZ{S;!5;rMG5yt4j;5!$WlY7UDUc6F-%L`=s- z7E2ech12Vq0H-iq2wqv?56({?St|gzhFtssNn8EkN3L|`@3@zml`n|Un460QsKwal zqhp>SW$v=%NU#^9Te323ewHYUqDGU6ec%!`PZW`cXi5@ge6oQxZ10L=0FgucwUV3E z-@Q4*&;cj^>^W?wLn37UwIQe83QFTWkC5aCP3!k~OI19>Pu`bMT@%q7wkLVSj`xLD zG^t|-B%20nuI8%bZb=xo z`bj_r*kN zDEhCU6o0Z|58sC2&u}aL-&g)di;q1bM{Yt)$D&td+CZb`WB#M|g_>Fj2~MG*A$ux0 z){uN)^;l>noCf^KD5rU+IdTHwjLVoAoQ-$Vtf0CicaPT@c}A%-ikN>1%v+RaMD{{$ zteOG;eUEGAOhltJ5q~Qgs<^rqe=$rLS;Q5XiAgOKb{i>Ppf zjPeGR5#=MNn&{?Av{`azxcPL0^a})4Ca?3s@;aN)B zZQzpq?qKS|Ub2lX@AIbdD$)A19Mn92JcBWd%_>7>%#a2dGQ$iQq*HADSSasN;RvDnhm?0Ve z(L=V_Iw(2{n>q)pC^%@TJj%#qUvE{!g>tX7Z&0EtI~RM1ot+(Ax^F39?jW04Mf40O?Of@MhD7o{O&`C;r$bIG#+8qq)avbS|H)g!6L9y~AhBR6oto|pVnq}{q$ z)oh#I!l2#aN7Wx^y~FsQvv7_0ea&U8);7JU8yU6%-GWO%71K~RZr1Mw8@H%_CM>H7 zrIyk@%jATNWpeS$FB4W?mfB6RY?ZurnR~W{tg;Twc(kj!*u{;sYg(~&`tULS+)}~$ zx``r^l^DG>;Ek1H9ls6JLFj>zxq^LCRVX5Hm!EFiL{Kmky!S}uOIgz zxu463P6{KvO^l0WvnMV%nU>jgjVLSGm3uO%8{lh^CkNEDl=0V}T-chbs&z~mxI~Da zV*Pg`h@6&+JbQ)XG8{$~kc*|Mr4s$^JOCAii7+2MR75)RfkQ)OqAnozEjjz(nM%Si zHYM_u)w~zQNnSxwzV(cFpyz*2F}Uwu{$vUP2nnjlvWgF53^O*l?)KYa`;bX1^6!I_ zESmJWr(wNP-)kl*2bowD_iqZOq`ZgLxyBRfkvMiIU#MW(ma$*t#3`x%_nLRoL6S>Z zU)#vPHQXnKg+(n3CC3I#6k%i?TJ&0Ks4;RVFcg~yj2?de7;4d^B$LWsTm4?rk)`j< zNVHh8tq_ZivxYmMW0;GXI1AD<5cEtBGh8>eq!la3L>e18;A$=6=eF?D-OqnE)0Tp2 zfDk;QVYQZWIA#)N7NK}16`QOMBoX$_Y>X^lA-YEhnU*SIK8L~tj|uvkztrwfX^Uj1UC9)7ccnSJC{UHZ z@x3cgC|PJ!)X}KRsRef(3k)5J4r;3*U*99^^Y@CdB3a+qteVs6k1I9+DibSSr~GS} zW^j91LRrH(v}ET@tkG&ienb**Y5fwTa$Zn1GFDl;2@{W$Q7pF4*^HAAmDJJx{vyP} zg;QyPf+{If%6w2P)!;P<1!oO75gD$Ix_r%MzeN1hXnEsD@7He* z>hTlLIwMq6)D$%|+Vnj4huqE5KM$I&g)p~nPH--o{QNcHFH8n4E*ysiz8rkD1(SJd z7u_SEuL;Y^RR``F%ZP>gD?dkbE34IBV|kNwBu|Hp4@xqBn?kT`MxLVnHR=za<&y@{8Z1p(En%{z}sLK|h& zO53+(>XMABvc~4hO*DZTxMD^*Z!g`H-7I!1`M8~DO^bg2YSQ0S)>9O+x5p&vr@z{m zx=wE1%#Y-C7*G_q1Fy2J!!-`iW)TLwe(K@o`w~xDTG~y2;|Q^BKY4-sloH(ryXkgB zqiQYC#7w!F-xR{!wQOTd7SBPeH{*DgEC6GW;p=)(eKz>Mgn!?zd$}mz+|xf6wdn>*toTVHBbgo2J^@gMqmY+;~5( zU#%nZClHGh*(!#0e@U7UWgkYJ=P6;-{1k*=5yb8lwW>5W?i^OH5WU{kw>6I%V1bv{ zQ15l#mj{#L`X}-3Xp;Bpcuk(5{N5$GdK0KXe!^m3hFG3md-45QS82Jq(I!kzdcpVc zRlKg3fVamb!jGm*KhGoIZ=f}e5UmEzm?0gn_WJd$4NO!7;L!Sdy&qC)$F)!Fe3fBu zIW^^3cTnP)zIBA=h|7o zEX5I4I-U`6`8zzuGN9o$C#IIxyZ1_LE~jMwUA`FzrWjmS%!Z*7m2#%4B~qkmNnexG zNzwCgbKDbS|Cevk{7F=J^9_>|Bt2-@A_=)ALq|i>xS9)XGe3nEkYS^>$WqaTrRFvK zxoXVn`exOcX>mtyRQ!{iY`HefWN#?B_{YWVz1Q_ct<$zbH!PUwt~|`wAFuD*9~-Yr zjB9rAv-wi6i}%R84f=n}Lh$aZJ7#suQS@&28~9~2Mc|PvxKP^*o4jC{0Zs3?`S99> zNBv$+a^k}-QPasJuhahY=LHvn{$$%${MtX`X6ajdwk^i#&6g(2ZLka)S42O5ib{GW zoOQ~7GCTXzoel_d7>r`sOWO6Fvh?BgOa-4=~6JWcPK z>R_q8kyf%gM3{rWM}CyV_&o99tv0s$kj|V+km;~&bx&Q52wR>7Yqv@$wY1$&CmpZI z@qF~~j}~l|HE_Q+6V&E{Y+KVmPwjmGq2vb*@|w#+fV!UN7u(k4b};R$yl-#OX^&(c zykJsXeg(-09N;WI9veG2IMl6yS2@L0w9U~N_0Z-0JWgDu0Kh+n#GY~!KkKo0C<4D~cG#q}2*ZE#`SbBzk+BG(| zJztOAL*i%xt!WmaC!dgzuy=TP$*;8P-@5aZ6Fd1`-`jGG6C>95O(vN&(pV)0B9;vf zoGTB#TIA1!&^inY`_x8ypNtXa6aXlNPktl5C_(o5$z|U-A^qsHwS;u|%p8lTO>6f^ z{))3G+M4^RkX5FU1WmHdfW^bOv9?WrO{Ef8-18 z<-E|PjqX~vdva4gBzSHkxTZcu?y%(^FDWVMYPHa|4m@c_v%!j6UEiGm2Jt0VQI+H?_iI9E^S=L=dwBUkbJZ_g^r4X?ubBJ-WVV6+c%f-`q!)dr8|qo^r{mRAIZWD-y^X3 z)aqV&kG%Yg5WcJ>S{hCusC@wbO`(us@gqHcdmc2{r08g!-a0dkPVz?JX+uCGZUY5G z$FQSshI%O}P4KVUC2x#2;QoYZ15@dHkFCEd>gaR@q7i{+ox=r8@tQ;5$g4lAI}a{I zLiplzI3D?x)RWvJji}^W5E>cOin7bfKHc3lG2U7HTUSsff{MrBut}ewKKjJl4g6uo zu*)TxQY|2m%%dm)#`02jaxUNf=Fh{<}EcNw)(|m{1S={SC z;PXo_p#ZZ3P}wP}XuQyEtgHK0+Ik%hP0Gxz4}r~pUJPqSzot4MixM&aLrl@GxFq16 zUtGABdMU78&AAA-9D^OP^~lut;uGwBEp#`!oouLIpXAkt&I7S}!y~E3pY8UekcU?< zP~qM<{vj5)K0n*e*yACUhY!Jf>>HwRon3FJ!s?qGSg#y1Ztqx9`m^MI>CJbxe`c+r zNPcZ*w;z=E#&wZd+k*(~*U>`}5etz7m)+?|5;3cudG}Uo_qV2^kczhjmY=(WrpA$M z$BHZ`a}rRdL(g`1`B%R&57}BHS@N*|ksOf3N~VY#|9CR$&n7->O^7hp?WR6Lr(hgS zO@$fWJ;C7jgT6b8?%gxkhBp1ZwN!Y0nIs+n@wgDM@f0){=a^RvKEKmn8}BnyR#xtu zE0td=@+eAl`x6lFwqE`^@z1z1s`cysDv^r%fduyCl{p(CZvo$Szq+mR6uV!P99GjG zXD_?ynNPMxb5My`DQ~`v_N4oC``pTW3$H&ryrrS1->6cSn;ng3yvDCI*~~_;7~v-}Dw0qIuokqq@u*KC!9wl-4gX!S4NIt9CjS6`#ECo~`tK>Tq;7dODBg>(1sgxBMC<>{DQdoy;Mp8G*2fEWHS0