From 65cfb976fa56e344711f5bf07d273b142bf51529 Mon Sep 17 00:00:00 2001 From: Marcus Minhorst <136136617+the-phinet@users.noreply.github.com> Date: Fri, 19 Sep 2025 22:49:56 -0400 Subject: [PATCH] Squashed 'libraries/ZWidget/' changes from 924b8e0e78..b5ae8c8ab0 (#3357) b5ae8c8ab0 Fix sdl2 initial paint a06bc7f8b4 Wayland Backend: Use unaccelerated dx and dy for OnMouseMoveRawEvent() f1789718d1 Wayland Backend: Implement clipboard support 11f3fe0e21 Fix a typo 2f4e3603e0 Add timers bcecfa9ee2 Fix filling with wrong window background sometimes 6681d2c2d8 Fix timer linked list 4564f7ea88 OnXInputEvent calls callbacks, which potentially could create/destroy windows 92f99a1abf Improve timer stability 4ccb1a03f8 Fix Cursor Shape protocol related crashes - If the protocol is available, avoid handling buffers directly fdefb2945d Add timers for X11 backend 25a2a2c846 WaylandDisplayBackend: New protocols and improvements - Add xdg_toplevel_icon and cursor_shape protocols support - Implement WaylandDisplayWindow::SetWindowIcon() based on the xdg_toplevel_icon protocol. d817b79b98 Fix compile error af5a87dba5 Add missing include statement 43996b7864 setIcon for x11 49ae157f31 Implement setIcon for Windows Fix spaces that should be tabs in the wayland backend a564526bb7 WaylandDisplayBackend: Combine LShift and RShift into Shift 46031df314 CMakeLists: Remove the unnecessary additional SDL2 check in the example section 5363f5354e Implement open folder dialog Improve support for paths with spaces in them bec2844e11 Fix the CI label for macOS e2c6af9e5d Seems CI was set to use a macOS version from 1865 991ca3377a Enable macOS in CI 1f5c95edef include SDL2 directory to fix mac compilation git-subtree-dir: libraries/ZWidget git-subtree-split: b5ae8c8ab038789298a8df1384322f0e69e57e5c --- .../ZWidget/.github/workflows/build.yaml | 44 +- libraries/ZWidget/CMakeLists.txt | 14 +- libraries/ZWidget/example/example.cpp | 30 +- .../example/surreal-engine-icon-128.png | Bin 0 -> 26320 bytes .../example/surreal-engine-icon-16.png | Bin 0 -> 896 bytes .../example/surreal-engine-icon-24.png | Bin 0 -> 1626 bytes .../example/surreal-engine-icon-256.png | Bin 0 -> 93253 bytes .../example/surreal-engine-icon-32.png | Bin 0 -> 2563 bytes .../example/surreal-engine-icon-48.png | Bin 0 -> 4909 bytes .../example/surreal-engine-icon-64.png | Bin 0 -> 7929 bytes .../ZWidget/include/zwidget/core/widget.h | 5 +- .../ZWidget/include/zwidget/window/window.h | 2 + libraries/ZWidget/src/core/timer.cpp | 6 +- libraries/ZWidget/src/core/widget.cpp | 14 +- .../src/window/dbus/dbus_open_file_dialog.cpp | 24 +- .../src/window/dbus/dbus_open_file_dialog.h | 2 + .../window/dbus/dbus_open_folder_dialog.cpp | 260 ++- .../src/window/dbus/dbus_open_folder_dialog.h | 7 +- .../src/window/sdl2/sdl2_display_window.cpp | 84 +- .../src/window/sdl2/sdl2_display_window.h | 1 + .../wayland/wayland_display_backend.cpp | 1924 +++++++++-------- .../window/wayland/wayland_display_backend.h | 214 +- .../window/wayland/wayland_display_window.cpp | 403 ++-- .../window/wayland/wayland_display_window.h | 176 +- .../src/window/wayland/wl_cursor_shape.cpp | 218 ++ .../src/window/wayland/wl_cursor_shape.hpp | 258 +++ .../window/wayland/wl_xdg_toplevel_icon.cpp | 267 +++ .../window/wayland/wl_xdg_toplevel_icon.hpp | 244 +++ .../src/window/win32/win32_display_window.cpp | 152 +- .../src/window/win32/win32_display_window.h | 4 + .../ZWidget/src/window/x11/x11_connection.cpp | 261 +++ .../ZWidget/src/window/x11/x11_connection.h | 65 + .../src/window/x11/x11_display_backend.cpp | 14 +- .../src/window/x11/x11_display_window.cpp | 371 ++-- .../src/window/x11/x11_display_window.h | 16 +- 35 files changed, 3467 insertions(+), 1613 deletions(-) create mode 100644 libraries/ZWidget/example/surreal-engine-icon-128.png create mode 100644 libraries/ZWidget/example/surreal-engine-icon-16.png create mode 100644 libraries/ZWidget/example/surreal-engine-icon-24.png create mode 100644 libraries/ZWidget/example/surreal-engine-icon-256.png create mode 100644 libraries/ZWidget/example/surreal-engine-icon-32.png create mode 100644 libraries/ZWidget/example/surreal-engine-icon-48.png create mode 100644 libraries/ZWidget/example/surreal-engine-icon-64.png create mode 100644 libraries/ZWidget/src/window/wayland/wl_cursor_shape.cpp create mode 100644 libraries/ZWidget/src/window/wayland/wl_cursor_shape.hpp create mode 100644 libraries/ZWidget/src/window/wayland/wl_xdg_toplevel_icon.cpp create mode 100644 libraries/ZWidget/src/window/wayland/wl_xdg_toplevel_icon.hpp create mode 100644 libraries/ZWidget/src/window/x11/x11_connection.cpp create mode 100644 libraries/ZWidget/src/window/x11/x11_connection.h diff --git a/libraries/ZWidget/.github/workflows/build.yaml b/libraries/ZWidget/.github/workflows/build.yaml index 396970061..6e8c51f6e 100644 --- a/libraries/ZWidget/.github/workflows/build.yaml +++ b/libraries/ZWidget/.github/workflows/build.yaml @@ -64,30 +64,30 @@ jobs: shell: bash run: cmake --build . --config $BUILD_TYPE -# macos-build: -# runs-on: macos-10.15 -# name: 🍎 macOS 10.15 -# steps: + macos-build: + runs-on: macos-latest + name: 🍎 macOS arm64 + steps: -# - name: 🧰 Checkout -# uses: actions/checkout@v2 -# with: -# fetch-depth: 0 -# submodules: true + - name: 🧰 Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + submodules: true -# - name: ⬇️ Install dependencies -# run: brew install sdl2 sdl2_mixer + - name: ⬇️ Install dependencies + run: brew install sdl2 sdl2_mixer -# - name: Create Build Environment -# run: cmake -E make_directory ${{runner.workspace}}/build + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build -# - name: Configure CMake -# shell: bash -# working-directory: ${{runner.workspace}}/build -# run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE + - name: Configure CMake + shell: bash + working-directory: ${{runner.workspace}}/build + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -# - name: Build -# working-directory: ${{runner.workspace}}/build -# shell: bash -# run: | -# cmake --build . --config $BUILD_TYPE + - name: Build + working-directory: ${{runner.workspace}}/build + shell: bash + run: | + cmake --build . --config $BUILD_TYPE diff --git a/libraries/ZWidget/CMakeLists.txt b/libraries/ZWidget/CMakeLists.txt index 17a9764ae..db78c31cb 100644 --- a/libraries/ZWidget/CMakeLists.txt +++ b/libraries/ZWidget/CMakeLists.txt @@ -146,6 +146,8 @@ set(ZWIDGET_X11_SOURCES src/window/x11/x11_display_backend.h src/window/x11/x11_display_window.cpp src/window/x11/x11_display_window.h + src/window/x11/x11_connection.cpp + src/window/x11/x11_connection.h ) set(ZWIDGET_WAYLAND_SOURCES @@ -155,6 +157,10 @@ set(ZWIDGET_WAYLAND_SOURCES src/window/wayland/wayland_display_window.h src/window/wayland/wl_fractional_scaling_protocol.cpp src/window/wayland/wl_fractional_scaling_protocol.hpp + src/window/wayland/wl_cursor_shape.cpp + src/window/wayland/wl_cursor_shape.hpp + src/window/wayland/wl_xdg_toplevel_icon.cpp + src/window/wayland/wl_xdg_toplevel_icon.hpp ) source_group("src" REGULAR_EXPRESSION "${CMAKE_CURRENT_SOURCE_DIR}/src/.+") @@ -273,7 +279,9 @@ target_link_options(zwidget PRIVATE ${ZWIDGET_LINK_OPTIONS}) target_link_libraries(zwidget ${ZWIDGET_LIBS}) set_target_properties(zwidget PROPERTIES CXX_STANDARD 20) target_compile_options(zwidget PRIVATE ${CXX_WARNING_FLAGS}) -target_include_directories(zwidget PRIVATE ${SDL2_INCLUDE_DIRS}) +if(SDL2_FOUND) + target_include_directories(zwidget PRIVATE ${SDL2_INCLUDE_DIRS}) +endif() if(MSVC) set_property(TARGET zwidget PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") @@ -282,10 +290,6 @@ endif() option(ZWIDGET_BUILD_EXAMPLE "Build the zwidget example application" ON) if(ZWIDGET_BUILD_EXAMPLE) - if (UNIX AND NOT APPLE) - find_package(SDL2 REQUIRED) - endif() - add_executable(zwidget_example WIN32 example/example.cpp example/picopng.cpp diff --git a/libraries/ZWidget/example/example.cpp b/libraries/ZWidget/example/example.cpp index 2f7747880..60fa8ecb0 100644 --- a/libraries/ZWidget/example/example.cpp +++ b/libraries/ZWidget/example/example.cpp @@ -86,6 +86,22 @@ LauncherWindow::LauncherWindow(): Widget(nullptr, WidgetType::Window) { SetWindowTitle("ZWidget Demo"); + try + { + SetWindowIcon({ + Image::LoadResource("surreal-engine-icon-16.png"), + Image::LoadResource("surreal-engine-icon-24.png"), + Image::LoadResource("surreal-engine-icon-32.png"), + Image::LoadResource("surreal-engine-icon-48.png"), + Image::LoadResource("surreal-engine-icon-64.png"), + Image::LoadResource("surreal-engine-icon-128.png"), + Image::LoadResource("surreal-engine-icon-256.png") + }); + } + catch (...) + { + } + Logo = new ImageBox(this); ExitButton = new PushButton(this); Pages = new TabWidget(this); @@ -298,18 +314,17 @@ std::vector LoadWidgetData(const std::string& name) return ReadAllBytes(name); } -enum class Backend { +enum class Backend +{ Default, Win32, SDL2, X11, Wayland }; -enum class Theme { +enum class Theme +{ Default, Light, Dark }; -int example( - Backend backend = Backend::Default, - Theme theme = Theme::Default -) +int example(Backend backend = Backend::Default, Theme theme = Theme::Default) { // just for testing themes switch (theme) @@ -344,6 +359,8 @@ int example( #ifdef WIN32 +#define WIN32_MEAN_AND_LEAN +#define NOMINMAX #include #include @@ -395,6 +412,7 @@ static std::vector ReadAllBytes(const std::string& filename) int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hInstPrev, PSTR cmdline, int cmdshow) { + SetProcessDPIAware(); example(); } diff --git a/libraries/ZWidget/example/surreal-engine-icon-128.png b/libraries/ZWidget/example/surreal-engine-icon-128.png new file mode 100644 index 0000000000000000000000000000000000000000..e79210f1cd4e394c081f29b1d9e97c650477e443 GIT binary patch literal 26320 zcmeAS@N?(olHy`uVBq!ia0y~yU}ykg4mJh`hQoG=rx_R+I14-?iy0W$6G519>fs&R z7#J8NOI#yLg7ec#$`gxH8OqDc^)mCai<1)zQuXqS(r3T3kz!y_VDNNt45^s&W^d($ zr#D0I|J?b#{C$3H^gHj=x8CccCPZ;?ZmDWv@Mcn!c0Q1i7GZOSM}oWEmq#$UTEf34 zeX^`!qVYqKykt*T-hGa0j;*M{Y1m6n&5hL*3L8M$*`&FlAnw^hIY^RarSZaZ&K z_cSk-I~`psYTk$bd$;@foy|4pzt0gmXZbJd@6*^%e)$j9Uz;0Szm%c=C*1<%uLUiT_v3NzwfO2`bv6k@%(>Mw%>oOyqvzC=ht2Z zPoYB^O6Nbl{rYwG{+zXcb)Vm9j_-b6r+YnKf;GcL%KP!hAMEGmSbi=S7Z>->T5DPS zjAw0F_rZh%?slI9-4+W5irl?-_pa^RIj7@a*_zB=TQ7e6mfQaY+5gYpxbLB;L-yZYKY#w5Z(H~0M?8O%!icx{g(=X3U+NT3r13`JCeaFP7eE7kI>eZtm_6tKa%{v&a3- zDl0GVT-0IVC(+v$v#+Lgb@=*4TNYi-y7>O=Uvlo|^df!zr@z`l8P3CkA^0C5a`|k70?f*)$H8VzKhlPjl zEP8rs@421nab0^k6rcIcwJXjxk#ftg{2a}3c!JJs0|}mvM>8~fIv#TAH1+BHxfYd4YcFOLeZN~i zS%tGuCb=ikXr_Twug7JPWS(OZYqr|_`EWQVCr63JC9vgrW&DlXx2vaCbM8x6ef8Jb zB^Uq9OJ1$5qpKSnrmF5Y=RvoB%|rv9{(nabePrB!I3Bq7PH(ziY*KA4^ZoCFhs3I@ z@BY6U_51vs;@|oQ3-_wLmUd%IOHbdw^={t(Pcx0v&za5k?US`GOL^A#YuoZxOwf`+)p;{nn(Zqk-5J9{*S=tcl7^Nep79!xIT09^E&I< zOXk>=N@eqgMsBQnBjIoRRV7eDuuU;gM6ks%mv>T&!iUa99VIr;9v$tr`Tb_|mMGmv z7Mknh_kZhOyY+qfxm5j=5C2x~yFWLKLGj4XsZ*cE$3#R_99|i`JR∾gyxaJnYO` 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 Y#|F_Nlr@Q~Wzsu$I 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 G5}E+bX0XNp literal 0 HcmV?d00001 diff --git a/libraries/ZWidget/example/surreal-engine-icon-24.png b/libraries/ZWidget/example/surreal-engine-icon-24.png new file mode 100644 index 0000000000000000000000000000000000000000..e7d197b540ae30ec61f46ca9cd215ef31a0864fe GIT binary patch literal 1626 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4mJh`h6m-gKNuJoI14-?iy0UglR%g;Uj0Ha z0|SF(iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$QVa~N&pllnLp(Zrr{2w% zNf$j<|NhLIGjA%&_ip}{m!n>oq@J5(of#7(^l-`qkK9?aLqr!eh$yb$3ZKI9QGCMf z7)60cOiLOB#2XkCJGfR&VY|2_vvaPA;nB<<^Ka`m|GU9oz5iKtoSuSc*PH{&vU9tO zzsxC)+gl~4xbz99!2gq(3z%K}vudQasEcY%_4*{8zvtuLb+7AEYisY$m_PsiV+%h) zh56_CWoOmwOHrNdSo!ne`n1K4nT;&FweOX64FNkG4eVu8msz@V8y$?RQ$M zepR~$8ZDHs$*Zlk6+LeF`0?Y4dE)nXe4MAc^vl2Xj13kQ9}=EetP2ofy}aq;--dO! zj~2%~(s0la3gu8x>q`y`4?hy-a%$QeCyiOlPXBH1Gd3{s6yP`%Fu}w)9RJ_^k+`H1l7fw(0QVr*rI5o)3PO{+PA=cEYs$0gs!Lw)2?)IN& zxqi>r-QHimzF&}kTW(48@r;;mmAQWHF?!!WA3b_>y%s~F#C|(tDju%TC;~`u)8xAYT9C3ccX<(=L|wYH;~FWHAJ7I(zPxy!F2MzB44NHZ!Y~ zG|xR}ZDYKxnP2`+`lZ>bx7UBVlrSxvUpF}af3ug>@%isdZv6Z+{p79Y7jFDQ8X}+8 z7VZ4AW=fD2!&I+S+sP;Y94y|wVpUgWs({mC`?yqQ-x0_X04UcUd{OZ-Pw)1>*hXZD=W^;qNUOOlGlcH_48&v;WYb`&13ej^YY5c0-;R-ii@9Hmeow+e}8BB_8_$nHj507 znCRM^udXa9DY<^=(4j?YEdlCkYS(t%T{p`^rQojEzT2)TthTnETBhx}H+g1XQ{k5F z?K3;neOUg&o?`Cuwch$zpa8nFPXx2^_OgGSj92PN=Nrx@Or(4nOgDn zpLfUn`d9h?@Avh~mrnVo{IF(`Jcr_!i4y~t-z*Xh^_}4Jb&K=_3ml(WZ%e z+fSqk-P!eXTh8{`VH+b#G z8yWQ(@C3)~sW7ZqC2v#KXk}sX!fdv$@AXm<*Mr;MZcCI17jX@2R+#as#A;jStQ*^U z6D0ySM)*wkQcYi-yPe13=Jx#i{cY{-*6w{WokctE^to>h(V8kRFW=uMZ-39W^wk%B v@1;SXJZ=0xJ^9aiaDRP*P=@{5kMXSmud26YoY>32z`)??>gTe~DWM4fliUkW literal 0 HcmV?d00001 diff --git a/libraries/ZWidget/example/surreal-engine-icon-256.png b/libraries/ZWidget/example/surreal-engine-icon-256.png new file mode 100644 index 0000000000000000000000000000000000000000..813ecc534e6cb1b98b7ed97f6c963d4ad490ab95 GIT binary patch literal 93253 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

xYo1C@2wKB%U};0< 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 BVe0?@ literal 0 HcmV?d00001 diff --git a/libraries/ZWidget/example/surreal-engine-icon-32.png b/libraries/ZWidget/example/surreal-engine-icon-32.png new file mode 100644 index 0000000000000000000000000000000000000000..7f75184820039092dc60a62d20d231a58613c594 GIT binary patch literal 2563 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 literal 0 HcmV?d00001 diff --git a/libraries/ZWidget/example/surreal-engine-icon-64.png b/libraries/ZWidget/example/surreal-engine-icon-64.png new file mode 100644 index 0000000000000000000000000000000000000000..790cb98926106a2039f7999cbbf0d5f708c970fb GIT binary patch literal 7929 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 literal 0 HcmV?d00001 diff --git a/libraries/ZWidget/include/zwidget/core/widget.h b/libraries/ZWidget/include/zwidget/core/widget.h index be41697cd..716e6b45a 100644 --- a/libraries/ZWidget/include/zwidget/core/widget.h +++ b/libraries/ZWidget/include/zwidget/core/widget.h @@ -40,8 +40,8 @@ public: std::string GetWindowTitle() const; void SetWindowTitle(const std::string& text); - // Icon GetWindowIcon() const; - // void SetWindowIcon(const Icon& icon); + std::vector> GetWindowIcon() const; + void SetWindowIcon(const std::vector>& images); // Widget content box Size GetSize() const; @@ -233,6 +233,7 @@ private: Colorf WindowBackground = Colorf::fromRgba8(240, 240, 240); std::string WindowTitle; + std::vector> WindowIcon; std::unique_ptr DispWindow; std::unique_ptr DispCanvas; Widget* FocusWidget = nullptr; diff --git a/libraries/ZWidget/include/zwidget/window/window.h b/libraries/ZWidget/include/zwidget/window/window.h index ad59f1f02..bb0581d51 100644 --- a/libraries/ZWidget/include/zwidget/window/window.h +++ b/libraries/ZWidget/include/zwidget/window/window.h @@ -26,6 +26,7 @@ class Widget; class OpenFileDialog; class SaveFileDialog; class OpenFolderDialog; +class Image; enum class StandardCursor { @@ -163,6 +164,7 @@ public: virtual ~DisplayWindow() = default; virtual void SetWindowTitle(const std::string& text) = 0; + virtual void SetWindowIcon(const std::vector>& images) = 0; virtual void SetWindowFrame(const Rect& box) = 0; virtual void SetClientFrame(const Rect& box) = 0; virtual void Show() = 0; diff --git a/libraries/ZWidget/src/core/timer.cpp b/libraries/ZWidget/src/core/timer.cpp index ba62a5ab3..1e395ad59 100644 --- a/libraries/ZWidget/src/core/timer.cpp +++ b/libraries/ZWidget/src/core/timer.cpp @@ -5,9 +5,9 @@ Timer::Timer(Widget* owner) : OwnerObj(owner) { - PrevTimerObj = owner->FirstTimerObj; - if (PrevTimerObj) - PrevTimerObj->PrevTimerObj = this; + NextTimerObj = owner->FirstTimerObj; + if (NextTimerObj) + NextTimerObj->PrevTimerObj = this; owner->FirstTimerObj = this; } diff --git a/libraries/ZWidget/src/core/widget.cpp b/libraries/ZWidget/src/core/widget.cpp index 27f6f7b1c..7018a5045 100644 --- a/libraries/ZWidget/src/core/widget.cpp +++ b/libraries/ZWidget/src/core/widget.cpp @@ -173,6 +173,18 @@ void Widget::SetWindowTitle(const std::string& text) } } +std::vector> Widget::GetWindowIcon() const +{ + return WindowIcon; +} + +void Widget::SetWindowIcon(const std::vector>& images) +{ + WindowIcon = images; + if (DispWindow) + DispWindow->SetWindowIcon(WindowIcon); +} + Size Widget::GetSize() const { return ContentGeometry.size(); @@ -365,7 +377,7 @@ void Widget::Repaint() Widget* w = Window(); if (w->DispCanvas) { - w->DispCanvas->begin(WindowBackground); + w->DispCanvas->begin(w->WindowBackground); w->Paint(w->DispCanvas.get()); w->DispCanvas->end(); } diff --git a/libraries/ZWidget/src/window/dbus/dbus_open_file_dialog.cpp b/libraries/ZWidget/src/window/dbus/dbus_open_file_dialog.cpp index 1ebc0668f..22c6c139b 100644 --- a/libraries/ZWidget/src/window/dbus/dbus_open_file_dialog.cpp +++ b/libraries/ZWidget/src/window/dbus/dbus_open_file_dialog.cpp @@ -281,8 +281,30 @@ bool DBusOpenFileDialog::Show() for (const std::string& uri : uris) { if (uri.size() > 7 && uri.substr(0, 7) == "file://") - outputFilenames.push_back(uri.substr(7)); + outputFilenames.push_back(unescapeUri(uri.substr(7))); } return !uris.empty(); } + +std::string DBusOpenFileDialog::unescapeUri(const std::string& s) +{ + // This needs to be much more advanced to support any kind of uri. Why did they have to pass it as an uri!? idiots. + std::string result; + result.reserve(s.size()); + size_t i = 0; + while (i < s.length()) + { + if (s[i] == '%' && i + 2 < s.length() && s[i + 1] == '2' && s[i + 2] == '0') + { + result += ' '; + i += 3; + } + else + { + result += s[i]; + i++; + } + } + return result; +} diff --git a/libraries/ZWidget/src/window/dbus/dbus_open_file_dialog.h b/libraries/ZWidget/src/window/dbus/dbus_open_file_dialog.h index f4efd2663..bf0737154 100644 --- a/libraries/ZWidget/src/window/dbus/dbus_open_file_dialog.h +++ b/libraries/ZWidget/src/window/dbus/dbus_open_file_dialog.h @@ -20,6 +20,8 @@ public: bool Show() override; private: + static std::string unescapeUri(const std::string& s); + std::string ownerHandle; std::string title; std::string initialDirectory; diff --git a/libraries/ZWidget/src/window/dbus/dbus_open_folder_dialog.cpp b/libraries/ZWidget/src/window/dbus/dbus_open_folder_dialog.cpp index 7bcd94953..9153d64e2 100644 --- a/libraries/ZWidget/src/window/dbus/dbus_open_folder_dialog.cpp +++ b/libraries/ZWidget/src/window/dbus/dbus_open_folder_dialog.cpp @@ -1,5 +1,8 @@ #include "dbus_open_folder_dialog.h" +#include +#include +#include DBusOpenFolderDialog::DBusOpenFolderDialog(std::string ownerHandle) : ownerHandle(ownerHandle) { @@ -7,20 +10,271 @@ DBusOpenFolderDialog::DBusOpenFolderDialog(std::string ownerHandle) : ownerHandl bool DBusOpenFolderDialog::Show() { - return false; + // https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.FileChooser.html + + dbus_bool_t bresult = {}; + DBusError error = {}; + dbus_error_init(&error); + + DBusConnection* connection = dbus_bus_get(DBUS_BUS_SESSION, &error); + if (!connection) + { + dbus_error_free(&error); + return false; + } + + std::string busname = dbus_bus_get_unique_name(connection); + + DBusMessage* request = dbus_message_new_method_call("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", "org.freedesktop.portal.FileChooser", "OpenFile"); + if (!request) + { + dbus_connection_unref(connection); + dbus_error_free(&error); + return false; + } + + const char* parentWindow = ownerHandle.c_str(); + const char* title = this->title.c_str(); + + DBusMessageIter requestArgs = {}; + dbus_message_iter_init_append(request, &requestArgs); + + bresult = dbus_message_iter_append_basic(&requestArgs, DBUS_TYPE_STRING, &parentWindow); + bresult = dbus_message_iter_append_basic(&requestArgs, DBUS_TYPE_STRING, &title); + + DBusMessageIter requestOptions = {}; + bresult = dbus_message_iter_open_container(&requestArgs, DBUS_TYPE_ARRAY, "{sv}", &requestOptions); + + // handle_token - s race condition prevention for signal (/org/freedesktop/portal/desktop/request/SENDER/TOKEN) + // accept_label - s text label for the OK button + // modal - b makes dialog modal. Defaults to true. Not really sure what it means since nothing stayed modal on my computer + // directory - b open folder mode, added in version 3 + // filters - a(sa(us)) [('Images', [(0, '*.ico'), (1, 'image/png')]), ('Text', [(0, '*.txt')])] + // current_filter - sa(us) filter from filters that should be current filter + // choices - a(ssa(ss)s) list of serialized combo boxes to add to the file chooser + // current_name - s suggested name + + // current_folder - ay + if (!initialDirectory.empty()) + { + // Note: the docs unfortunately says "The portal implementation is free to ignore this option" + + const char* key = "current_folder"; + const char* value = initialDirectory.c_str(); + int valueCount = (int)initialDirectory.size() + 1; + + DBusMessageIter entry = {}; + bresult = dbus_message_iter_open_container(&requestOptions, DBUS_TYPE_DICT_ENTRY, nullptr, &entry); + bresult = dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key); + + DBusMessageIter variant = {}; + DBusMessageIter array = {}; + bresult = dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "ay", &variant); + bresult = dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE_AS_STRING, &array); + bresult = dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &value, valueCount); + bresult = dbus_message_iter_close_container(&variant, &array); + bresult = dbus_message_iter_close_container(&entry, &variant); + + bresult = dbus_message_iter_close_container(&requestOptions, &entry); + } + + // directory - b + { + const char* key = "directory"; + dbus_bool_t value = true; + + DBusMessageIter entry = {}; + bresult = dbus_message_iter_open_container(&requestOptions, DBUS_TYPE_DICT_ENTRY, nullptr, &entry); + bresult = dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &key); + + DBusMessageIter variant = {}; + bresult = dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, DBUS_TYPE_BOOLEAN_AS_STRING, &variant); + bresult = dbus_message_iter_append_basic(&variant, DBUS_TYPE_BOOLEAN, &value); + bresult = dbus_message_iter_close_container(&entry, &variant); + + bresult = dbus_message_iter_close_container(&requestOptions, &entry); + } + + bresult = dbus_message_iter_close_container(&requestArgs, &requestOptions); + + DBusMessage* response = dbus_connection_send_with_reply_and_block(connection, request, DBUS_TIMEOUT_USE_DEFAULT, &error); + if (!response) + { + dbus_message_unref(request); + dbus_connection_unref(connection); + dbus_error_free(&error); + return false; + } + + const char* handle = nullptr; + bresult = dbus_message_get_args(response, &error, DBUS_TYPE_OBJECT_PATH, &handle, DBUS_TYPE_INVALID); + if (!bresult) + { + dbus_message_unref(response); + dbus_message_unref(request); + dbus_connection_unref(connection); + dbus_error_free(&error); + return false; + } + + std::string signalObjectPath = handle; + + dbus_message_unref(response); + dbus_message_unref(request); + + std::string rule = "type='signal',interface='org.freedesktop.portal.Request',member='Response',path='" + signalObjectPath + "'"; + dbus_bus_add_match(connection, rule.c_str(), &error); + + // Wait for the response signal + // + // To do: process the run loop while we wait + // + DBusMessage* signalmsg = nullptr; + while (!signalmsg && dbus_connection_read_write(connection, -1)) + { + while (true) + { + DBusMessage* message = dbus_connection_pop_message(connection); + if (!message) + break; + + if (dbus_message_is_signal(message, "org.freedesktop.portal.Request", "Response") && dbus_message_get_path(message) == signalObjectPath) + { + signalmsg = message; + break; + } + else + { + dbus_message_unref(message); + } + } + } + dbus_bus_remove_match(connection, rule.c_str(), &error); + + // Read the response + + dbus_uint32_t responseCode = 0; + std::vector uris; + + DBusMessageIter signalArgs; + bresult = dbus_message_iter_init(signalmsg, &signalArgs); + + // response code - u + if (dbus_message_iter_get_arg_type(&signalArgs) == DBUS_TYPE_UINT32) + { + dbus_message_iter_get_basic(&signalArgs, &responseCode); + } + dbus_message_iter_next(&signalArgs); + + // results - a{sv} + if (dbus_message_iter_get_arg_type(&signalArgs) == DBUS_TYPE_ARRAY) + { + DBusMessageIter resultsArray = {}; + dbus_message_iter_recurse(&signalArgs, &resultsArray); + while (true) + { + int type = dbus_message_iter_get_arg_type(&resultsArray); + if (type != DBUS_TYPE_DICT_ENTRY) + break; + + DBusMessageIter entry = {}; + dbus_message_iter_recurse(&resultsArray, &entry); + + const char* key = nullptr; + dbus_message_iter_get_basic(&entry, &key); + dbus_message_iter_next(&entry); + + DBusMessageIter value = {}; + dbus_message_iter_recurse(&entry, &value); + + std::string k = key; + if (k == "uris" && dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_ARRAY) // as + { + DBusMessageIter uriArray = {}; + dbus_message_iter_recurse(&value, &uriArray); + while (true) + { + int type = dbus_message_iter_get_arg_type(&uriArray); + if (type != DBUS_TYPE_STRING) + break; + + const char* uri = nullptr; + dbus_message_iter_get_basic(&uriArray, &uri); + uris.push_back(uri); + + dbus_message_iter_next(&uriArray); + } + } + else if (k == "choices" && dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_ARRAY) // a(ss) + { + // An array of pairs of strings, + // the first string being the ID of a combobox that was passed into this call, + // the second string being the selected option. + } + else if (k == "current_filter" && dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_STRUCT) // sa(us) + { + // The filter that was selected. + // This may match a filter in the filter list or another filter that was applied unconditionally. + } + + dbus_message_iter_next(&entry); + dbus_message_iter_next(&resultsArray); + } + } + dbus_message_iter_next(&signalArgs); + + dbus_message_unref(signalmsg); + dbus_connection_unref(connection); + dbus_error_free(&error); + + if (responseCode != 0) // User cancelled + return false; + + for (const std::string& uri : uris) + { + if (uri.size() > 7 && uri.substr(0, 7) == "file://") + { + selectedPath = unescapeUri(uri.substr(7)); + break; + } + } + + return !selectedPath.empty(); } std::string DBusOpenFolderDialog::SelectedPath() const { - return selected_path; + return selectedPath; } void DBusOpenFolderDialog::SetInitialDirectory(const std::string& path) { - initial_directory = path; + initialDirectory = path; } void DBusOpenFolderDialog::SetTitle(const std::string& newtitle) { title = newtitle; } + +std::string DBusOpenFolderDialog::unescapeUri(const std::string& s) +{ + // This needs to be much more advanced to support any kind of uri. Why did they have to pass it as an uri!? idiots. + std::string result; + result.reserve(s.size()); + size_t i = 0; + while (i < s.length()) + { + if (s[i] == '%' && i + 2 < s.length() && s[i + 1] == '2' && s[i + 2] == '0') + { + result += ' '; + i += 3; + } + else + { + result += s[i]; + i++; + } + } + return result; +} diff --git a/libraries/ZWidget/src/window/dbus/dbus_open_folder_dialog.h b/libraries/ZWidget/src/window/dbus/dbus_open_folder_dialog.h index 3e40cc7bc..059443979 100644 --- a/libraries/ZWidget/src/window/dbus/dbus_open_folder_dialog.h +++ b/libraries/ZWidget/src/window/dbus/dbus_open_folder_dialog.h @@ -13,9 +13,10 @@ public: void SetTitle(const std::string& newtitle) override; private: - std::string ownerHandle; + static std::string unescapeUri(const std::string& s); - std::string selected_path; - std::string initial_directory; + std::string ownerHandle; std::string title; + std::string initialDirectory; + std::string selectedPath; }; diff --git a/libraries/ZWidget/src/window/sdl2/sdl2_display_window.cpp b/libraries/ZWidget/src/window/sdl2/sdl2_display_window.cpp index 44bdfb0ec..dea0b8f0f 100644 --- a/libraries/ZWidget/src/window/sdl2/sdl2_display_window.cpp +++ b/libraries/ZWidget/src/window/sdl2/sdl2_display_window.cpp @@ -1,4 +1,3 @@ - #include "sdl2_display_window.h" #include #include @@ -33,7 +32,7 @@ SDL2DisplayWindow::SDL2DisplayWindow(DisplayWindowHost* windowHost, bool popupWi if (result != 0) throw std::runtime_error(std::string("Unable to create SDL window:") + SDL_GetError()); } - + WindowList[SDL_GetWindowID(Handle.window)] = this; } @@ -83,6 +82,12 @@ void SDL2DisplayWindow::SetWindowTitle(const std::string& text) SDL_SetWindowTitle(Handle.window, text.c_str()); } +void SDL2DisplayWindow::SetWindowIcon(const std::vector>& images) +{ + // To do: call SDL_SetWindowIcon for the highest resolution image (or maybe the first 32x32 or higher? not sure what is best here) + // To do: when this is upgraded to SDL3, call SDL_AddSurfaceAlternateImage for all the images +} + void SDL2DisplayWindow::SetWindowFrame(const Rect& box) { // SDL2 doesn't really seem to have an API for this. @@ -107,14 +112,12 @@ void SDL2DisplayWindow::SetClientFrame(const Rect& box) void SDL2DisplayWindow::Show() { SDL_ShowWindow(Handle.window); - WindowHost->OnWindowPaint(); } void SDL2DisplayWindow::ShowFullscreen() { SDL_ShowWindow(Handle.window); SDL_SetWindowFullscreen(Handle.window, SDL_WINDOW_FULLSCREEN_DESKTOP); - WindowHost->OnWindowPaint(); isFullscreen = true; } @@ -122,14 +125,12 @@ void SDL2DisplayWindow::ShowMaximized() { SDL_ShowWindow(Handle.window); SDL_MaximizeWindow(Handle.window); - WindowHost->OnWindowPaint(); } void SDL2DisplayWindow::ShowMinimized() { SDL_ShowWindow(Handle.window); SDL_MinimizeWindow(Handle.window); - WindowHost->OnWindowPaint(); } void SDL2DisplayWindow::ShowNormal() @@ -137,7 +138,6 @@ void SDL2DisplayWindow::ShowNormal() SDL_ShowWindow(Handle.window); SDL_SetWindowFullscreen(Handle.window, 0); isFullscreen = false; - WindowHost->OnWindowPaint(); } bool SDL2DisplayWindow::IsWindowFullscreen() @@ -427,14 +427,14 @@ SDL2DisplayWindow* SDL2DisplayWindow::FindEventWindow(const SDL_Event& event) int windowID; switch (event.type) { - case SDL_WINDOWEVENT: windowID = event.window.windowID; break; - case SDL_TEXTINPUT: windowID = event.text.windowID; break; - case SDL_KEYUP: windowID = event.key.windowID; break; - case SDL_KEYDOWN: windowID = event.key.windowID; break; - case SDL_MOUSEBUTTONUP: windowID = event.button.windowID; break; + case SDL_WINDOWEVENT: windowID = event.window.windowID; break; + case SDL_TEXTINPUT: windowID = event.text.windowID; break; + case SDL_KEYUP: windowID = event.key.windowID; break; + case SDL_KEYDOWN: windowID = event.key.windowID; break; + case SDL_MOUSEBUTTONUP: windowID = event.button.windowID; break; case SDL_MOUSEBUTTONDOWN: windowID = event.button.windowID; break; - case SDL_MOUSEWHEEL: windowID = event.wheel.windowID; break; - case SDL_MOUSEMOTION: windowID = event.motion.windowID; break; + case SDL_MOUSEWHEEL: windowID = event.wheel.windowID; break; + case SDL_MOUSEMOTION: windowID = event.motion.windowID; break; default: if (event.type == PaintEventNumber) windowID = event.user.windowID; else return nullptr; @@ -455,14 +455,14 @@ void SDL2DisplayWindow::DispatchEvent(const SDL_Event& event) switch (event.type) { - case SDL_WINDOWEVENT: window->OnWindowEvent(event.window); break; - case SDL_TEXTINPUT: window->OnTextInput(event.text); break; - case SDL_KEYUP: window->OnKeyUp(event.key); break; - case SDL_KEYDOWN: window->OnKeyDown(event.key); break; - case SDL_MOUSEBUTTONUP: window->OnMouseButtonUp(event.button); break; + case SDL_WINDOWEVENT: window->OnWindowEvent (event.window); break; + case SDL_TEXTINPUT: window->OnTextInput (event.text); break; + case SDL_KEYUP: window->OnKeyUp (event.key); break; + case SDL_KEYDOWN: window->OnKeyDown (event.key); break; + case SDL_MOUSEBUTTONUP: window->OnMouseButtonUp (event.button); break; case SDL_MOUSEBUTTONDOWN: window->OnMouseButtonDown(event.button); break; - case SDL_MOUSEWHEEL: window->OnMouseWheel(event.wheel); break; - case SDL_MOUSEMOTION: window->OnMouseMotion(event.motion); break; + case SDL_MOUSEWHEEL: window->OnMouseWheel (event.wheel); break; + case SDL_MOUSEMOTION: window->OnMouseMotion (event.motion); break; default: if (event.type == PaintEventNumber) window->OnPaintEvent(); } @@ -472,13 +472,39 @@ void SDL2DisplayWindow::OnWindowEvent(const SDL_WindowEvent& event) { switch (event.event) { - case SDL_WINDOWEVENT_CLOSE: WindowHost->OnWindowClose(); break; - case SDL_WINDOWEVENT_MOVED: WindowHost->OnWindowGeometryChanged(); break; - case SDL_WINDOWEVENT_RESIZED: WindowHost->OnWindowGeometryChanged(); WindowHost->OnWindowPaint(); break; - case SDL_WINDOWEVENT_SHOWN: WindowHost->OnWindowPaint(); break; - case SDL_WINDOWEVENT_EXPOSED: WindowHost->OnWindowPaint(); break; - case SDL_WINDOWEVENT_FOCUS_GAINED: WindowHost->OnWindowActivated(); break; - case SDL_WINDOWEVENT_FOCUS_LOST: WindowHost->OnWindowDeactivated(); break; + case SDL_WINDOWEVENT_CLOSE: + WindowHost->OnWindowClose(); + break; + + case SDL_WINDOWEVENT_MOVED: + case SDL_WINDOWEVENT_SIZE_CHANGED: + case SDL_WINDOWEVENT_RESIZED: + WindowHost->OnWindowGeometryChanged(); + break; + + case SDL_WINDOWEVENT_SHOWN: + case SDL_WINDOWEVENT_EXPOSED: + WindowHost->OnWindowPaint(); + break; + + case SDL_WINDOWEVENT_FOCUS_GAINED: + case SDL_WINDOWEVENT_FOCUS_LOST: + WindowHost->OnWindowDeactivated(); + break; + + case SDL_WINDOWEVENT_NONE: + case SDL_WINDOWEVENT_HIDDEN: + case SDL_WINDOWEVENT_MINIMIZED: + case SDL_WINDOWEVENT_MAXIMIZED: + case SDL_WINDOWEVENT_RESTORED: + case SDL_WINDOWEVENT_ENTER: + case SDL_WINDOWEVENT_LEAVE: + case SDL_WINDOWEVENT_TAKE_FOCUS: + case SDL_WINDOWEVENT_HIT_TEST: + case SDL_WINDOWEVENT_ICCPROF_CHANGED: + case SDL_WINDOWEVENT_DISPLAY_CHANGED: + // nope + break; } } @@ -792,5 +818,5 @@ SDL_Scancode SDL2DisplayWindow::InputKeyToScancode(InputKey inputkey) case InputKey::RControl: return SDL_SCANCODE_RCTRL; case InputKey::Tilde: return SDL_SCANCODE_GRAVE; default: return (SDL_Scancode)0; - } + } } diff --git a/libraries/ZWidget/src/window/sdl2/sdl2_display_window.h b/libraries/ZWidget/src/window/sdl2/sdl2_display_window.h index 5a510878e..759980db8 100644 --- a/libraries/ZWidget/src/window/sdl2/sdl2_display_window.h +++ b/libraries/ZWidget/src/window/sdl2/sdl2_display_window.h @@ -13,6 +13,7 @@ public: ~SDL2DisplayWindow(); void SetWindowTitle(const std::string& text) override; + void SetWindowIcon(const std::vector>& images) override; void SetWindowFrame(const Rect& box) override; void SetClientFrame(const Rect& box) override; void Show() override; diff --git a/libraries/ZWidget/src/window/wayland/wayland_display_backend.cpp b/libraries/ZWidget/src/window/wayland/wayland_display_backend.cpp index d99283248..f969b25df 100644 --- a/libraries/ZWidget/src/window/wayland/wayland_display_backend.cpp +++ b/libraries/ZWidget/src/window/wayland/wayland_display_backend.cpp @@ -1,5 +1,6 @@ #include "wayland_display_backend.h" #include "wayland_display_window.h" +#include #ifdef USE_DBUS #include "window/dbus/dbus_open_file_dialog.h" @@ -8,469 +9,496 @@ #endif WaylandDisplayBackend::WaylandDisplayBackend() + :m_DataOfferMimeTypes(0, hash_proxy) { - if (!s_waylandDisplay) - throw std::runtime_error("Wayland Display initialization failed!"); + if (!s_waylandDisplay) + throw std::runtime_error("Wayland Display initialization failed!"); - s_waylandRegistry = s_waylandDisplay.get_registry(); + s_waylandRegistry = s_waylandDisplay.get_registry(); - s_waylandRegistry.on_global() = [&](uint32_t name, std::string interface, uint32_t version) { - if (interface == wayland::compositor_t::interface_name) - s_waylandRegistry.bind(name, m_waylandCompositor, 3); - if (interface == wayland::shm_t::interface_name) - s_waylandRegistry.bind(name, m_waylandSHM, version); - if (interface == wayland::output_t::interface_name) - s_waylandRegistry.bind(name, m_waylandOutput, version); - if (interface == wayland::seat_t::interface_name) - s_waylandRegistry.bind(name, m_waylandSeat, 8); - if (interface == wayland::data_device_manager_t::interface_name) - s_waylandRegistry.bind(name, m_DataDeviceManager, 3); - if (interface == wayland::xdg_wm_base_t::interface_name) - s_waylandRegistry.bind(name, m_XDGWMBase, 4); - if (interface == wayland::zxdg_output_manager_v1_t::interface_name) - s_waylandRegistry.bind(name, m_XDGOutputManager, version); - if (interface == wayland::zxdg_exporter_v2_t::interface_name) - s_waylandRegistry.bind(name, m_XDGExporter, 1); - if (interface == wayland::zwp_pointer_constraints_v1_t::interface_name) - s_waylandRegistry.bind(name, m_PointerConstraints, 1); - if (interface == wayland::xdg_activation_v1_t::interface_name) - s_waylandRegistry.bind(name, m_XDGActivation, 1); - if (interface == wayland::zxdg_decoration_manager_v1_t::interface_name) - s_waylandRegistry.bind(name, m_XDGDecorationManager, 1); - if (interface == wayland::fractional_scale_manager_v1_t::interface_name) - s_waylandRegistry.bind(name, m_FractionalScaleManager, 1); - if (interface == wayland::zwp_relative_pointer_manager_v1_t::interface_name) - s_waylandRegistry.bind(name, m_RelativePointerManager, 1); - }; + s_waylandRegistry.on_global() = [&](uint32_t name, std::string interface, uint32_t version) { + if (interface == wayland::compositor_t::interface_name) + s_waylandRegistry.bind(name, m_waylandCompositor, 3); + if (interface == wayland::shm_t::interface_name) + s_waylandRegistry.bind(name, m_waylandSHM, version); + if (interface == wayland::output_t::interface_name) + s_waylandRegistry.bind(name, m_waylandOutput, version); + if (interface == wayland::seat_t::interface_name) + s_waylandRegistry.bind(name, m_waylandSeat, 8); + if (interface == wayland::data_device_manager_t::interface_name) + s_waylandRegistry.bind(name, m_DataDeviceManager, 3); + if (interface == wayland::xdg_wm_base_t::interface_name) + s_waylandRegistry.bind(name, m_XDGWMBase, 4); + if (interface == wayland::zxdg_output_manager_v1_t::interface_name) + s_waylandRegistry.bind(name, m_XDGOutputManager, version); + if (interface == wayland::zxdg_exporter_v2_t::interface_name) + s_waylandRegistry.bind(name, m_XDGExporter, 1); + if (interface == wayland::zwp_pointer_constraints_v1_t::interface_name) + s_waylandRegistry.bind(name, m_PointerConstraints, 1); + if (interface == wayland::xdg_activation_v1_t::interface_name) + s_waylandRegistry.bind(name, m_XDGActivation, 1); + if (interface == wayland::zxdg_decoration_manager_v1_t::interface_name) + s_waylandRegistry.bind(name, m_XDGDecorationManager, 1); + if (interface == wayland::fractional_scale_manager_v1_t::interface_name) + s_waylandRegistry.bind(name, m_FractionalScaleManager, 1); + if (interface == wayland::zwp_relative_pointer_manager_v1_t::interface_name) + s_waylandRegistry.bind(name, m_RelativePointerManager, 1); + if (interface == wayland::xdg_toplevel_icon_manager_v1_t::interface_name) + s_waylandRegistry.bind(name, m_XDGToplevelIconManager, 1); + if (interface == wayland::cursor_shape_manager_v1_t::interface_name) + s_waylandRegistry.bind(name, m_CursorShapeManager, 2); + }; - s_waylandDisplay.roundtrip(); + s_waylandDisplay.roundtrip(); - if (!m_XDGWMBase) - throw std::runtime_error("WaylandDisplayBackend: XDG-Shell is required!"); + if (!m_XDGWMBase) + throw std::runtime_error("WaylandDisplayBackend: XDG-Shell is required!"); - if (!m_XDGOutputManager) - throw std::runtime_error("WaylandDisplayBackend: xdg-output-manager-v1 is required!"); + if (!m_XDGOutputManager) + throw std::runtime_error("WaylandDisplayBackend: xdg-output-manager-v1 is required!"); - if (!m_XDGExporter) - throw std::runtime_error("WaylandDisplayBackend: xdg-foreign-unstable-v2 is required!"); + if (!m_XDGExporter) + throw std::runtime_error("WaylandDisplayBackend: xdg-foreign-unstable-v2 is required!"); - if (!m_PointerConstraints) - throw std::runtime_error("WaylandDisplayBackend: pointer-constrains-unstable-v1 is required!"); + if (!m_PointerConstraints) + throw std::runtime_error("WaylandDisplayBackend: pointer-constrains-unstable-v1 is required!"); - if (!m_RelativePointerManager) - throw std::runtime_error("WaylandDisplayBackend: relative-pointer-unstable-v1 is required!"); + if (!m_RelativePointerManager) + throw std::runtime_error("WaylandDisplayBackend: relative-pointer-unstable-v1 is required!"); - m_waylandOutput.on_mode() = [this] (wayland::output_mode flags, int32_t width, int32_t height, int32_t refresh) { - s_ScreenSize = Size(width, height); - }; + m_waylandOutput.on_mode() = [this] (wayland::output_mode flags, int32_t width, int32_t height, int32_t refresh) { + s_ScreenSize = Size(width, height); + }; - m_XDGWMBase.on_ping() = [this] (uint32_t serial) { - m_XDGWMBase.pong(serial); - }; + m_XDGWMBase.on_ping() = [this] (uint32_t serial) { + m_XDGWMBase.pong(serial); + }; - m_waylandSeat.on_capabilities() = [this] (uint32_t capabilities) { - hasKeyboard = capabilities & wayland::seat_capability::keyboard; - hasPointer = capabilities & wayland::seat_capability::pointer; - }; + m_waylandSeat.on_capabilities() = [this] (uint32_t capabilities) { + hasKeyboard = capabilities & wayland::seat_capability::keyboard; + hasPointer = capabilities & wayland::seat_capability::pointer; + }; - m_XDGOutput = m_XDGOutputManager.get_xdg_output(m_waylandOutput); + m_XDGOutput = m_XDGOutputManager.get_xdg_output(m_waylandOutput); - m_XDGOutput.on_logical_position() = [this] (int32_t x, int32_t y) { - //m_WindowGlobalPos = Point(x, y); - }; + m_XDGOutput.on_logical_position() = [this] (int32_t x, int32_t y) { + //m_WindowGlobalPos = Point(x, y); + }; - m_XDGOutput.on_logical_size() = [this] (int32_t width, int32_t height) { - s_ScreenSize = Size(width, height); - }; + m_XDGOutput.on_logical_size() = [this] (int32_t width, int32_t height) { + s_ScreenSize = Size(width, height); + }; - if (!m_FractionalScaleManager) + if (!m_FractionalScaleManager) { - m_waylandOutput.on_scale() = [this] (int32_t scale) { + m_waylandOutput.on_scale() = [this] (int32_t scale) { for (WaylandDisplayWindow* w : s_Windows) { w->m_ScaleFactor = scale; w->m_NeedsUpdate = true; w->windowHost->OnWindowDpiScaleChanged(); } - }; + }; } - s_waylandDisplay.roundtrip(); + // Data Device for receiving contents + m_DataDevice = m_DataDeviceManager.get_data_device(m_waylandSeat); + + m_DataDevice.on_data_offer() = [&] (wayland::data_offer_t offer) + { + m_DataOfferMimeTypes[offer] = {}; + + offer.on_offer() = [this, offer] (std::string const& mime_type) + { + m_DataOfferMimeTypes[offer].insert(mime_type); + }; + }; + + m_DataDevice.on_selection() = [&] (wayland::data_offer_t offer) + { + // Clipboard is empty + if (!offer) + return; + + // There shouldn't be any previous data_offer events + if (!m_DataOfferMimeTypes.count(offer)) + return; + + // Reject anything that's not text/plain + if (!m_DataOfferMimeTypes[offer].count("text/plain")) + { + m_DataOfferMimeTypes.erase(offer); + return; + } + + int fds[2]; + pipe(fds); + offer.receive("text/plain", fds[1]); + close(fds[1]); + + s_waylandDisplay.roundtrip(); + + m_ClipboardContents.clear(); + + // Assign the read data to m_ClipboardContents + while (true) + { + std::string buf(1024, 0); + auto n = read(fds[0], const_cast(buf.c_str()), buf.size()); + if (n <= 0) + break; + + buf.resize(n); + m_ClipboardContents += buf; + } + close(fds[0]); + + m_DataOfferMimeTypes.erase(offer); + }; + + s_waylandDisplay.roundtrip(); // To do: this shouldn't really be fatal. The user might have forgotten to plug in their keyboard or mouse. - if (!hasKeyboard) - throw std::runtime_error("No keyboard detected!"); - if (!hasPointer) - throw std::runtime_error("No pointer device detected!"); + if (!hasKeyboard) + throw std::runtime_error("No keyboard detected!"); + if (!hasPointer) + throw std::runtime_error("No pointer device detected!"); - m_waylandKeyboard = m_waylandSeat.get_keyboard(); - m_waylandPointer = m_waylandSeat.get_pointer(); - m_RelativePointer = m_RelativePointerManager.get_relative_pointer(m_waylandPointer); + m_waylandKeyboard = m_waylandSeat.get_keyboard(); + m_waylandPointer = m_waylandSeat.get_pointer(); + m_RelativePointer = m_RelativePointerManager.get_relative_pointer(m_waylandPointer); + + if (m_CursorShapeManager && m_waylandPointer) + { + // Opt to Cursor Shape Protocol instead + m_CursorShapeDevice = m_CursorShapeManager.get_pointer(m_waylandPointer); + } ConnectDeviceEvents(); - m_cursorSurface = m_waylandCompositor.create_surface(); - SetCursor(StandardCursor::arrow); - -/* - m_DataDevice = m_DataDeviceManager.get_data_device(m_waylandSeat); - - m_DataSource = m_DataDeviceManager.create_data_source(); - m_DataSource.offer("text/plain"); - - m_DataSource.on_send() = [&] (std::string mime_type, int fd) { - if (mime_type != "text/plain") - return; - - if (!m_ClipboardContents.empty()) - write(fd, m_ClipboardContents.data(), m_ClipboardContents.size()); - close(fd); - }; - - m_DataDevice.on_selection() = [&] (wayland::data_offer_t dataOffer) { - m_ClipboardContents.clear(); - - if (!dataOffer) - // Clipboard is empty - return; - - int fds[2]; - pipe(fds); - - dataOffer.receive("text/plain", fds[1]); - close(fds[1]); - - m_waylandDisplay.roundtrip(); - - while (true) - { - char buf[1024]; - - ssize_t n = read(fds[0], buf, sizeof(buf)); - - if (n <= 0) - break; - - m_ClipboardContents += buf; - } - - close(fds[0]); - - dataOffer.proxy_release(); - }; -*/ + m_cursorSurface = m_waylandCompositor.create_surface(); + SetCursor(StandardCursor::arrow); } WaylandDisplayBackend::~WaylandDisplayBackend() { - if (m_KeymapContext) - xkb_context_unref(m_KeymapContext); + if (m_KeymapContext) + xkb_context_unref(m_KeymapContext); - if (m_KeyboardState) - xkb_state_unref(m_KeyboardState); + if (m_KeyboardState) + xkb_state_unref(m_KeyboardState); } void WaylandDisplayBackend::ConnectDeviceEvents() { - m_KeymapContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + m_KeymapContext = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - m_waylandKeyboard.on_keymap() = [this] (wayland::keyboard_keymap_format format, int fd, uint32_t size) { - if (format != wayland::keyboard_keymap_format::xkb_v1) - throw std::runtime_error("WaylandDisplayBackend: Unrecognized keymap format!"); + m_waylandKeyboard.on_keymap() = [this] (wayland::keyboard_keymap_format format, int fd, uint32_t size) { + if (format != wayland::keyboard_keymap_format::xkb_v1) + throw std::runtime_error("WaylandDisplayBackend: Unrecognized keymap format!"); - char* mapSHM = (char*)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); - if (mapSHM == MAP_FAILED) - throw std::runtime_error("WaylandDisplayBackend: Keymap shared memory allocation failed!"); + char* mapSHM = (char*)mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (mapSHM == MAP_FAILED) + throw std::runtime_error("WaylandDisplayBackend: Keymap shared memory allocation failed!"); - if (m_Keymap) - xkb_keymap_unref(m_Keymap); + if (m_Keymap) + xkb_keymap_unref(m_Keymap); - m_Keymap = xkb_keymap_new_from_string(m_KeymapContext, mapSHM, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); - munmap(mapSHM, size); - close(fd); + m_Keymap = xkb_keymap_new_from_string(m_KeymapContext, mapSHM, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + munmap(mapSHM, size); + close(fd); - if (m_KeyboardState) - xkb_state_unref(m_KeyboardState); + if (m_KeyboardState) + xkb_state_unref(m_KeyboardState); - m_KeyboardState = xkb_state_new(m_Keymap); - }; + m_KeyboardState = xkb_state_new(m_Keymap); + }; - m_waylandKeyboard.on_modifiers() = [this] (uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { - xkb_state_update_mask(m_KeyboardState, mods_depressed, mods_latched, mods_locked, 0, 0, group); - }; + m_waylandKeyboard.on_modifiers() = [this] (uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { + xkb_state_update_mask(m_KeyboardState, mods_depressed, mods_latched, mods_locked, 0, 0, group); + }; - m_waylandKeyboard.on_enter() = [this] (uint32_t serial, wayland::surface_t surfaceEntered, wayland::array_t keys) { - std::vector keysVec = keys; + m_waylandKeyboard.on_enter() = [this] (uint32_t serial, wayland::surface_t surfaceEntered, wayland::array_t keys) { + std::vector keysVec = keys; - m_KeyboardSerial = serial; + m_KeyboardSerial = serial; // Find the window to focus on by checking the surface window owns. - if (!m_FocusWindow || m_FocusWindow->GetWindowSurface() != surfaceEntered) - { - for (auto win: s_Windows) - { - if (win->GetWindowSurface() == surfaceEntered) - m_FocusWindow = win; - } - } + if (!m_FocusWindow || m_FocusWindow->GetWindowSurface() != surfaceEntered) + { + for (auto win: s_Windows) + { + if (win->GetWindowSurface() == surfaceEntered) + m_FocusWindow = win; + } + } + for (auto key: keysVec) + { + // keys parameter represents the keys pressed when entering the surface + // key variable is Linux evdev scancode, to translate it to XKB keycode, we must add 8 + xkb_keysym_t sym = xkb_state_key_get_one_sym(m_KeyboardState, key + 8); + OnKeyboardKeyEvent(sym, wayland::keyboard_key_state::pressed); - for (auto key: keysVec) - { - // keys parameter represents the keys pressed when entering the surface - // key variable is Linux evdev scancode, to translate it to XKB keycode, we must add 8 - xkb_keysym_t sym = xkb_state_key_get_one_sym(m_KeyboardState, key + 8); - OnKeyboardKeyEvent(sym, wayland::keyboard_key_state::pressed); + // Also cause a Char event + char buf[128]; + xkb_state_key_get_utf8(m_KeyboardState, key + 8, buf, sizeof(buf)); - // Also cause a Char event - char buf[128]; - xkb_state_key_get_utf8(m_KeyboardState, key + 8, buf, sizeof(buf)); + OnKeyboardCharEvent(buf, wayland::keyboard_key_state::pressed); + } + }; - OnKeyboardCharEvent(buf, wayland::keyboard_key_state::pressed); - } - }; + m_waylandKeyboard.on_key() = [this] (uint32_t serial, uint32_t time, uint32_t key, wayland::keyboard_key_state state) { + // key is Linux evdev scancode, to translate it to XKB keycode, we must add 8 + xkb_keysym_t sym = xkb_state_key_get_one_sym(m_KeyboardState, key + 8); + OnKeyboardKeyEvent(sym, state); - m_waylandKeyboard.on_key() = [this] (uint32_t serial, uint32_t time, uint32_t key, wayland::keyboard_key_state state) { - // key is Linux evdev scancode, to translate it to XKB keycode, we must add 8 - xkb_keysym_t sym = xkb_state_key_get_one_sym(m_KeyboardState, key + 8); - OnKeyboardKeyEvent(sym, state); + // Also cause a Char event + char buf[128]; + xkb_state_key_get_utf8(m_KeyboardState, key + 8, buf, sizeof(buf)); - // Also cause a Char event - char buf[128]; - xkb_state_key_get_utf8(m_KeyboardState, key + 8, buf, sizeof(buf)); + OnKeyboardCharEvent(buf, state); - OnKeyboardCharEvent(buf, state); + //m_DataDevice.set_selection(m_DataSource, m_KeyboardSerial); + }; - //m_DataDevice.set_selection(m_DataSource, m_KeyboardSerial); - }; + m_waylandPointer.on_enter() = [this](uint32_t serial, wayland::surface_t surfaceEntered, double surfaceX, double surfaceY) { + // Keep track of the mouse serial for using it in Cursor Shape protocol + m_MouseSerial = serial; - m_waylandPointer.on_enter() = [this](uint32_t serial, wayland::surface_t surfaceEntered, double surfaceX, double surfaceY) { // Find the window to focus on by checking the surface window owns. - if (!m_MouseFocusWindow || m_MouseFocusWindow->GetWindowSurface() != surfaceEntered) - { - for (auto win: s_Windows) - { - if (win->GetWindowSurface() == surfaceEntered) - { - m_MouseFocusWindow = win; - } - } - } + if (!m_MouseFocusWindow || m_MouseFocusWindow->GetWindowSurface() != surfaceEntered) + { + for (auto win: s_Windows) + { + if (win->GetWindowSurface() == surfaceEntered) + { + m_MouseFocusWindow = win; + } + } + } currentPointerEvent.event_mask |= POINTER_EVENT_ENTER; currentPointerEvent.serial = serial; - currentPointerEvent.surfaceX = surfaceX; - currentPointerEvent.surfaceY = surfaceY; - }; + currentPointerEvent.surfaceX = surfaceX; + currentPointerEvent.surfaceY = surfaceY; + }; - m_waylandPointer.on_leave() = [this](uint32_t serial, wayland::surface_t surfaceLeft) { - currentPointerEvent.event_mask |= POINTER_EVENT_LEAVE; - currentPointerEvent.serial = serial; - }; + m_waylandPointer.on_leave() = [this](uint32_t serial, wayland::surface_t surfaceLeft) { + currentPointerEvent.event_mask |= POINTER_EVENT_LEAVE; + currentPointerEvent.serial = serial; + }; - m_waylandPointer.on_motion() = [this] (uint32_t serial, double surfaceX, double surfaceY) { - currentPointerEvent.event_mask |= POINTER_EVENT_MOTION; - currentPointerEvent.serial = serial; - currentPointerEvent.surfaceX = surfaceX; - currentPointerEvent.surfaceY = surfaceY; - }; + m_waylandPointer.on_motion() = [this] (uint32_t serial, double surfaceX, double surfaceY) { + currentPointerEvent.event_mask |= POINTER_EVENT_MOTION; + currentPointerEvent.serial = serial; + currentPointerEvent.surfaceX = surfaceX; + currentPointerEvent.surfaceY = surfaceY; + }; - m_waylandPointer.on_button() = [this] (uint32_t serial, uint32_t time, uint32_t button, wayland::pointer_button_state state) { - currentPointerEvent.event_mask |= POINTER_EVENT_BUTTON; - currentPointerEvent.serial = serial; - currentPointerEvent.time = time; - currentPointerEvent.button = button; - currentPointerEvent.state = state; - }; + m_waylandPointer.on_button() = [this] (uint32_t serial, uint32_t time, uint32_t button, wayland::pointer_button_state state) { + currentPointerEvent.event_mask |= POINTER_EVENT_BUTTON; + currentPointerEvent.serial = serial; + currentPointerEvent.time = time; + currentPointerEvent.button = button; + currentPointerEvent.state = state; + }; - m_waylandPointer.on_axis() = [this] (uint32_t serial, wayland::pointer_axis axis, double value) { - currentPointerEvent.event_mask |= POINTER_EVENT_AXIS; - currentPointerEvent.serial = serial; - currentPointerEvent.axes[uint32_t(axis)].value = value; - currentPointerEvent.axes[uint32_t(axis)].valid = true; - }; + m_waylandPointer.on_axis() = [this] (uint32_t serial, wayland::pointer_axis axis, double value) { + currentPointerEvent.event_mask |= POINTER_EVENT_AXIS; + currentPointerEvent.serial = serial; + currentPointerEvent.axes[uint32_t(axis)].value = value; + currentPointerEvent.axes[uint32_t(axis)].valid = true; + }; - // High resolution scroll event - m_waylandPointer.on_axis_value120() = [this] (wayland::pointer_axis axis, int32_t value) { - currentPointerEvent.event_mask |= POINTER_EVENT_AXIS_120; - currentPointerEvent.axes[uint32_t(axis)].valid = true; - currentPointerEvent.axes[uint32_t(axis)].value120 = value; - }; + // High resolution scroll event + m_waylandPointer.on_axis_value120() = [this] (wayland::pointer_axis axis, int32_t value) { + currentPointerEvent.event_mask |= POINTER_EVENT_AXIS_120; + currentPointerEvent.axes[uint32_t(axis)].valid = true; + currentPointerEvent.axes[uint32_t(axis)].value120 = value; + }; - m_waylandPointer.on_axis_source() = [this] (wayland::pointer_axis_source axis) { - currentPointerEvent.event_mask |= POINTER_EVENT_AXIS_SOURCE; - currentPointerEvent.axis_source = axis; - }; + m_waylandPointer.on_axis_source() = [this] (wayland::pointer_axis_source axis) { + currentPointerEvent.event_mask |= POINTER_EVENT_AXIS_SOURCE; + currentPointerEvent.axis_source = axis; + }; - m_waylandPointer.on_axis_stop() = [this] (uint32_t time, wayland::pointer_axis axis) { - currentPointerEvent.event_mask |= POINTER_EVENT_AXIS_STOP; - currentPointerEvent.time = time; - currentPointerEvent.axes[uint32_t(axis)].valid = true; - }; + m_waylandPointer.on_axis_stop() = [this] (uint32_t time, wayland::pointer_axis axis) { + currentPointerEvent.event_mask |= POINTER_EVENT_AXIS_STOP; + currentPointerEvent.time = time; + currentPointerEvent.axes[uint32_t(axis)].valid = true; + }; - m_RelativePointer.on_relative_motion() = [this] (uint32_t utime_hi, uint32_t utime_lo, - double dx, double dy, - double dx_unaccel, double dy_unaccel) { - currentPointerEvent.event_mask |= POINTER_EVENT_RELATIVE_MOTION; - currentPointerEvent.time = utime_lo; - currentPointerEvent.dx = dx; - currentPointerEvent.dy = dy; - }; + m_RelativePointer.on_relative_motion() = [this] (uint32_t utime_hi, uint32_t utime_lo, + double dx, double dy, + double dx_unaccel, double dy_unaccel) { + currentPointerEvent.event_mask |= POINTER_EVENT_RELATIVE_MOTION; + currentPointerEvent.time = utime_lo; + currentPointerEvent.dx = dx; + currentPointerEvent.dy = dy; + currentPointerEvent.dx_unaccel = dx_unaccel; + currentPointerEvent.dy_unaccel = dy_unaccel; + }; - m_waylandPointer.on_frame() = [this] () { - if (currentPointerEvent.event_mask & POINTER_EVENT_ENTER) - OnMouseEnterEvent(currentPointerEvent.serial); + m_waylandPointer.on_frame() = [this] () { + if (currentPointerEvent.event_mask & POINTER_EVENT_ENTER) + OnMouseEnterEvent(currentPointerEvent.serial); - if (currentPointerEvent.event_mask & POINTER_EVENT_LEAVE) - OnMouseLeaveEvent(); + if (currentPointerEvent.event_mask & POINTER_EVENT_LEAVE) + OnMouseLeaveEvent(); - if (currentPointerEvent.event_mask & POINTER_EVENT_MOTION) - { - OnMouseMoveEvent(Point(currentPointerEvent.surfaceX, currentPointerEvent.surfaceY)); - } + if (currentPointerEvent.event_mask & POINTER_EVENT_MOTION) + { + OnMouseMoveEvent(Point(currentPointerEvent.surfaceX, currentPointerEvent.surfaceY)); + } - if (currentPointerEvent.event_mask & POINTER_EVENT_RELATIVE_MOTION) - { - if (hasMouseLock) - OnMouseMoveRawEvent(int(currentPointerEvent.dx), int(currentPointerEvent.dy)); - } + if (currentPointerEvent.event_mask & POINTER_EVENT_RELATIVE_MOTION) + { + if (hasMouseLock) + OnMouseMoveRawEvent(int(currentPointerEvent.dx_unaccel), int(currentPointerEvent.dy_unaccel)); + } - if (currentPointerEvent.event_mask & POINTER_EVENT_BUTTON) - { - InputKey ikey = LinuxInputEventCodeToInputKey(currentPointerEvent.button); - if (currentPointerEvent.state == wayland::pointer_button_state::pressed) - OnMousePressEvent(ikey); - else // released - OnMouseReleaseEvent(ikey); - } + if (currentPointerEvent.event_mask & POINTER_EVENT_BUTTON) + { + InputKey ikey = LinuxInputEventCodeToInputKey(currentPointerEvent.button); + if (currentPointerEvent.state == wayland::pointer_button_state::pressed) + OnMousePressEvent(ikey); + else // released + OnMouseReleaseEvent(ikey); + } - uint32_t axisevents = POINTER_EVENT_AXIS | POINTER_EVENT_AXIS_120 | POINTER_EVENT_AXIS_SOURCE | POINTER_EVENT_AXIS_STOP; + uint32_t axisevents = POINTER_EVENT_AXIS | POINTER_EVENT_AXIS_120 | POINTER_EVENT_AXIS_SOURCE | POINTER_EVENT_AXIS_STOP; - if (currentPointerEvent.event_mask & axisevents) - { - for (size_t idx = 0 ; idx < 2 ; idx++) - { - if (!currentPointerEvent.axes[idx].valid) - continue; + if (currentPointerEvent.event_mask & axisevents) + { + for (size_t idx = 0 ; idx < 2 ; idx++) + { + if (!currentPointerEvent.axes[idx].valid) + continue; - if (currentPointerEvent.event_mask & POINTER_EVENT_AXIS_120) - { - if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value > 0) - OnMouseWheelEvent(InputKey::MouseWheelDown); - if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value < 0) - OnMouseWheelEvent(InputKey::MouseWheelUp); - } - else if (currentPointerEvent.event_mask & POINTER_EVENT_AXIS) - { - if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value120 > 0) - OnMouseWheelEvent(InputKey::MouseWheelDown); - if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value120 < 0) - OnMouseWheelEvent(InputKey::MouseWheelUp); - } - } - } + if (currentPointerEvent.event_mask & POINTER_EVENT_AXIS_120) + { + if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value > 0) + OnMouseWheelEvent(InputKey::MouseWheelDown); + if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value < 0) + OnMouseWheelEvent(InputKey::MouseWheelUp); + } + else if (currentPointerEvent.event_mask & POINTER_EVENT_AXIS) + { + if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value120 > 0) + OnMouseWheelEvent(InputKey::MouseWheelDown); + if (idx == uint32_t(wayland::pointer_axis::vertical_scroll) && currentPointerEvent.axes[idx].value120 < 0) + OnMouseWheelEvent(InputKey::MouseWheelUp); + } + } + } - // Reset everything once all the events are processed - currentPointerEvent = {0}; - }; + // Reset everything once all the events are processed + currentPointerEvent = {0}; + }; - m_keyboardDelayTimer = ZTimer(); - m_keyboardRepeatTimer = ZTimer(); + m_keyboardDelayTimer = ZTimer(); + m_keyboardRepeatTimer = ZTimer(); - m_waylandKeyboard.on_repeat_info() = [this] (int32_t rate, int32_t delay) { - // rate is characters per second, delay is in milliseconds - m_keyboardDelayTimer.SetDuration(ZTimer::Duration(delay)); - m_keyboardRepeatTimer.SetDuration(ZTimer::Duration(1000.0 / rate)); - }; + m_waylandKeyboard.on_repeat_info() = [this] (int32_t rate, int32_t delay) { + // rate is characters per second, delay is in milliseconds + m_keyboardDelayTimer.SetDuration(ZTimer::Duration(delay)); + m_keyboardRepeatTimer.SetDuration(ZTimer::Duration(1000.0 / rate)); + }; - m_keyboardDelayTimer.SetCallback([this] () { OnKeyboardDelayEnd(); }); - m_keyboardRepeatTimer.SetCallback([this] () { OnKeyboardRepeat(); }); + m_keyboardDelayTimer.SetCallback([this] () { OnKeyboardDelayEnd(); }); + m_keyboardRepeatTimer.SetCallback([this] () { OnKeyboardRepeat(); }); - m_keyboardRepeatTimer.SetRepeating(true); + m_keyboardRepeatTimer.SetRepeating(true); - m_previousTime = ZTimer::Clock::now(); - m_currentTime = ZTimer::Clock::now(); + m_previousTime = ZTimer::Clock::now(); + m_currentTime = ZTimer::Clock::now(); } void WaylandDisplayBackend::OnKeyboardKeyEvent(xkb_keysym_t xkbKeySym, wayland::keyboard_key_state state) { - InputKey inputKey = XKBKeySymToInputKey(xkbKeySym); - - if (state == wayland::keyboard_key_state::pressed) - { - inputKeyStates[inputKey] = true; - m_FocusWindow->windowHost->OnWindowKeyDown(inputKey); - if (inputKey != previousKey) - { - previousKey = inputKey; - m_keyboardDelayTimer.Stop(); - m_keyboardRepeatTimer.Stop(); - } - m_keyboardDelayTimer.Start(); - } - if (state == wayland::keyboard_key_state::released) - { - inputKeyStates[inputKey] = false; - if (m_FocusWindow) - m_FocusWindow->windowHost->OnWindowKeyUp(inputKey); - m_keyboardDelayTimer.Stop(); - m_keyboardRepeatTimer.Stop(); - } + InputKey inputKey = XKBKeySymToInputKey(xkbKeySym); + if (state == wayland::keyboard_key_state::pressed) + { + inputKeyStates[inputKey] = true; + m_FocusWindow->windowHost->OnWindowKeyDown(inputKey); + if (inputKey != previousKey) + { + previousKey = inputKey; + m_keyboardDelayTimer.Stop(); + m_keyboardRepeatTimer.Stop(); + } + m_keyboardDelayTimer.Start(); + } + if (state == wayland::keyboard_key_state::released) + { + inputKeyStates[inputKey] = false; + if (m_FocusWindow) + m_FocusWindow->windowHost->OnWindowKeyUp(inputKey); + m_keyboardDelayTimer.Stop(); + m_keyboardRepeatTimer.Stop(); + } } void WaylandDisplayBackend::OnKeyboardCharEvent(const char* ch, wayland::keyboard_key_state state) { - if (state == wayland::keyboard_key_state::pressed) - { - previousChars = std::string(ch); - m_FocusWindow->windowHost->OnWindowKeyChar(previousChars); - } + if (state == wayland::keyboard_key_state::pressed) + { + previousChars = std::string(ch); + m_FocusWindow->windowHost->OnWindowKeyChar(previousChars); + } } void WaylandDisplayBackend::OnKeyboardDelayEnd() { - if (inputKeyStates[previousKey]) - m_keyboardRepeatTimer.Start(); + if (inputKeyStates[previousKey]) + m_keyboardRepeatTimer.Start(); } void WaylandDisplayBackend::OnKeyboardRepeat() { - if (inputKeyStates[previousKey]) - { - m_FocusWindow->windowHost->OnWindowKeyDown(previousKey); - m_FocusWindow->windowHost->OnWindowKeyChar(previousChars); - } + if (inputKeyStates[previousKey]) + { + m_FocusWindow->windowHost->OnWindowKeyDown(previousKey); + m_FocusWindow->windowHost->OnWindowKeyChar(previousChars); + } } void WaylandDisplayBackend::OnMouseEnterEvent(uint32_t serial) { - m_cursorSurface.attach(!hasMouseLock ? m_cursorBuffer : nullptr, 0, 0); - if (!hasMouseLock) - m_cursorSurface.damage(0, 0, m_cursorImage.width(), m_cursorImage.height()); - m_cursorSurface.commit(); - m_waylandPointer.set_cursor(serial, m_cursorSurface, 0, 0); + if (!m_CursorShapeDevice) + { + m_cursorSurface.attach(!hasMouseLock ? m_cursorBuffer : nullptr, 0, 0); + if (!hasMouseLock) + { + m_cursorSurface.damage(0, 0, m_cursorImage.width(), m_cursorImage.height()); + } + + m_cursorSurface.commit(); + } + m_waylandPointer.set_cursor(serial, !hasMouseLock ? m_cursorSurface : nullptr, 0, 0); } void WaylandDisplayBackend::OnMouseLeaveEvent() { - if (m_MouseFocusWindow) - { - m_MouseFocusWindow->windowHost->OnWindowMouseLeave(); - //m_MouseFocusWindow = nullptr; // Borks up the menus - } + if (m_MouseFocusWindow) + { + m_MouseFocusWindow->windowHost->OnWindowMouseLeave(); + //m_MouseFocusWindow = nullptr; // Borks up the menus + } } void WaylandDisplayBackend::OnMousePressEvent(InputKey button) { - if (m_MouseFocusWindow) - m_MouseFocusWindow->windowHost->OnWindowMouseDown(m_MouseFocusWindow->m_SurfaceMousePos, button); + if (m_MouseFocusWindow) + m_MouseFocusWindow->windowHost->OnWindowMouseDown(m_MouseFocusWindow->m_SurfaceMousePos, button); } void WaylandDisplayBackend::OnMouseReleaseEvent(InputKey button) { - if (m_MouseFocusWindow) - m_MouseFocusWindow->windowHost->OnWindowMouseUp(m_MouseFocusWindow->m_SurfaceMousePos, button); + if (m_MouseFocusWindow) + m_MouseFocusWindow->windowHost->OnWindowMouseUp(m_MouseFocusWindow->m_SurfaceMousePos, button); } void WaylandDisplayBackend::OnMouseMoveEvent(Point surfacePos) @@ -484,31 +512,39 @@ void WaylandDisplayBackend::OnMouseMoveEvent(Point surfacePos) void WaylandDisplayBackend::OnMouseMoveRawEvent(int dx, int dy) { - if (m_MouseFocusWindow) - m_MouseFocusWindow->windowHost->OnWindowRawMouseMove(dx, dy); + if (m_MouseFocusWindow) + m_MouseFocusWindow->windowHost->OnWindowRawMouseMove(dx, dy); } void WaylandDisplayBackend::OnMouseWheelEvent(InputKey button) { - if (m_MouseFocusWindow) - m_MouseFocusWindow->windowHost->OnWindowMouseWheel(m_MouseFocusWindow->m_SurfaceMousePos, button); + if (m_MouseFocusWindow) + m_MouseFocusWindow->windowHost->OnWindowMouseWheel(m_MouseFocusWindow->m_SurfaceMousePos, button); } void WaylandDisplayBackend::SetCursor(StandardCursor cursor) { - std::string cursorName = GetWaylandCursorName(cursor); + if (m_CursorShapeDevice) + { + // Use cursor shapes protocol + m_CursorShapeDevice.set_shape(m_MouseSerial, GetWaylandCursorShape(cursor)); + } + else + { + // Use the older cursor buffer approach + std::string cursorName = GetWaylandCursorName(cursor); - // Perhaps the cursor size can be inferred from the user prefs as well? - wayland::cursor_theme_t cursorTheme = wayland::cursor_theme_t("default", 16, m_waylandSHM); - wayland::cursor_t obtainedCursor = cursorTheme.get_cursor(cursorName); - m_cursorImage = obtainedCursor.image(0); - m_cursorBuffer = m_cursorImage.get_buffer(); + // Perhaps the cursor size can be inferred from the user prefs as well? + wayland::cursor_theme_t cursorTheme = wayland::cursor_theme_t("default", 16, m_waylandSHM); + wayland::cursor_t obtainedCursor = cursorTheme.get_cursor(cursorName); + m_cursorImage = obtainedCursor.image(0); + m_cursorBuffer = m_cursorImage.get_buffer(); + } } void WaylandDisplayBackend::ShowCursor(bool enable) { - m_cursorSurface.attach(enable ? m_cursorBuffer : nullptr, 0, 0); - m_cursorSurface.commit(); + m_waylandPointer.set_cursor(m_MouseSerial, enable ? m_cursorSurface : nullptr, 0, 0); } std::unique_ptr WaylandDisplayBackend::Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) @@ -518,101 +554,161 @@ std::unique_ptr WaylandDisplayBackend::Create(DisplayWindowHost* void WaylandDisplayBackend::OnWindowCreated(WaylandDisplayWindow* window) { - s_Windows.push_back(window); - if (!window->m_PopupWindow) - { - m_FocusWindow = window; - m_MouseFocusWindow = window; - } + s_Windows.push_back(window); + if (!window->m_PopupWindow) + { + m_FocusWindow = window; + m_MouseFocusWindow = window; + } } void WaylandDisplayBackend::OnWindowDestroyed(WaylandDisplayWindow* window) { - auto it = std::find(s_Windows.begin(), s_Windows.end(), window); - if (it != s_Windows.end()) - s_Windows.erase(it); + auto it = std::find(s_Windows.begin(), s_Windows.end(), window); + if (it != s_Windows.end()) + s_Windows.erase(it); - if (m_FocusWindow == window) - m_FocusWindow = nullptr; + if (m_FocusWindow == window) + m_FocusWindow = nullptr; - if (m_MouseFocusWindow == window) - m_MouseFocusWindow = nullptr; + if (m_MouseFocusWindow == window) + m_MouseFocusWindow = nullptr; } void WaylandDisplayBackend::ProcessEvents() { - while (wl_display_prepare_read(s_waylandDisplay)) - s_waylandDisplay.dispatch_pending(); + CheckNeedsUpdate(); - while (wl_display_flush(s_waylandDisplay) < 0 && errno == EAGAIN) - poll_single(s_waylandDisplay.get_fd(), POLLOUT, -1); + while (wl_display_prepare_read(s_waylandDisplay)) + s_waylandDisplay.dispatch_pending(); - if (poll_single(s_waylandDisplay.get_fd(), POLLIN, 0) & POLLIN) - { - wl_display_read_events(s_waylandDisplay); - s_waylandDisplay.dispatch_pending(); - } - else - wl_display_cancel_read(s_waylandDisplay); + while (wl_display_flush(s_waylandDisplay) < 0 && errno == EAGAIN) + poll_single(s_waylandDisplay.get_fd(), POLLOUT, -1); - if (s_waylandDisplay.get_error()) - throw std::runtime_error("Wayland Protocol Error"); + if (poll_single(s_waylandDisplay.get_fd(), POLLIN, 0) & POLLIN) + { + wl_display_read_events(s_waylandDisplay); + s_waylandDisplay.dispatch_pending(); + } + else + wl_display_cancel_read(s_waylandDisplay); + + if (s_waylandDisplay.get_error()) + throw std::runtime_error("Wayland Protocol Error"); + + UpdateTimers(); } void WaylandDisplayBackend::RunLoop() { - exitRunLoop = false; + exitRunLoop = false; - while (!exitRunLoop) - { - CheckNeedsUpdate(); - UpdateTimers(); + while (!exitRunLoop && !s_Windows.empty()) + { + ProcessEvents(); + WaitForEvents(GetTimerTimeout()); + } +} - ProcessEvents(); - } +void WaylandDisplayBackend::WaitForEvents(int timeout) +{ + poll_single(s_waylandDisplay.get_fd(), POLLIN, -1); +} + +static int64_t GetTimePoint() +{ + using namespace std::chrono; + return (int64_t)(duration_cast(steady_clock::now().time_since_epoch()).count()); +} + +int WaylandDisplayBackend::GetTimerTimeout() +{ + if (m_timers.empty()) + return 0; + + int64_t nextTime = m_timers.front()->nextTime; + for (auto& timer : m_timers) + { + nextTime = std::min(nextTime, timer->nextTime); + } + + int64_t now = GetTimePoint(); + return (int)std::max(nextTime - now, (int64_t)1); } void WaylandDisplayBackend::UpdateTimers() { - m_currentTime = ZTimer::Clock::now(); + m_currentTime = ZTimer::Clock::now(); - m_keyboardDelayTimer.Update(m_currentTime - m_previousTime); - m_keyboardRepeatTimer.Update(m_currentTime - m_previousTime); + m_keyboardDelayTimer.Update(m_currentTime - m_previousTime); + m_keyboardRepeatTimer.Update(m_currentTime - m_previousTime); - m_previousTime = m_currentTime; + m_previousTime = m_currentTime; + + int64_t now = GetTimePoint(); + + // The callback may stop timers. Iterators might invalidate. + while (true) + { + std::shared_ptr foundTimer; + for (auto& timer : m_timers) + { + if (timer->nextTime < now) + { + foundTimer = timer; + break; + } + } + + if (!foundTimer) + break; + + // Not very precise, but these aren't high precision timers + foundTimer->nextTime = now + foundTimer->timeoutMilliseconds; + foundTimer->onTimer(); + } } void WaylandDisplayBackend::CheckNeedsUpdate() { - for (auto window: s_Windows) - { - if (window->m_NeedsUpdate) - { - window->m_NeedsUpdate = false; - window->windowHost->OnWindowPaint(); - } - } -} - -void WaylandDisplayBackend::ExitLoop() -{ - exitRunLoop = true; -} - -Size WaylandDisplayBackend::GetScreenSize() -{ - return s_ScreenSize; + for (auto window: s_Windows) + { + if (window->m_NeedsUpdate) + { + window->m_NeedsUpdate = false; + window->windowHost->OnWindowPaint(); + } + } } void* WaylandDisplayBackend::StartTimer(int timeoutMilliseconds, std::function onTimer) { - throw std::logic_error("unimplemented: WaylandDisplayBackend::StartTimer"); + timeoutMilliseconds = std::max(timeoutMilliseconds, 1); + m_timers.push_back(std::make_shared(timeoutMilliseconds, onTimer, GetTimePoint() + timeoutMilliseconds)); + return m_timers.back().get(); } void WaylandDisplayBackend::StopTimer(void* timerID) { - throw std::logic_error("unimplemented: WaylandDisplayBackend::StopTimer"); + for (auto it = m_timers.begin(); it != m_timers.end(); ++it) + { + if (it->get() == timerID) + { + m_timers.erase(it); + return; + } + } +} + +void WaylandDisplayBackend::ExitLoop() +{ + exitRunLoop = true; +} + +Size WaylandDisplayBackend::GetScreenSize() +{ + return s_ScreenSize; } #ifdef USE_DBUS @@ -643,509 +739,543 @@ std::unique_ptr WaylandDisplayBackend::CreateOpenFolderDialog( bool WaylandDisplayBackend::GetKeyState(InputKey key) { - auto it = inputKeyStates.find(key); + auto it = inputKeyStates.find(key); - // if the key isn't "registered", then it is not pressed. - if (it == inputKeyStates.end()) - return false; + // if the key isn't "registered", then it is not pressed. + if (it == inputKeyStates.end()) + return false; - return it->second; + return it->second; } InputKey WaylandDisplayBackend::XKBKeySymToInputKey(xkb_keysym_t keySym) { - switch (keySym) - { - case XKB_KEY_Escape: - return InputKey::Escape; - case XKB_KEY_1: - return InputKey::_1; - case XKB_KEY_2: - return InputKey::_2; - case XKB_KEY_3: - return InputKey::_3; - case XKB_KEY_4: - return InputKey::_4; - case XKB_KEY_5: - return InputKey::_5; - case XKB_KEY_6: - return InputKey::_6; - case XKB_KEY_7: - return InputKey::_7; - case XKB_KEY_8: - return InputKey::_8; - case XKB_KEY_9: - return InputKey::_9; - case XKB_KEY_0: - return InputKey::_0; - case XKB_KEY_KP_1: - return InputKey::NumPad1; - case XKB_KEY_KP_2: - return InputKey::NumPad2; - case XKB_KEY_KP_3: - return InputKey::NumPad3; - case XKB_KEY_KP_4: - return InputKey::NumPad4; - case XKB_KEY_KP_5: - return InputKey::NumPad5; - case XKB_KEY_KP_6: - return InputKey::NumPad6; - case XKB_KEY_KP_7: - return InputKey::NumPad7; - case XKB_KEY_KP_8: - return InputKey::NumPad8; - case XKB_KEY_KP_9: - return InputKey::NumPad9; - case XKB_KEY_KP_0: - return InputKey::NumPad0; - case XKB_KEY_F1: - return InputKey::F1; - case XKB_KEY_F2: - return InputKey::F2; - case XKB_KEY_F3: - return InputKey::F3; - case XKB_KEY_F4: - return InputKey::F4; - case XKB_KEY_F5: - return InputKey::F5; - case XKB_KEY_F6: - return InputKey::F6; - case XKB_KEY_F7: - return InputKey::F7; - case XKB_KEY_F8: - return InputKey::F8; - case XKB_KEY_F9: - return InputKey::F9; - case XKB_KEY_F10: - return InputKey::F10; - case XKB_KEY_F11: - return InputKey::F11; - case XKB_KEY_F12: - return InputKey::F12; - case XKB_KEY_F13: - return InputKey::F13; - case XKB_KEY_F14: - return InputKey::F14; - case XKB_KEY_F15: - return InputKey::F15; - case XKB_KEY_F16: - return InputKey::F16; - case XKB_KEY_F17: - return InputKey::F17; - case XKB_KEY_F18: - return InputKey::F18; - case XKB_KEY_F19: - return InputKey::F19; - case XKB_KEY_F20: - return InputKey::F20; - case XKB_KEY_F21: - return InputKey::F21; - case XKB_KEY_F22: - return InputKey::F22; - case XKB_KEY_F23: - return InputKey::F23; - case XKB_KEY_F24: - return InputKey::F24; - case XKB_KEY_minus: - case XKB_KEY_KP_Subtract: - return InputKey::Minus; - case XKB_KEY_equal: - return InputKey::Equals; - case XKB_KEY_BackSpace: - return InputKey::Backspace; - case XKB_KEY_backslash: - return InputKey::Backslash; - case XKB_KEY_Tab: - return InputKey::Tab; - case XKB_KEY_braceleft: - return InputKey::LeftBracket; - case XKB_KEY_braceright: - return InputKey::RightBracket; - case XKB_KEY_Control_L: - case XKB_KEY_Control_R: - return InputKey::Ctrl; - case XKB_KEY_Alt_L: - case XKB_KEY_Alt_R: - return InputKey::Alt; - case XKB_KEY_Delete: - return InputKey::Delete; - case XKB_KEY_semicolon: - return InputKey::Semicolon; - case XKB_KEY_comma: - return InputKey::Comma; - case XKB_KEY_period: - return InputKey::Period; - case XKB_KEY_Num_Lock: - return InputKey::NumLock; - case XKB_KEY_Caps_Lock: - return InputKey::CapsLock; - case XKB_KEY_Scroll_Lock: - return InputKey::ScrollLock; - case XKB_KEY_Shift_L: - return InputKey::LShift; - case XKB_KEY_Shift_R: - return InputKey::RShift; - case XKB_KEY_grave: - return InputKey::Tilde; - case XKB_KEY_apostrophe: - return InputKey::SingleQuote; - case XKB_KEY_KP_Enter: - case XKB_KEY_Return: - return InputKey::Enter; - case XKB_KEY_space: - return InputKey::Space; + switch (keySym) + { + case XKB_KEY_Escape: + return InputKey::Escape; + case XKB_KEY_1: + return InputKey::_1; + case XKB_KEY_2: + return InputKey::_2; + case XKB_KEY_3: + return InputKey::_3; + case XKB_KEY_4: + return InputKey::_4; + case XKB_KEY_5: + return InputKey::_5; + case XKB_KEY_6: + return InputKey::_6; + case XKB_KEY_7: + return InputKey::_7; + case XKB_KEY_8: + return InputKey::_8; + case XKB_KEY_9: + return InputKey::_9; + case XKB_KEY_0: + return InputKey::_0; + case XKB_KEY_KP_1: + return InputKey::NumPad1; + case XKB_KEY_KP_2: + return InputKey::NumPad2; + case XKB_KEY_KP_3: + return InputKey::NumPad3; + case XKB_KEY_KP_4: + return InputKey::NumPad4; + case XKB_KEY_KP_5: + return InputKey::NumPad5; + case XKB_KEY_KP_6: + return InputKey::NumPad6; + case XKB_KEY_KP_7: + return InputKey::NumPad7; + case XKB_KEY_KP_8: + return InputKey::NumPad8; + case XKB_KEY_KP_9: + return InputKey::NumPad9; + case XKB_KEY_KP_0: + return InputKey::NumPad0; + case XKB_KEY_F1: + return InputKey::F1; + case XKB_KEY_F2: + return InputKey::F2; + case XKB_KEY_F3: + return InputKey::F3; + case XKB_KEY_F4: + return InputKey::F4; + case XKB_KEY_F5: + return InputKey::F5; + case XKB_KEY_F6: + return InputKey::F6; + case XKB_KEY_F7: + return InputKey::F7; + case XKB_KEY_F8: + return InputKey::F8; + case XKB_KEY_F9: + return InputKey::F9; + case XKB_KEY_F10: + return InputKey::F10; + case XKB_KEY_F11: + return InputKey::F11; + case XKB_KEY_F12: + return InputKey::F12; + case XKB_KEY_F13: + return InputKey::F13; + case XKB_KEY_F14: + return InputKey::F14; + case XKB_KEY_F15: + return InputKey::F15; + case XKB_KEY_F16: + return InputKey::F16; + case XKB_KEY_F17: + return InputKey::F17; + case XKB_KEY_F18: + return InputKey::F18; + case XKB_KEY_F19: + return InputKey::F19; + case XKB_KEY_F20: + return InputKey::F20; + case XKB_KEY_F21: + return InputKey::F21; + case XKB_KEY_F22: + return InputKey::F22; + case XKB_KEY_F23: + return InputKey::F23; + case XKB_KEY_F24: + return InputKey::F24; + case XKB_KEY_minus: + case XKB_KEY_KP_Subtract: + return InputKey::Minus; + case XKB_KEY_equal: + return InputKey::Equals; + case XKB_KEY_BackSpace: + return InputKey::Backspace; + case XKB_KEY_backslash: + return InputKey::Backslash; + case XKB_KEY_Tab: + return InputKey::Tab; + case XKB_KEY_braceleft: + return InputKey::LeftBracket; + case XKB_KEY_braceright: + return InputKey::RightBracket; + case XKB_KEY_Control_L: + case XKB_KEY_Control_R: + return InputKey::Ctrl; + case XKB_KEY_Alt_L: + case XKB_KEY_Alt_R: + return InputKey::Alt; + case XKB_KEY_Delete: + return InputKey::Delete; + case XKB_KEY_semicolon: + return InputKey::Semicolon; + case XKB_KEY_comma: + return InputKey::Comma; + case XKB_KEY_period: + return InputKey::Period; + case XKB_KEY_Num_Lock: + return InputKey::NumLock; + case XKB_KEY_Caps_Lock: + return InputKey::CapsLock; + case XKB_KEY_Scroll_Lock: + return InputKey::ScrollLock; + case XKB_KEY_Shift_L: + case XKB_KEY_Shift_R: + return InputKey::Shift; + case XKB_KEY_grave: + return InputKey::Tilde; + case XKB_KEY_apostrophe: + return InputKey::SingleQuote; + case XKB_KEY_KP_Enter: + case XKB_KEY_Return: + return InputKey::Enter; + case XKB_KEY_space: + return InputKey::Space; - case XKB_KEY_Up: - return InputKey::Up; - case XKB_KEY_Down: - return InputKey::Down; - case XKB_KEY_Left: - return InputKey::Left; - case XKB_KEY_Right: - return InputKey::Right; + case XKB_KEY_Up: + return InputKey::Up; + case XKB_KEY_Down: + return InputKey::Down; + case XKB_KEY_Left: + return InputKey::Left; + case XKB_KEY_Right: + return InputKey::Right; - case XKB_KEY_A: - case XKB_KEY_a: - return InputKey::A; - case XKB_KEY_B: - case XKB_KEY_b: - return InputKey::B; - case XKB_KEY_C: - case XKB_KEY_c: - return InputKey::C; - case XKB_KEY_D: - case XKB_KEY_d: - return InputKey::D; - case XKB_KEY_E: - case XKB_KEY_e: - return InputKey::E; - case XKB_KEY_F: - case XKB_KEY_f: - return InputKey::F; - case XKB_KEY_G: - case XKB_KEY_g: - return InputKey::G; - case XKB_KEY_H: - case XKB_KEY_h: - return InputKey::H; - case XKB_KEY_I: - case XKB_KEY_i: - return InputKey::I; - case XKB_KEY_J: - case XKB_KEY_j: - return InputKey::J; - case XKB_KEY_K: - case XKB_KEY_k: - return InputKey::K; - case XKB_KEY_L: - case XKB_KEY_l: - return InputKey::L; - case XKB_KEY_M: - case XKB_KEY_m: - return InputKey::M; - case XKB_KEY_N: - case XKB_KEY_n: - return InputKey::N; - case XKB_KEY_O: - case XKB_KEY_o: - return InputKey::O; - case XKB_KEY_P: - case XKB_KEY_p: - return InputKey::P; - case XKB_KEY_Q: - case XKB_KEY_q: - return InputKey::Q; - case XKB_KEY_R: - case XKB_KEY_r: - return InputKey::R; - case XKB_KEY_S: - case XKB_KEY_s: - return InputKey::S; - case XKB_KEY_T: - case XKB_KEY_t: - return InputKey::T; - case XKB_KEY_U: - case XKB_KEY_u: - return InputKey::U; - case XKB_KEY_V: - case XKB_KEY_v: - return InputKey::V; - case XKB_KEY_W: - case XKB_KEY_w: - return InputKey::W; - case XKB_KEY_X: - case XKB_KEY_x: - return InputKey::X; - case XKB_KEY_Y: - case XKB_KEY_y: - return InputKey::Y; - case XKB_KEY_Z: - case XKB_KEY_z: - return InputKey::Z; + case XKB_KEY_A: + case XKB_KEY_a: + return InputKey::A; + case XKB_KEY_B: + case XKB_KEY_b: + return InputKey::B; + case XKB_KEY_C: + case XKB_KEY_c: + return InputKey::C; + case XKB_KEY_D: + case XKB_KEY_d: + return InputKey::D; + case XKB_KEY_E: + case XKB_KEY_e: + return InputKey::E; + case XKB_KEY_F: + case XKB_KEY_f: + return InputKey::F; + case XKB_KEY_G: + case XKB_KEY_g: + return InputKey::G; + case XKB_KEY_H: + case XKB_KEY_h: + return InputKey::H; + case XKB_KEY_I: + case XKB_KEY_i: + return InputKey::I; + case XKB_KEY_J: + case XKB_KEY_j: + return InputKey::J; + case XKB_KEY_K: + case XKB_KEY_k: + return InputKey::K; + case XKB_KEY_L: + case XKB_KEY_l: + return InputKey::L; + case XKB_KEY_M: + case XKB_KEY_m: + return InputKey::M; + case XKB_KEY_N: + case XKB_KEY_n: + return InputKey::N; + case XKB_KEY_O: + case XKB_KEY_o: + return InputKey::O; + case XKB_KEY_P: + case XKB_KEY_p: + return InputKey::P; + case XKB_KEY_Q: + case XKB_KEY_q: + return InputKey::Q; + case XKB_KEY_R: + case XKB_KEY_r: + return InputKey::R; + case XKB_KEY_S: + case XKB_KEY_s: + return InputKey::S; + case XKB_KEY_T: + case XKB_KEY_t: + return InputKey::T; + case XKB_KEY_U: + case XKB_KEY_u: + return InputKey::U; + case XKB_KEY_V: + case XKB_KEY_v: + return InputKey::V; + case XKB_KEY_W: + case XKB_KEY_w: + return InputKey::W; + case XKB_KEY_X: + case XKB_KEY_x: + return InputKey::X; + case XKB_KEY_Y: + case XKB_KEY_y: + return InputKey::Y; + case XKB_KEY_Z: + case XKB_KEY_z: + return InputKey::Z; - case XKB_KEY_NoSymbol: - case XKB_KEY_VoidSymbol: - return InputKey::None; - default: - return InputKey::None; - } + case XKB_KEY_NoSymbol: + case XKB_KEY_VoidSymbol: + return InputKey::None; + default: + return InputKey::None; + } } InputKey WaylandDisplayBackend::LinuxInputEventCodeToInputKey(uint32_t inputCode) { - switch (inputCode) - { - // Keyboard - // Probably not needed due to the existence of XKBKeySym - case KEY_ESC: - return InputKey::Escape; - case KEY_1: - return InputKey::_1; - case KEY_2: - return InputKey::_2; - case KEY_3: - return InputKey::_3; - case KEY_4: - return InputKey::_4; - case KEY_5: - return InputKey::_5; - case KEY_6: - return InputKey::_6; - case KEY_7: - return InputKey::_7; - case KEY_8: - return InputKey::_8; - case KEY_9: - return InputKey::_9; - case KEY_0: - return InputKey::_0; - case KEY_KP1: - return InputKey::NumPad1; - case KEY_KP2: - return InputKey::NumPad2; - case KEY_KP3: - return InputKey::NumPad3; - case KEY_KP4: - return InputKey::NumPad4; - case KEY_KP5: - return InputKey::NumPad5; - case KEY_KP6: - return InputKey::NumPad6; - case KEY_KP7: - return InputKey::NumPad7; - case KEY_KP8: - return InputKey::NumPad8; - case KEY_KP9: - return InputKey::NumPad9; - case KEY_KP0: - return InputKey::NumPad0; - case KEY_F1: - return InputKey::F1; - case KEY_F2: - return InputKey::F2; - case KEY_F3: - return InputKey::F3; - case KEY_F4: - return InputKey::F4; - case KEY_F5: - return InputKey::F5; - case KEY_F6: - return InputKey::F6; - case KEY_F7: - return InputKey::F7; - case KEY_F8: - return InputKey::F8; - case KEY_F9: - return InputKey::F9; - case KEY_F10: - return InputKey::F10; - case KEY_F11: - return InputKey::F11; - case KEY_F12: - return InputKey::F12; - case KEY_F13: - return InputKey::F13; - case KEY_F14: - return InputKey::F14; - case KEY_F15: - return InputKey::F15; - case KEY_F16: - return InputKey::F16; - case KEY_F17: - return InputKey::F17; - case KEY_F18: - return InputKey::F18; - case KEY_F19: - return InputKey::F19; - case KEY_F20: - return InputKey::F20; - case KEY_F21: - return InputKey::F21; - case KEY_F22: - return InputKey::F22; - case KEY_F23: - return InputKey::F23; - case KEY_F24: - return InputKey::F24; - case KEY_MINUS: - case KEY_KPMINUS: - return InputKey::Minus; - case KEY_EQUAL: - return InputKey::Equals; - case KEY_BACKSPACE: - return InputKey::Backspace; - case KEY_BACKSLASH: - return InputKey::Backslash; - case KEY_TAB: - return InputKey::Tab; - case KEY_LEFTBRACE: - return InputKey::LeftBracket; - case KEY_RIGHTBRACE: - return InputKey::RightBracket; - case KEY_LEFTCTRL: - return InputKey::LControl; - case KEY_RIGHTCTRL: - return InputKey::RControl; - case KEY_LEFTALT: - case KEY_RIGHTALT: - return InputKey::Alt; - case KEY_DELETE: - return InputKey::Delete; - case KEY_SEMICOLON: - return InputKey::Semicolon; - case KEY_COMMA: - return InputKey::Comma; - case KEY_DOT: - return InputKey::Period; - case KEY_NUMLOCK: - return InputKey::NumLock; - case KEY_CAPSLOCK: - return InputKey::CapsLock; - case KEY_SCROLLDOWN: - return InputKey::ScrollLock; - case KEY_LEFTSHIFT: - return InputKey::LShift; - case KEY_RIGHTSHIFT: - return InputKey::RShift; - case KEY_GRAVE: - return InputKey::Tilde; - case KEY_APOSTROPHE: - return InputKey::SingleQuote; - case KEY_SPACE: - return InputKey::Space; - case KEY_ENTER: - case KEY_KPENTER: - return InputKey::Enter; + switch (inputCode) + { + // Keyboard + // Probably not needed due to the existence of XKBKeySym + case KEY_ESC: + return InputKey::Escape; + case KEY_1: + return InputKey::_1; + case KEY_2: + return InputKey::_2; + case KEY_3: + return InputKey::_3; + case KEY_4: + return InputKey::_4; + case KEY_5: + return InputKey::_5; + case KEY_6: + return InputKey::_6; + case KEY_7: + return InputKey::_7; + case KEY_8: + return InputKey::_8; + case KEY_9: + return InputKey::_9; + case KEY_0: + return InputKey::_0; + case KEY_KP1: + return InputKey::NumPad1; + case KEY_KP2: + return InputKey::NumPad2; + case KEY_KP3: + return InputKey::NumPad3; + case KEY_KP4: + return InputKey::NumPad4; + case KEY_KP5: + return InputKey::NumPad5; + case KEY_KP6: + return InputKey::NumPad6; + case KEY_KP7: + return InputKey::NumPad7; + case KEY_KP8: + return InputKey::NumPad8; + case KEY_KP9: + return InputKey::NumPad9; + case KEY_KP0: + return InputKey::NumPad0; + case KEY_F1: + return InputKey::F1; + case KEY_F2: + return InputKey::F2; + case KEY_F3: + return InputKey::F3; + case KEY_F4: + return InputKey::F4; + case KEY_F5: + return InputKey::F5; + case KEY_F6: + return InputKey::F6; + case KEY_F7: + return InputKey::F7; + case KEY_F8: + return InputKey::F8; + case KEY_F9: + return InputKey::F9; + case KEY_F10: + return InputKey::F10; + case KEY_F11: + return InputKey::F11; + case KEY_F12: + return InputKey::F12; + case KEY_F13: + return InputKey::F13; + case KEY_F14: + return InputKey::F14; + case KEY_F15: + return InputKey::F15; + case KEY_F16: + return InputKey::F16; + case KEY_F17: + return InputKey::F17; + case KEY_F18: + return InputKey::F18; + case KEY_F19: + return InputKey::F19; + case KEY_F20: + return InputKey::F20; + case KEY_F21: + return InputKey::F21; + case KEY_F22: + return InputKey::F22; + case KEY_F23: + return InputKey::F23; + case KEY_F24: + return InputKey::F24; + case KEY_MINUS: + case KEY_KPMINUS: + return InputKey::Minus; + case KEY_EQUAL: + return InputKey::Equals; + case KEY_BACKSPACE: + return InputKey::Backspace; + case KEY_BACKSLASH: + return InputKey::Backslash; + case KEY_TAB: + return InputKey::Tab; + case KEY_LEFTBRACE: + return InputKey::LeftBracket; + case KEY_RIGHTBRACE: + return InputKey::RightBracket; + case KEY_LEFTCTRL: + return InputKey::LControl; + case KEY_RIGHTCTRL: + return InputKey::RControl; + case KEY_LEFTALT: + case KEY_RIGHTALT: + return InputKey::Alt; + case KEY_DELETE: + return InputKey::Delete; + case KEY_SEMICOLON: + return InputKey::Semicolon; + case KEY_COMMA: + return InputKey::Comma; + case KEY_DOT: + return InputKey::Period; + case KEY_NUMLOCK: + return InputKey::NumLock; + case KEY_CAPSLOCK: + return InputKey::CapsLock; + case KEY_SCROLLDOWN: + return InputKey::ScrollLock; + case KEY_LEFTSHIFT: + return InputKey::LShift; + case KEY_RIGHTSHIFT: + return InputKey::RShift; + case KEY_GRAVE: + return InputKey::Tilde; + case KEY_APOSTROPHE: + return InputKey::SingleQuote; + case KEY_SPACE: + return InputKey::Space; + case KEY_ENTER: + case KEY_KPENTER: + return InputKey::Enter; - case KEY_UP: - return InputKey::Up; - case KEY_DOWN: - return InputKey::Down; - case KEY_LEFT: - return InputKey::Left; - case KEY_RIGHT: - return InputKey::Right; + case KEY_UP: + return InputKey::Up; + case KEY_DOWN: + return InputKey::Down; + case KEY_LEFT: + return InputKey::Left; + case KEY_RIGHT: + return InputKey::Right; - case KEY_A: - return InputKey::A; - case KEY_B: - return InputKey::B; - case KEY_C: - return InputKey::C; - case KEY_D: - return InputKey::D; - case KEY_E: - return InputKey::E; - case KEY_F: - return InputKey::F; - case KEY_G: - return InputKey::G; - case KEY_H: - return InputKey::H; - case KEY_I: - return InputKey::I; - case KEY_J: - return InputKey::J; - case KEY_K: - return InputKey::K; - case KEY_L: - return InputKey::L; - case KEY_M: - return InputKey::M; - case KEY_N: - return InputKey::N; - case KEY_O: - return InputKey::O; - case KEY_P: - return InputKey::P; - case KEY_Q: - return InputKey::Q; - case KEY_R: - return InputKey::R; - case KEY_S: - return InputKey::S; - case KEY_T: - return InputKey::T; - case KEY_U: - return InputKey::U; - case KEY_V: - return InputKey::V; - case KEY_W: - return InputKey::W; - case KEY_X: - return InputKey::X; - case KEY_Y: - return InputKey::Y; - case KEY_Z: - return InputKey::Z; + case KEY_A: + return InputKey::A; + case KEY_B: + return InputKey::B; + case KEY_C: + return InputKey::C; + case KEY_D: + return InputKey::D; + case KEY_E: + return InputKey::E; + case KEY_F: + return InputKey::F; + case KEY_G: + return InputKey::G; + case KEY_H: + return InputKey::H; + case KEY_I: + return InputKey::I; + case KEY_J: + return InputKey::J; + case KEY_K: + return InputKey::K; + case KEY_L: + return InputKey::L; + case KEY_M: + return InputKey::M; + case KEY_N: + return InputKey::N; + case KEY_O: + return InputKey::O; + case KEY_P: + return InputKey::P; + case KEY_Q: + return InputKey::Q; + case KEY_R: + return InputKey::R; + case KEY_S: + return InputKey::S; + case KEY_T: + return InputKey::T; + case KEY_U: + return InputKey::U; + case KEY_V: + return InputKey::V; + case KEY_W: + return InputKey::W; + case KEY_X: + return InputKey::X; + case KEY_Y: + return InputKey::Y; + case KEY_Z: + return InputKey::Z; - // Mouse - case BTN_LEFT: - return InputKey::LeftMouse; - case BTN_RIGHT: - return InputKey::RightMouse; - case BTN_MIDDLE: - return InputKey::MiddleMouse; - default: - return InputKey::None; - } + // Mouse + case BTN_LEFT: + return InputKey::LeftMouse; + case BTN_RIGHT: + return InputKey::RightMouse; + case BTN_MIDDLE: + return InputKey::MiddleMouse; + default: + return InputKey::None; + } } std::string WaylandDisplayBackend::GetWaylandCursorName(StandardCursor cursor) { - // Checked out Adwaita and Breeze cursors for the names. - // Other cursor themes should adhere to the names these two have. - switch (cursor) - { - case StandardCursor::arrow: - return "default"; - case StandardCursor::appstarting: - return "progress"; - case StandardCursor::cross: - return "crosshair"; - case StandardCursor::hand: - return "pointer"; - case StandardCursor::ibeam: - return "text"; - case StandardCursor::no: - return "not-allowed"; - case StandardCursor::size_all: - return "fleur"; - case StandardCursor::size_nesw: - return "nesw-resize"; - case StandardCursor::size_ns: - return "ns-resize"; - case StandardCursor::size_nwse: - return "nwse-resize"; - case StandardCursor::size_we: - return "ew-resize"; - case StandardCursor::uparrow: - // Breeze actually has an up-arrow cursor, but Adwaita doesn't, so the default cursor it is - return "default"; - case StandardCursor::wait: - return "wait"; - default: - return "default"; - } + // Checked out Adwaita and Breeze cursors for the names. + // Other cursor themes should adhere to the names these two have. + switch (cursor) + { + case StandardCursor::arrow: + return "default"; + case StandardCursor::appstarting: + return "progress"; + case StandardCursor::cross: + return "crosshair"; + case StandardCursor::hand: + return "pointer"; + case StandardCursor::ibeam: + return "text"; + case StandardCursor::no: + return "not-allowed"; + case StandardCursor::size_all: + return "fleur"; + case StandardCursor::size_nesw: + return "nesw-resize"; + case StandardCursor::size_ns: + return "ns-resize"; + case StandardCursor::size_nwse: + return "nwse-resize"; + case StandardCursor::size_we: + return "ew-resize"; + case StandardCursor::uparrow: + // Breeze actually has an up-arrow cursor, but Adwaita doesn't, so the default cursor it is + return "default"; + case StandardCursor::wait: + return "wait"; + default: + return "default"; + } } +wayland::cursor_shape_device_v1_shape WaylandDisplayBackend::GetWaylandCursorShape(StandardCursor cursor) +{ + switch (cursor) + { + case StandardCursor::arrow: + return wayland::cursor_shape_device_v1_shape::_default; + case StandardCursor::appstarting: + return wayland::cursor_shape_device_v1_shape::progress; + case StandardCursor::cross: + return wayland::cursor_shape_device_v1_shape::crosshair; + case StandardCursor::hand: + return wayland::cursor_shape_device_v1_shape::pointer; + case StandardCursor::ibeam: + return wayland::cursor_shape_device_v1_shape::text; + case StandardCursor::no: + return wayland::cursor_shape_device_v1_shape::not_allowed; + case StandardCursor::size_all: + return wayland::cursor_shape_device_v1_shape::all_resize; + case StandardCursor::size_nesw: + return wayland::cursor_shape_device_v1_shape::nesw_resize; + case StandardCursor::size_ns: + return wayland::cursor_shape_device_v1_shape::ns_resize; + case StandardCursor::size_nwse: + return wayland::cursor_shape_device_v1_shape::nwse_resize; + case StandardCursor::size_we: + return wayland::cursor_shape_device_v1_shape::ew_resize; + case StandardCursor::uparrow: + // Up arrow cursor doesn't seem to be defined in the shapes, so _default it is + return wayland::cursor_shape_device_v1_shape::_default; + case StandardCursor::wait: + return wayland::cursor_shape_device_v1_shape::wait; + default: + return wayland::cursor_shape_device_v1_shape::_default; + } +} diff --git a/libraries/ZWidget/src/window/wayland/wayland_display_backend.h b/libraries/ZWidget/src/window/wayland/wayland_display_backend.h index 26e3e7f64..b9912fdb8 100644 --- a/libraries/ZWidget/src/window/wayland/wayland_display_backend.h +++ b/libraries/ZWidget/src/window/wayland/wayland_display_backend.h @@ -9,49 +9,69 @@ #include #include #include "wl_fractional_scaling_protocol.hpp" +#include "wl_xdg_toplevel_icon.hpp" #include #include #include +#include #include -static short poll_single(int fd, short events, int timeout) { - pollfd pfd { .fd = fd, .events = events, .revents = 0 }; - if (0 > poll(&pfd, 1, timeout)) { - throw std::runtime_error("poll() failed"); - } +#include "wl_cursor_shape.hpp" - return pfd.revents; +inline auto hash_proxy = [](wayland::proxy_t const& proxy) { return reinterpret_cast(proxy.c_ptr()); }; +using hash_proxy_t = decltype(hash_proxy); + +static short poll_single(int fd, short events, int timeout) +{ + pollfd pfd { .fd = fd, .events = events, .revents = 0 }; + if (0 > poll(&pfd, 1, timeout)) + { + throw std::runtime_error("poll() failed"); + } + + return pfd.revents; } -enum pointer_event_mask { - POINTER_EVENT_ENTER = 1 << 0, - POINTER_EVENT_LEAVE = 1 << 1, - POINTER_EVENT_MOTION = 1 << 2, - POINTER_EVENT_BUTTON = 1 << 3, - POINTER_EVENT_AXIS = 1 << 4, - POINTER_EVENT_AXIS_SOURCE = 1 << 5, - POINTER_EVENT_AXIS_STOP = 1 << 6, - POINTER_EVENT_AXIS_DISCRETE = 1 << 7, - POINTER_EVENT_AXIS_120 = 1 << 8, - POINTER_EVENT_RELATIVE_MOTION = 1 << 9, +enum pointer_event_mask +{ + POINTER_EVENT_ENTER = 1 << 0, + POINTER_EVENT_LEAVE = 1 << 1, + POINTER_EVENT_MOTION = 1 << 2, + POINTER_EVENT_BUTTON = 1 << 3, + POINTER_EVENT_AXIS = 1 << 4, + POINTER_EVENT_AXIS_SOURCE = 1 << 5, + POINTER_EVENT_AXIS_STOP = 1 << 6, + POINTER_EVENT_AXIS_DISCRETE = 1 << 7, + POINTER_EVENT_AXIS_120 = 1 << 8, + POINTER_EVENT_RELATIVE_MOTION = 1 << 9, }; struct WaylandPointerEvent { - uint32_t event_mask; - double surfaceX, surfaceY; - double dx, dy; - uint32_t button; - wayland::pointer_button_state state; - uint32_t time; - uint32_t serial; - struct { - bool valid; - double value; - int32_t discrete; - int32_t value120; - } axes[2]; - wayland::pointer_axis_source axis_source; + uint32_t event_mask; + double surfaceX, surfaceY; + double dx, dy, dx_unaccel, dy_unaccel; + uint32_t button; + wayland::pointer_button_state state; + uint32_t time; + uint32_t serial; + struct { + bool valid; + double value; + int32_t discrete; + int32_t value120; + } axes[2]; + wayland::pointer_axis_source axis_source; +}; + +class WaylandTimer +{ +public: + WaylandTimer(int timeoutMilliseconds, std::function onTimer, int64_t nextTime) : timeoutMilliseconds(timeoutMilliseconds), onTimer(onTimer), nextTime(nextTime) {} + + int timeoutMilliseconds = 0; + std::function onTimer; + int64_t nextTime = 0; }; class WaylandDisplayWindow; @@ -60,7 +80,7 @@ class WaylandDisplayBackend : public DisplayBackend { public: WaylandDisplayBackend(); - ~WaylandDisplayBackend(); + ~WaylandDisplayBackend(); std::unique_ptr Create(DisplayWindowHost* windowHost, bool popupWindow, DisplayWindow* owner, RenderAPI renderAPI) override; void ProcessEvents() override; @@ -78,9 +98,13 @@ public: void OnWindowDestroyed(WaylandDisplayWindow* window); void SetCursor(StandardCursor cursor); - void ShowCursor(bool enable); + void ShowCursor(bool enable); bool GetKeyState(InputKey key); + std::string GetClipboardText() { return m_ClipboardContents; } + wayland::data_device_t& GetDataDevice() { return m_DataDevice; } + uint32_t GetKeyboardSerial() const { return m_KeyboardSerial; } + #ifdef USE_DBUS std::unique_ptr CreateOpenFileDialog(DisplayWindow* owner) override; std::unique_ptr CreateSaveFileDialog(DisplayWindow* owner) override; @@ -93,76 +117,92 @@ public: wayland::registry_t s_waylandRegistry; std::vector s_Windows; WaylandDisplayWindow* m_FocusWindow = nullptr; - WaylandDisplayWindow* m_MouseFocusWindow = nullptr; // Mouse focus should be tracked separately. + WaylandDisplayWindow* m_MouseFocusWindow = nullptr; // Mouse focus should be tracked separately. - wayland::compositor_t m_waylandCompositor; - wayland::shm_t m_waylandSHM; - wayland::seat_t m_waylandSeat; - wayland::output_t m_waylandOutput; - wayland::data_device_manager_t m_DataDeviceManager; - wayland::xdg_wm_base_t m_XDGWMBase; - wayland::zwp_pointer_constraints_v1_t m_PointerConstraints; - wayland::xdg_activation_v1_t m_XDGActivation; - wayland::zxdg_decoration_manager_v1_t m_XDGDecorationManager; - wayland::fractional_scale_manager_v1_t m_FractionalScaleManager; - wayland::zxdg_output_manager_v1_t m_XDGOutputManager; - wayland::zxdg_output_v1_t m_XDGOutput; - wayland::zxdg_exporter_v2_t m_XDGExporter; + wayland::compositor_t m_waylandCompositor; + wayland::shm_t m_waylandSHM; + wayland::seat_t m_waylandSeat; + wayland::output_t m_waylandOutput; + wayland::data_device_manager_t m_DataDeviceManager; + wayland::xdg_wm_base_t m_XDGWMBase; + wayland::zwp_pointer_constraints_v1_t m_PointerConstraints; + wayland::xdg_activation_v1_t m_XDGActivation; + wayland::zxdg_decoration_manager_v1_t m_XDGDecorationManager; + wayland::fractional_scale_manager_v1_t m_FractionalScaleManager; + wayland::zxdg_output_manager_v1_t m_XDGOutputManager; + wayland::zxdg_output_v1_t m_XDGOutput; + wayland::zxdg_exporter_v2_t m_XDGExporter; - wayland::keyboard_t m_waylandKeyboard; - wayland::pointer_t m_waylandPointer; + wayland::keyboard_t m_waylandKeyboard; + wayland::pointer_t m_waylandPointer; - wayland::zwp_relative_pointer_manager_v1_t m_RelativePointerManager; - wayland::zwp_relative_pointer_v1_t m_RelativePointer; + wayland::zwp_relative_pointer_manager_v1_t m_RelativePointerManager; + wayland::zwp_relative_pointer_v1_t m_RelativePointer; - wayland::cursor_image_t m_cursorImage; - wayland::surface_t m_cursorSurface; - wayland::buffer_t m_cursorBuffer; + wayland::xdg_toplevel_icon_manager_v1_t m_XDGToplevelIconManager; - std::map inputKeyStates; // True when the key is pressed, false when isn't + wayland::cursor_shape_manager_v1_t m_CursorShapeManager; + wayland::cursor_shape_device_v1_t m_CursorShapeDevice; - bool IsMouseLocked() { return hasMouseLock; } - void SetMouseLocked(bool val) { hasMouseLock = val; } + wayland::cursor_image_t m_cursorImage; + wayland::surface_t m_cursorSurface; + wayland::buffer_t m_cursorBuffer; + + std::map inputKeyStates; // True when the key is pressed, false when isn't + + bool IsMouseLocked() { return hasMouseLock; } + void SetMouseLocked(bool val) { hasMouseLock = val; } private: - void CheckNeedsUpdate(); + void CheckNeedsUpdate(); void UpdateTimers(); - void ConnectDeviceEvents(); - void OnKeyboardKeyEvent(xkb_keysym_t xkbKeySym, wayland::keyboard_key_state state); - void OnKeyboardCharEvent(const char* ch, wayland::keyboard_key_state state); - void OnKeyboardDelayEnd(); - void OnKeyboardRepeat(); - void OnMouseEnterEvent(uint32_t serial); - void OnMouseLeaveEvent(); - void OnMousePressEvent(InputKey button); - void OnMouseReleaseEvent(InputKey button); - void OnMouseMoveEvent(Point surfacePos); - void OnMouseMoveRawEvent(int surfaceX, int surfaceY); - void OnMouseWheelEvent(InputKey button); + void WaitForEvents(int timeout); + int GetTimerTimeout(); + void ConnectDeviceEvents(); + void OnKeyboardKeyEvent(xkb_keysym_t xkbKeySym, wayland::keyboard_key_state state); + void OnKeyboardCharEvent(const char* ch, wayland::keyboard_key_state state); + void OnKeyboardDelayEnd(); + void OnKeyboardRepeat(); + void OnMouseEnterEvent(uint32_t serial); + void OnMouseLeaveEvent(); + void OnMousePressEvent(InputKey button); + void OnMouseReleaseEvent(InputKey button); + void OnMouseMoveEvent(Point surfacePos); + void OnMouseMoveRawEvent(int surfaceX, int surfaceY); + void OnMouseWheelEvent(InputKey button); - InputKey XKBKeySymToInputKey(xkb_keysym_t keySym); - InputKey LinuxInputEventCodeToInputKey(uint32_t inputCode); + InputKey XKBKeySymToInputKey(xkb_keysym_t keySym); + InputKey LinuxInputEventCodeToInputKey(uint32_t inputCode); - std::string GetWaylandCursorName(StandardCursor cursor); + wayland::cursor_shape_device_v1_shape GetWaylandCursorShape(StandardCursor cursor); + std::string GetWaylandCursorName(StandardCursor cursor); - bool hasKeyboard = false; - bool hasPointer = false; - bool hasMouseLock = false; + bool hasKeyboard = false; + bool hasPointer = false; + bool hasMouseLock = false; - ZTimer::TimePoint m_previousTime; - ZTimer::TimePoint m_currentTime; + ZTimer::TimePoint m_previousTime; + ZTimer::TimePoint m_currentTime; - ZTimer m_keyboardDelayTimer; - ZTimer m_keyboardRepeatTimer; + ZTimer m_keyboardDelayTimer; + ZTimer m_keyboardRepeatTimer; - InputKey previousKey = {}; - std::string previousChars; + InputKey previousKey = {}; + std::string previousChars; - uint32_t m_KeyboardSerial = 0; + uint32_t m_KeyboardSerial = 0; + uint32_t m_MouseSerial = 0; - xkb_context* m_KeymapContext = nullptr; - xkb_keymap* m_Keymap = nullptr; - xkb_state* m_KeyboardState = nullptr; + xkb_context* m_KeymapContext = nullptr; + xkb_keymap* m_Keymap = nullptr; + xkb_state* m_KeyboardState = nullptr; - WaylandPointerEvent currentPointerEvent = {0}; + WaylandPointerEvent currentPointerEvent = {0}; + + wayland::data_device_t m_DataDevice; + std::unordered_map, hash_proxy_t> m_DataOfferMimeTypes; + + std::string m_ClipboardContents; + + std::vector> m_timers; }; diff --git a/libraries/ZWidget/src/window/wayland/wayland_display_window.cpp b/libraries/ZWidget/src/window/wayland/wayland_display_window.cpp index f41b3317e..22c1668f0 100644 --- a/libraries/ZWidget/src/window/wayland/wayland_display_window.cpp +++ b/libraries/ZWidget/src/window/wayland/wayland_display_window.cpp @@ -3,288 +3,313 @@ #include #include +#include "core/image.h" + WaylandDisplayWindow::WaylandDisplayWindow(WaylandDisplayBackend* backend, DisplayWindowHost* windowHost, bool popupWindow, WaylandDisplayWindow* owner, RenderAPI renderAPI) - : backend(backend), m_owner(owner), windowHost(windowHost), m_PopupWindow(popupWindow), m_renderAPI(renderAPI) + : backend(backend), m_owner(owner), windowHost(windowHost), m_PopupWindow(popupWindow), m_renderAPI(renderAPI) { - m_AppSurface = backend->m_waylandCompositor.create_surface(); + m_AppSurface = backend->m_waylandCompositor.create_surface(); - m_NativeHandle.display = backend->s_waylandDisplay; - m_NativeHandle.surface = m_AppSurface; + m_NativeHandle.display = backend->s_waylandDisplay; + m_NativeHandle.surface = m_AppSurface; - if (backend->m_FractionalScaleManager) - { - m_FractionalScale = backend->m_FractionalScaleManager.get_fractional_scale(m_AppSurface); + if (backend->m_FractionalScaleManager) + { + m_FractionalScale = backend->m_FractionalScaleManager.get_fractional_scale(m_AppSurface); - m_FractionalScale.on_preferred_scale() = [&](uint32_t scale_numerator) { - // parameter is the numerator of a fraction with the denominator of 120 - m_ScaleFactor = scale_numerator / 120.0; + m_FractionalScale.on_preferred_scale() = [&](uint32_t scale_numerator) { + // parameter is the numerator of a fraction with the denominator of 120 + m_ScaleFactor = scale_numerator / 120.0; - m_NeedsUpdate = true; - windowHost->OnWindowDpiScaleChanged(); - }; - } + m_NeedsUpdate = true; + windowHost->OnWindowDpiScaleChanged(); + }; + } - m_XDGSurface = backend->m_XDGWMBase.get_xdg_surface(m_AppSurface); - m_XDGSurface.on_configure() = [&] (uint32_t serial) { - m_XDGSurface.ack_configure(serial); - }; + m_XDGSurface = backend->m_XDGWMBase.get_xdg_surface(m_AppSurface); + m_XDGSurface.on_configure() = [&] (uint32_t serial) { + m_XDGSurface.ack_configure(serial); + }; - if (popupWindow) - { - InitializePopup(); - } - else - { - InitializeToplevel(); - } + if (popupWindow) + { + InitializePopup(); + } + else + { + InitializeToplevel(); + } - backend->OnWindowCreated(this); + backend->OnWindowCreated(this); - this->DrawSurface(); + this->DrawSurface(); } WaylandDisplayWindow::~WaylandDisplayWindow() { - backend->OnWindowDestroyed(this); + backend->OnWindowDestroyed(this); } void WaylandDisplayWindow::InitializeToplevel() { - m_XDGToplevel = m_XDGSurface.get_toplevel(); - m_XDGToplevel.set_title("ZWidget Window"); + m_XDGToplevel = m_XDGSurface.get_toplevel(); + m_XDGToplevel.set_title("ZWidget Window"); - if (m_owner) - m_XDGToplevel.set_parent(m_owner->m_XDGToplevel); + if (m_owner) + m_XDGToplevel.set_parent(m_owner->m_XDGToplevel); - if (backend->m_XDGDecorationManager) - { - // Force server side decorations if possible - m_XDGToplevelDecoration = backend->m_XDGDecorationManager.get_toplevel_decoration(m_XDGToplevel); - m_XDGToplevelDecoration.set_mode(wayland::zxdg_toplevel_decoration_v1_mode::server_side); - } + if (backend->m_XDGDecorationManager) + { + // Force server side decorations if possible + m_XDGToplevelDecoration = backend->m_XDGDecorationManager.get_toplevel_decoration(m_XDGToplevel); + m_XDGToplevelDecoration.set_mode(wayland::zxdg_toplevel_decoration_v1_mode::server_side); + } - m_AppSurface.commit(); + m_AppSurface.commit(); - backend->s_waylandDisplay.roundtrip(); + backend->s_waylandDisplay.roundtrip(); - // These have to be added after the roundtrip - m_XDGToplevel.on_configure() = [&] (int32_t width, int32_t height, wayland::array_t states) { - OnXDGToplevelConfigureEvent(width, height); - }; + // These have to be added after the roundtrip + m_XDGToplevel.on_configure() = [&] (int32_t width, int32_t height, wayland::array_t states) { + OnXDGToplevelConfigureEvent(width, height); + }; - m_XDGToplevel.on_close() = [&] () { - OnExitEvent(); - }; + m_XDGToplevel.on_close() = [&] () { + OnExitEvent(); + }; - m_XDGToplevel.on_configure_bounds() = [this] (int32_t width, int32_t height) - { + m_XDGToplevel.on_configure_bounds() = [this] (int32_t width, int32_t height) + { - }; + }; - m_XDGExported = backend->m_XDGExporter.export_toplevel(m_AppSurface); + m_XDGExported = backend->m_XDGExporter.export_toplevel(m_AppSurface); - m_XDGExported.on_handle() = [&] (std::string handleStr) { - OnExportHandleEvent(handleStr); - }; + m_XDGExported.on_handle() = [&] (std::string handleStr) { + OnExportHandleEvent(handleStr); + }; } void WaylandDisplayWindow::InitializePopup() { - if (!m_owner) - throw std::runtime_error("Popup window must have an owner!"); + if (!m_owner) + throw std::runtime_error("Popup window must have an owner!"); - wayland::xdg_positioner_t popupPositioner = backend->m_XDGWMBase.create_positioner(); + wayland::xdg_positioner_t popupPositioner = backend->m_XDGWMBase.create_positioner(); - popupPositioner.set_anchor(wayland::xdg_positioner_anchor::bottom); - popupPositioner.set_anchor_rect(0, 0, 1, 30); - popupPositioner.set_size(1, 1); + popupPositioner.set_anchor(wayland::xdg_positioner_anchor::bottom); + popupPositioner.set_anchor_rect(0, 0, 1, 30); + popupPositioner.set_size(1, 1); - m_XDGPopup = m_XDGSurface.get_popup(m_owner->m_XDGSurface, popupPositioner); + m_XDGPopup = m_XDGSurface.get_popup(m_owner->m_XDGSurface, popupPositioner); - m_XDGPopup.on_configure() = [&] (int32_t x, int32_t y, int32_t width, int32_t height) { - SetClientFrame(Rect::xywh(x, y, width, height)); - }; + m_XDGPopup.on_configure() = [&] (int32_t x, int32_t y, int32_t width, int32_t height) { + SetClientFrame(Rect::xywh(x, y, width, height)); + }; - //m_XDGPopup.on_repositioned() + //m_XDGPopup.on_repositioned() - m_XDGPopup.on_popup_done() = [&] () { - OnExitEvent(); - }; + m_XDGPopup.on_popup_done() = [&] () { + OnExitEvent(); + }; - m_AppSurface.commit(); + m_AppSurface.commit(); - backend->s_waylandDisplay.roundtrip(); + backend->s_waylandDisplay.roundtrip(); } void WaylandDisplayWindow::SetWindowTitle(const std::string& text) { - if (m_XDGToplevel) - m_XDGToplevel.set_title(text); + if (m_XDGToplevel) + m_XDGToplevel.set_title(text); +} + +void WaylandDisplayWindow::SetWindowIcon(const std::vector>& images) +{ + if (backend->m_XDGToplevelIconManager) + { + if (images.empty()) + { + backend->m_XDGToplevelIconManager.set_icon(m_XDGToplevel, nullptr); + return; + } + + m_XDGToplevelIcon = backend->m_XDGToplevelIconManager.create_icon(); + + CreateAppIconBuffers(images); + + for (auto& icon_buffer : appIconBuffers) + { + m_XDGToplevelIcon.add_buffer(icon_buffer, 1); + } + + backend->m_XDGToplevelIconManager.set_icon(m_XDGToplevel, m_XDGToplevelIcon); + } } void WaylandDisplayWindow::SetWindowFrame(const Rect& box) { - // Resizing will be shown on the next commit - CreateBuffers(box.width, box.height); - windowHost->OnWindowGeometryChanged(); - m_NeedsUpdate = true; - m_AppSurface.commit(); + // Resizing will be shown on the next commit + CreateBuffers(box.width, box.height); + windowHost->OnWindowGeometryChanged(); + m_NeedsUpdate = true; + m_AppSurface.commit(); } void WaylandDisplayWindow::SetClientFrame(const Rect& box) { - SetWindowFrame(box); + SetWindowFrame(box); } void WaylandDisplayWindow::Show() { - m_AppSurface.attach(m_AppSurfaceBuffer, 0, 0); - m_AppSurface.damage(0, 0, m_WindowSize.width, m_WindowSize.height); - m_AppSurface.commit(); + m_AppSurface.attach(m_AppSurfaceBuffer, 0, 0); + m_AppSurface.damage(0, 0, m_WindowSize.width, m_WindowSize.height); + m_AppSurface.commit(); } void WaylandDisplayWindow::ShowFullscreen() { - if (m_XDGToplevel) - { - m_XDGToplevel.set_fullscreen(backend->m_waylandOutput); - isFullscreen = true; - } + if (m_XDGToplevel) + { + m_XDGToplevel.set_fullscreen(backend->m_waylandOutput); + isFullscreen = true; + } } void WaylandDisplayWindow::ShowMaximized() { - if (m_XDGToplevel) - m_XDGToplevel.set_maximized(); + if (m_XDGToplevel) + m_XDGToplevel.set_maximized(); } void WaylandDisplayWindow::ShowMinimized() { - if (m_XDGToplevel) - m_XDGToplevel.set_minimized(); + if (m_XDGToplevel) + m_XDGToplevel.set_minimized(); } void WaylandDisplayWindow::ShowNormal() { - if (m_XDGToplevel) - m_XDGToplevel.unset_fullscreen(); + if (m_XDGToplevel) + m_XDGToplevel.unset_fullscreen(); } bool WaylandDisplayWindow::IsWindowFullscreen() { - return isFullscreen; + return isFullscreen; } void WaylandDisplayWindow::Hide() { - // Apparently this is how hiding a window works - // By attaching a null buffer to the surface - // See: https://lists.freedesktop.org/archives/wayland-devel/2017-November/035963.html - m_AppSurface.attach(nullptr, 0, 0); - m_AppSurface.commit(); + // Apparently this is how hiding a window works + // By attaching a null buffer to the surface + // See: https://lists.freedesktop.org/archives/wayland-devel/2017-November/035963.html + m_AppSurface.attach(nullptr, 0, 0); + m_AppSurface.commit(); } void WaylandDisplayWindow::Activate() { - // To do: this needs to be in the backend instance if all windows share the activation token - wayland::xdg_activation_token_v1_t xdgActivationToken = backend->m_XDGActivation.get_activation_token(); + // To do: this needs to be in the backend instance if all windows share the activation token + wayland::xdg_activation_token_v1_t xdgActivationToken = backend->m_XDGActivation.get_activation_token(); - std::string tokenString; + std::string tokenString; - xdgActivationToken.on_done() = [&tokenString] (std::string obtainedString) { - tokenString = obtainedString; - }; + xdgActivationToken.on_done() = [&tokenString] (std::string obtainedString) { + tokenString = obtainedString; + }; - xdgActivationToken.set_surface(m_AppSurface); - xdgActivationToken.commit(); // This will set our token string + xdgActivationToken.set_surface(m_AppSurface); + xdgActivationToken.commit(); // This will set our token string - backend->m_XDGActivation.activate(tokenString, m_AppSurface); - backend->m_FocusWindow = this; - backend->m_MouseFocusWindow = this; - windowHost->OnWindowActivated(); + backend->m_XDGActivation.activate(tokenString, m_AppSurface); + backend->m_FocusWindow = this; + backend->m_MouseFocusWindow = this; + windowHost->OnWindowActivated(); } void WaylandDisplayWindow::ShowCursor(bool enable) { - backend->ShowCursor(enable); + backend->ShowCursor(enable); } void WaylandDisplayWindow::LockCursor() { - m_LockedPointer = backend->m_PointerConstraints.lock_pointer(m_AppSurface, backend->m_waylandPointer, nullptr, wayland::zwp_pointer_constraints_v1_lifetime::persistent); - backend->SetMouseLocked(true); - ShowCursor(false); + m_LockedPointer = backend->m_PointerConstraints.lock_pointer(m_AppSurface, backend->m_waylandPointer, nullptr, wayland::zwp_pointer_constraints_v1_lifetime::persistent); + backend->SetMouseLocked(true); + ShowCursor(false); } void WaylandDisplayWindow::UnlockCursor() { - if (m_LockedPointer) - m_LockedPointer.proxy_release(); - backend->SetMouseLocked(false); - ShowCursor(true); + if (m_LockedPointer) + m_LockedPointer.proxy_release(); + backend->SetMouseLocked(false); + ShowCursor(true); } void WaylandDisplayWindow::CaptureMouse() { - m_ConfinedPointer = backend->m_PointerConstraints.confine_pointer(GetWindowSurface(), backend->m_waylandPointer, nullptr, wayland::zwp_pointer_constraints_v1_lifetime::persistent); - ShowCursor(false); + m_ConfinedPointer = backend->m_PointerConstraints.confine_pointer(GetWindowSurface(), backend->m_waylandPointer, nullptr, wayland::zwp_pointer_constraints_v1_lifetime::persistent); + ShowCursor(false); } void WaylandDisplayWindow::ReleaseMouseCapture() { - if (m_ConfinedPointer) - m_ConfinedPointer.proxy_release(); - ShowCursor(true); + if (m_ConfinedPointer) + m_ConfinedPointer.proxy_release(); + ShowCursor(true); } void WaylandDisplayWindow::Update() { - m_NeedsUpdate = true; + m_NeedsUpdate = true; } bool WaylandDisplayWindow::GetKeyState(InputKey key) { - return backend->GetKeyState(key); + return backend->GetKeyState(key); } void WaylandDisplayWindow::SetCursor(StandardCursor cursor) { - backend->SetCursor(cursor); + backend->SetCursor(cursor); } Rect WaylandDisplayWindow::GetWindowFrame() const { - return Rect(m_WindowGlobalPos.x, m_WindowGlobalPos.y, m_WindowSize.width, m_WindowSize.height); + return Rect(m_WindowGlobalPos.x, m_WindowGlobalPos.y, m_WindowSize.width, m_WindowSize.height); } Size WaylandDisplayWindow::GetClientSize() const { - return m_WindowSize / m_ScaleFactor; + return m_WindowSize / m_ScaleFactor; } int WaylandDisplayWindow::GetPixelWidth() const { - return m_WindowSize.width; + return m_WindowSize.width; } int WaylandDisplayWindow::GetPixelHeight() const { - return m_WindowSize.height; + return m_WindowSize.height; } double WaylandDisplayWindow::GetDpiScale() const { - return m_ScaleFactor; + return m_ScaleFactor; } void WaylandDisplayWindow::PresentBitmap(int width, int height, const uint32_t* pixels) { - // Make new buffers if the sizes don't match - if (width != m_WindowSize.width || height != m_WindowSize.height) - CreateBuffers(width, height); + // Make new buffers if the sizes don't match + if (width != m_WindowSize.width || height != m_WindowSize.height) + CreateBuffers(width, height); - std::memcpy(shared_mem->get_mem(), (void*)pixels, width * height * 4); + std::memcpy(shared_mem->get_mem(), (void*)pixels, width * height * 4); } void WaylandDisplayWindow::SetBorderColor(uint32_t bgra8) @@ -304,79 +329,115 @@ void WaylandDisplayWindow::SetCaptionTextColor(uint32_t bgra8) std::string WaylandDisplayWindow::GetClipboardText() { - return m_ClipboardContents; + return backend->GetClipboardText(); } void WaylandDisplayWindow::SetClipboardText(const std::string& text) { - m_ClipboardContents = text; + m_DataSource = backend->m_DataDeviceManager.create_data_source(); + + m_DataSource.on_send() = [text] (const std::string& mime_type, const int fd) + { + write(fd, text.c_str(), text.size()); + close(fd); + }; + + m_DataSource.offer("text/plain"); + backend->GetDataDevice().set_selection(m_DataSource, backend->GetKeyboardSerial()); } Point WaylandDisplayWindow::MapFromGlobal(const Point& pos) const { - return (pos - m_WindowGlobalPos) / m_ScaleFactor; + return (pos - m_WindowGlobalPos) / m_ScaleFactor; } Point WaylandDisplayWindow::MapToGlobal(const Point& pos) const { - return (m_WindowGlobalPos + pos) / m_ScaleFactor; + return (m_WindowGlobalPos + pos) / m_ScaleFactor; } void WaylandDisplayWindow::OnXDGToplevelConfigureEvent(int32_t width, int32_t height) { - Rect rect = GetWindowFrame(); - rect.width = width / m_ScaleFactor; - rect.height = height / m_ScaleFactor; - SetWindowFrame(rect); - windowHost->OnWindowGeometryChanged(); + Rect rect = GetWindowFrame(); + rect.width = width / m_ScaleFactor; + rect.height = height / m_ScaleFactor; + SetWindowFrame(rect); + windowHost->OnWindowGeometryChanged(); } void WaylandDisplayWindow::OnExportHandleEvent(std::string exportedHandle) { - m_windowID = exportedHandle; + m_windowID = exportedHandle; } void WaylandDisplayWindow::OnExitEvent() { - windowHost->OnWindowClose(); + windowHost->OnWindowClose(); } void WaylandDisplayWindow::DrawSurface(uint32_t serial) { - m_AppSurface.attach(m_AppSurfaceBuffer, 0, 0); - m_AppSurface.damage(0, 0, m_WindowSize.width, m_WindowSize.height); + m_AppSurface.attach(m_AppSurfaceBuffer, 0, 0); + m_AppSurface.damage(0, 0, m_WindowSize.width, m_WindowSize.height); - if (m_renderAPI == RenderAPI::Unspecified || m_renderAPI == RenderAPI::Bitmap) - { - m_FrameCallback = m_AppSurface.frame(); + if (m_renderAPI == RenderAPI::Unspecified || m_renderAPI == RenderAPI::Bitmap) + { + m_FrameCallback = m_AppSurface.frame(); - m_FrameCallback.on_done() = bind_mem_fn(&WaylandDisplayWindow::DrawSurface, this); - } - m_AppSurface.commit(); + m_FrameCallback.on_done() = bind_mem_fn(&WaylandDisplayWindow::DrawSurface, this); + } + m_AppSurface.commit(); } void WaylandDisplayWindow::CreateBuffers(int32_t width, int32_t height) { - if (width == 0 || height == 0) - return; + if (width == 0 || height == 0) + return; - if (shared_mem) - shared_mem.reset(); + if (shared_mem) + shared_mem.reset(); - int scaled_width = width * m_ScaleFactor; - int scaled_height = height * m_ScaleFactor; + int scaled_width = width * m_ScaleFactor; + int scaled_height = height * m_ScaleFactor; - shared_mem = std::make_shared(scaled_width * scaled_height * 4); - auto pool = backend->m_waylandSHM.create_pool(shared_mem->get_fd(), scaled_width * scaled_height * 4); + shared_mem = std::make_shared(scaled_width * scaled_height * 4); + auto pool = backend->m_waylandSHM.create_pool(shared_mem->get_fd(), scaled_width * scaled_height * 4); - m_AppSurfaceBuffer = pool.create_buffer(0, scaled_width, scaled_height, scaled_width * 4, wayland::shm_format::xrgb8888); + m_AppSurfaceBuffer = pool.create_buffer(0, scaled_width, scaled_height, scaled_width * 4, wayland::shm_format::xrgb8888); - m_WindowSize = Size(scaled_width, scaled_height); + m_WindowSize = Size(scaled_width, scaled_height); +} + +void WaylandDisplayWindow::CreateAppIconBuffers(const std::vector>& images) +{ + if (images.empty()) + return; + + appIconBuffers.clear(); + appIconSharedMems.clear(); + + for (auto& image: images) + { + const int image_size = image->GetWidth() * image->GetHeight() * 4; + const int stride = image->GetWidth() * 4; + + auto new_shared_mem = std::make_shared(image_size); + + auto pool = backend->m_waylandSHM.create_pool(new_shared_mem->get_fd(), image_size); + + auto new_buffer = pool.create_buffer(0, image->GetWidth(), image->GetHeight(), stride, wayland::shm_format::argb8888); + + // Fill the buffer with the icon data + std::memcpy(new_shared_mem->get_mem(), image->GetData(), image_size); + + appIconSharedMems.push_back(new_shared_mem); + appIconBuffers.push_back(new_buffer); + } } std::string WaylandDisplayWindow::GetWaylandWindowID() { - return m_windowID; + return m_windowID; } // This is to avoid needing all the Vulkan headers and the volk binding library just for this: @@ -398,11 +459,11 @@ typedef PFN_vkVoidFunction(VKAPI_PTR* PFN_vkGetInstanceProcAddr)(VkInstance inst typedef VkFlags VkWaylandSurfaceCreateFlagsKHR; typedef struct VkWaylandSurfaceCreateInfoKHR { - VkStructureType sType; - const void* pNext; - VkWaylandSurfaceCreateFlagsKHR flags; - struct wl_display* display; - struct wl_surface* surface; + VkStructureType sType; + const void* pNext; + VkWaylandSurfaceCreateFlagsKHR flags; + struct wl_display* display; + struct wl_surface* surface; } VkWaylandSurfaceCreateInfoKHR; typedef VkResult(VKAPI_PTR* PFN_vkCreateWaylandSurfaceKHR)(VkInstance instance, const VkWaylandSurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface); diff --git a/libraries/ZWidget/src/window/wayland/wayland_display_window.h b/libraries/ZWidget/src/window/wayland/wayland_display_window.h index 5553ece84..8479c303f 100644 --- a/libraries/ZWidget/src/window/wayland/wayland_display_window.h +++ b/libraries/ZWidget/src/window/wayland/wayland_display_window.h @@ -25,70 +25,71 @@ template std::function bind_mem_fn(R(T::* func)(Args...), T *t) { return [func, t] (Args... args) - { - return (t->*func)(args...); - }; + { + return (t->*func)(args...); + }; } class SharedMemHelper { public: - SharedMemHelper(size_t size) - : len(size) - { - std::stringstream ss; - std::random_device device; - std::default_random_engine engine(device()); - std::uniform_int_distribution distribution(0, std::numeric_limits::max()); - ss << distribution(engine); - name = ss.str(); + SharedMemHelper(size_t size) + : len(size) + { + std::stringstream ss; + std::random_device device; + std::default_random_engine engine(device()); + std::uniform_int_distribution distribution(0, std::numeric_limits::max()); + ss << distribution(engine); + name = ss.str(); - fd = memfd_create(name.c_str(), 0); - if(fd < 0) - throw std::runtime_error("shm_open failed."); + fd = memfd_create(name.c_str(), 0); + if(fd < 0) + throw std::runtime_error("shm_open failed."); - if(ftruncate(fd, size) < 0) - throw std::runtime_error("ftruncate failed."); + if(ftruncate(fd, size) < 0) + throw std::runtime_error("ftruncate failed."); - mem = mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - if(mem == MAP_FAILED) // NOLINT - throw std::runtime_error("mmap failed with len " + std::to_string(len) + "."); - } + mem = mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if(mem == MAP_FAILED) // NOLINT + throw std::runtime_error("mmap failed with len " + std::to_string(len) + "."); + } - ~SharedMemHelper() noexcept - { - if(fd) - { - munmap(mem, len); - close(fd); - shm_unlink(name.c_str()); - } - } + ~SharedMemHelper() noexcept + { + if(fd) + { + munmap(mem, len); + close(fd); + shm_unlink(name.c_str()); + } + } - int get_fd() const - { - return fd; - } + int get_fd() const + { + return fd; + } - void *get_mem() - { - return mem; - } + void *get_mem() + { + return mem; + } private: - std::string name; - int fd = 0; - size_t len = 0; - void *mem = nullptr; + std::string name; + int fd = 0; + size_t len = 0; + void *mem = nullptr; }; class WaylandDisplayWindow : public DisplayWindow { public: - WaylandDisplayWindow(WaylandDisplayBackend* backend, DisplayWindowHost* windowHost, bool popupWindow, WaylandDisplayWindow* owner, RenderAPI renderAPI); - ~WaylandDisplayWindow(); + WaylandDisplayWindow(WaylandDisplayBackend* backend, DisplayWindowHost* windowHost, bool popupWindow, WaylandDisplayWindow* owner, RenderAPI renderAPI); + ~WaylandDisplayWindow(); - void SetWindowTitle(const std::string& text) override; + void SetWindowTitle(const std::string& text) override; + void SetWindowIcon(const std::vector>& images) override; void SetWindowFrame(const Rect& box) override; void SetClientFrame(const Rect& box) override; void Show() override; @@ -135,64 +136,69 @@ public: bool IsWindowFullscreen() override; private: - // Event handlers as otherwise linking DisplayWindowHost On...() functions with Wayland events directly crashes the app - // Alternatively to avoid crashes one can capture by value ([=]) instead of reference ([&]) - void OnXDGToplevelConfigureEvent(int32_t width, int32_t height); - void OnExportHandleEvent(std::string exportedHandle); - void OnExitEvent(); + // Event handlers as otherwise linking DisplayWindowHost On...() functions with Wayland events directly crashes the app + // Alternatively to avoid crashes one can capture by value ([=]) instead of reference ([&]) + void OnXDGToplevelConfigureEvent(int32_t width, int32_t height); + void OnExportHandleEvent(std::string exportedHandle); + void OnExitEvent(); - void DrawSurface(uint32_t serial = 0); + void DrawSurface(uint32_t serial = 0); - void InitializeToplevel(); - void InitializePopup(); + void InitializeToplevel(); + void InitializePopup(); - WaylandDisplayBackend* backend = nullptr; - WaylandDisplayWindow* m_owner = nullptr; - DisplayWindowHost* windowHost = nullptr; - bool m_PopupWindow = false; + WaylandDisplayBackend* backend = nullptr; + WaylandDisplayWindow* m_owner = nullptr; + DisplayWindowHost* windowHost = nullptr; + bool m_PopupWindow = false; - bool m_NeedsUpdate = true; + bool m_NeedsUpdate = true; - Point m_WindowGlobalPos = Point(0, 0); - Size m_WindowSize = Size(0, 0); - double m_ScaleFactor = 1.0; + Point m_WindowGlobalPos = Point(0, 0); + Size m_WindowSize = Size(0, 0); + double m_ScaleFactor = 1.0; - Point m_SurfaceMousePos = Point(0, 0); + Point m_SurfaceMousePos = Point(0, 0); - WaylandNativeHandle m_NativeHandle; - RenderAPI m_renderAPI; + WaylandNativeHandle m_NativeHandle; + RenderAPI m_renderAPI; - wayland::data_device_t m_DataDevice; - wayland::data_source_t m_DataSource; + wayland::data_source_t m_DataSource; - wayland::zxdg_toplevel_decoration_v1_t m_XDGToplevelDecoration; + wayland::zxdg_toplevel_decoration_v1_t m_XDGToplevelDecoration; - wayland::fractional_scale_v1_t m_FractionalScale; + wayland::fractional_scale_v1_t m_FractionalScale; - wayland::surface_t m_AppSurface; - wayland::buffer_t m_AppSurfaceBuffer; + wayland::surface_t m_AppSurface; + wayland::buffer_t m_AppSurfaceBuffer; - wayland::xdg_surface_t m_XDGSurface; - wayland::xdg_toplevel_t m_XDGToplevel; - wayland::xdg_popup_t m_XDGPopup; + wayland::xdg_surface_t m_XDGSurface; + wayland::xdg_toplevel_t m_XDGToplevel; + wayland::xdg_popup_t m_XDGPopup; - wayland::zxdg_exported_v2_t m_XDGExported; + wayland::zxdg_exported_v2_t m_XDGExported; - wayland::zwp_locked_pointer_v1_t m_LockedPointer; - wayland::zwp_confined_pointer_v1_t m_ConfinedPointer; + wayland::zwp_locked_pointer_v1_t m_LockedPointer; + wayland::zwp_confined_pointer_v1_t m_ConfinedPointer; - wayland::callback_t m_FrameCallback; + wayland::xdg_toplevel_icon_v1_t m_XDGToplevelIcon; - std::string m_windowID; - std::string m_ClipboardContents; + wayland::callback_t m_FrameCallback; - std::shared_ptr shared_mem; + std::string m_windowID; - bool isFullscreen = false; + std::shared_ptr shared_mem; - // Helper functions - void CreateBuffers(int32_t width, int32_t height); - std::string GetWaylandWindowID(); + std::vector> appIconSharedMems; + std::vector appIconBuffers; - friend WaylandDisplayBackend; + bool isFullscreen = false; + + // Helper functions + void CreateBuffers(int32_t width, int32_t height); + + void CreateAppIconBuffers(const std::vector>& images); + std::string GetWaylandWindowID(); + + friend WaylandDisplayBackend; }; diff --git a/libraries/ZWidget/src/window/wayland/wl_cursor_shape.cpp b/libraries/ZWidget/src/window/wayland/wl_cursor_shape.cpp new file mode 100644 index 000000000..2884071c0 --- /dev/null +++ b/libraries/ZWidget/src/window/wayland/wl_cursor_shape.cpp @@ -0,0 +1,218 @@ +#include "wl_cursor_shape.hpp" + +using namespace wayland; +using namespace wayland::detail; + +const wl_interface* cursor_shape_manager_v1_interface_destroy_request[0] = { +}; + +const wl_interface* cursor_shape_manager_v1_interface_get_pointer_request[2] = { + &cursor_shape_device_v1_interface, + &pointer_interface, +}; + +const wl_interface* cursor_shape_manager_v1_interface_get_tablet_tool_v2_request[2] = { + &cursor_shape_device_v1_interface, + &zwp_tablet_tool_v2_interface, +}; + +const wl_message cursor_shape_manager_v1_interface_requests[3] = { + { + "destroy", + "", + cursor_shape_manager_v1_interface_destroy_request, + }, + { + "get_pointer", + "no", + cursor_shape_manager_v1_interface_get_pointer_request, + }, + { + "get_tablet_tool_v2", + "no", + cursor_shape_manager_v1_interface_get_tablet_tool_v2_request, + }, +}; + +const wl_message cursor_shape_manager_v1_interface_events[0] = { +}; + +const wl_interface wayland::detail::cursor_shape_manager_v1_interface = + { + "wp_cursor_shape_manager_v1", + 2, + 3, + cursor_shape_manager_v1_interface_requests, + 0, + cursor_shape_manager_v1_interface_events, + }; + +const wl_interface* cursor_shape_device_v1_interface_destroy_request[0] = { +}; + +const wl_interface* cursor_shape_device_v1_interface_set_shape_request[2] = { + nullptr, + nullptr, +}; + +const wl_message cursor_shape_device_v1_interface_requests[2] = { + { + "destroy", + "", + cursor_shape_device_v1_interface_destroy_request, + }, + { + "set_shape", + "uu", + cursor_shape_device_v1_interface_set_shape_request, + }, +}; + +const wl_message cursor_shape_device_v1_interface_events[0] = { +}; + +const wl_interface wayland::detail::cursor_shape_device_v1_interface = + { + "wp_cursor_shape_device_v1", + 2, + 2, + cursor_shape_device_v1_interface_requests, + 0, + cursor_shape_device_v1_interface_events, + }; + +cursor_shape_manager_v1_t::cursor_shape_manager_v1_t(const proxy_t &p) + : proxy_t(p) +{ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&cursor_shape_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return cursor_shape_manager_v1_t(p); }); +} + +cursor_shape_manager_v1_t::cursor_shape_manager_v1_t() +{ + set_interface(&cursor_shape_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return cursor_shape_manager_v1_t(p); }); +} + +cursor_shape_manager_v1_t::cursor_shape_manager_v1_t(wp_cursor_shape_manager_v1 *p, wrapper_type t) + : proxy_t(reinterpret_cast (p), t){ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&cursor_shape_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return cursor_shape_manager_v1_t(p); }); +} + +cursor_shape_manager_v1_t::cursor_shape_manager_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/) + : proxy_t(wrapped_proxy, construct_proxy_wrapper_tag()){ + set_interface(&cursor_shape_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return cursor_shape_manager_v1_t(p); }); +} + +cursor_shape_manager_v1_t cursor_shape_manager_v1_t::proxy_create_wrapper() +{ + return {*this, construct_proxy_wrapper_tag()}; +} + +const std::string cursor_shape_manager_v1_t::interface_name = "wp_cursor_shape_manager_v1"; + +cursor_shape_manager_v1_t::operator wp_cursor_shape_manager_v1*() const +{ + return reinterpret_cast (c_ptr()); +} + +cursor_shape_device_v1_t cursor_shape_manager_v1_t::get_pointer(pointer_t const& pointer) +{ + proxy_t p = marshal_constructor(1U, &cursor_shape_device_v1_interface, nullptr, pointer.proxy_has_object() ? reinterpret_cast(pointer.c_ptr()) : nullptr); + return cursor_shape_device_v1_t(p); +} + + +cursor_shape_device_v1_t cursor_shape_manager_v1_t::get_tablet_tool_v2(zwp_tablet_tool_v2_t const& tablet_tool) +{ + proxy_t p = marshal_constructor(2U, &cursor_shape_device_v1_interface, nullptr, tablet_tool.proxy_has_object() ? reinterpret_cast(tablet_tool.c_ptr()) : nullptr); + return cursor_shape_device_v1_t(p); +} + + +int cursor_shape_manager_v1_t::dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e) +{ + return 0; +} + +cursor_shape_device_v1_t::cursor_shape_device_v1_t(const proxy_t &p) + : proxy_t(p) +{ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&cursor_shape_device_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return cursor_shape_device_v1_t(p); }); +} + +cursor_shape_device_v1_t::cursor_shape_device_v1_t() +{ + set_interface(&cursor_shape_device_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return cursor_shape_device_v1_t(p); }); +} + +cursor_shape_device_v1_t::cursor_shape_device_v1_t(wp_cursor_shape_device_v1 *p, wrapper_type t) + : proxy_t(reinterpret_cast (p), t){ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&cursor_shape_device_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return cursor_shape_device_v1_t(p); }); +} + +cursor_shape_device_v1_t::cursor_shape_device_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/) + : proxy_t(wrapped_proxy, construct_proxy_wrapper_tag()){ + set_interface(&cursor_shape_device_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return cursor_shape_device_v1_t(p); }); +} + +cursor_shape_device_v1_t cursor_shape_device_v1_t::proxy_create_wrapper() +{ + return {*this, construct_proxy_wrapper_tag()}; +} + +const std::string cursor_shape_device_v1_t::interface_name = "wp_cursor_shape_device_v1"; + +cursor_shape_device_v1_t::operator wp_cursor_shape_device_v1*() const +{ + return reinterpret_cast (c_ptr()); +} + +void cursor_shape_device_v1_t::set_shape(uint32_t serial, cursor_shape_device_v1_shape const& shape) +{ + marshal(1U, serial, static_cast(shape)); +} + + +int cursor_shape_device_v1_t::dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e) +{ + return 0; +} + + + + diff --git a/libraries/ZWidget/src/window/wayland/wl_cursor_shape.hpp b/libraries/ZWidget/src/window/wayland/wl_cursor_shape.hpp new file mode 100644 index 000000000..7514b128e --- /dev/null +++ b/libraries/ZWidget/src/window/wayland/wl_cursor_shape.hpp @@ -0,0 +1,258 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +struct wp_cursor_shape_manager_v1; +struct wp_cursor_shape_device_v1; + +namespace wayland +{ +class cursor_shape_manager_v1_t; +class cursor_shape_device_v1_t; +enum class cursor_shape_device_v1_shape : uint32_t; +enum class cursor_shape_device_v1_error : uint32_t; + +namespace detail +{ + extern const wl_interface cursor_shape_manager_v1_interface; + extern const wl_interface cursor_shape_device_v1_interface; +} + +/** \brief cursor shape manager + + This global offers an alternative, optional way to set cursor images. This + new way uses enumerated cursors instead of a wl_surface like + wl_pointer.set_cursor does. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. + +*/ +class cursor_shape_manager_v1_t : public proxy_t +{ +private: + struct events_t : public detail::events_base_t + { + }; + + static int dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e); + + cursor_shape_manager_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/); + +public: + cursor_shape_manager_v1_t(); + explicit cursor_shape_manager_v1_t(const proxy_t &proxy); + cursor_shape_manager_v1_t(wp_cursor_shape_manager_v1 *p, wrapper_type t = wrapper_type::standard); + + cursor_shape_manager_v1_t proxy_create_wrapper(); + + static const std::string interface_name; + + operator wp_cursor_shape_manager_v1*() const; + + /** \brief manage the cursor shape of a pointer device + \param pointer + + Obtain a wp_cursor_shape_device_v1 for a wl_pointer object. + + When the pointer capability is removed from the wl_seat, the + wp_cursor_shape_device_v1 object becomes inert. + + */ + cursor_shape_device_v1_t get_pointer(pointer_t const& pointer); + + /** \brief Minimum protocol version required for the \ref get_pointer function + */ + static constexpr std::uint32_t get_pointer_since_version = 1; + + /** \brief manage the cursor shape of a tablet tool device + \param tablet_tool + + Obtain a wp_cursor_shape_device_v1 for a zwp_tablet_tool_v2 object. + + When the zwp_tablet_tool_v2 is removed, the wp_cursor_shape_device_v1 + object becomes inert. + + */ + cursor_shape_device_v1_t get_tablet_tool_v2(zwp_tablet_tool_v2_t const& tablet_tool); + + /** \brief Minimum protocol version required for the \ref get_tablet_tool_v2 function + */ + static constexpr std::uint32_t get_tablet_tool_v2_since_version = 1; + +}; + + +/** \brief cursor shape for a device + + This interface allows clients to set the cursor shape. + +*/ +class cursor_shape_device_v1_t : public proxy_t +{ +private: + struct events_t : public detail::events_base_t + { + }; + + static int dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e); + + cursor_shape_device_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/); + +public: + cursor_shape_device_v1_t(); + explicit cursor_shape_device_v1_t(const proxy_t &proxy); + cursor_shape_device_v1_t(wp_cursor_shape_device_v1 *p, wrapper_type t = wrapper_type::standard); + + cursor_shape_device_v1_t proxy_create_wrapper(); + + static const std::string interface_name; + + operator wp_cursor_shape_device_v1*() const; + + /** \brief set device cursor to the shape + \param serial serial number of the enter event + \param shape + + Sets the device cursor to the specified shape. The compositor will + change the cursor image based on the specified shape. + + The cursor actually changes only if the input device focus is one of + the requesting client's surfaces. If any, the previous cursor image + (surface or shape) is replaced. + + The "shape" argument must be a valid enum entry, otherwise the + invalid_shape protocol error is raised. + + This is similar to the wl_pointer.set_cursor and + zwp_tablet_tool_v2.set_cursor requests, but this request accepts a + shape instead of contents in the form of a surface. Clients can mix + set_cursor and set_shape requests. + + The serial parameter must match the latest wl_pointer.enter or + zwp_tablet_tool_v2.proximity_in serial number sent to the client. + Otherwise the request will be ignored. + + */ + void set_shape(uint32_t serial, cursor_shape_device_v1_shape const& shape); + + /** \brief Minimum protocol version required for the \ref set_shape function + */ + static constexpr std::uint32_t set_shape_since_version = 1; + +}; + +/** \brief cursor shapes + + This enum describes cursor shapes. + + The names are taken from the CSS W3C specification: + https://w3c.github.io/csswg-drafts/css-ui/#cursor + with a few additions. + + Note that there are some groups of cursor shapes that are related: + The first group is drag-and-drop cursors which are used to indicate + the selected action during dnd operations. The second group is resize + cursors which are used to indicate resizing and moving possibilities + on window borders. It is recommended that the shapes in these groups + should use visually compatible images and metaphors. + + */ +enum class cursor_shape_device_v1_shape : uint32_t + { + /** \brief default cursor */ + _default = 1, + /** \brief a context menu is available for the object under the cursor */ + context_menu = 2, + /** \brief help is available for the object under the cursor */ + help = 3, + /** \brief pointer that indicates a link or another interactive element */ + pointer = 4, + /** \brief progress indicator */ + progress = 5, + /** \brief program is busy, user should wait */ + wait = 6, + /** \brief a cell or set of cells may be selected */ + cell = 7, + /** \brief simple crosshair */ + crosshair = 8, + /** \brief text may be selected */ + text = 9, + /** \brief vertical text may be selected */ + vertical_text = 10, + /** \brief drag-and-drop: alias of/shortcut to something is to be created */ + alias = 11, + /** \brief drag-and-drop: something is to be copied */ + copy = 12, + /** \brief drag-and-drop: something is to be moved */ + move = 13, + /** \brief drag-and-drop: the dragged item cannot be dropped at the current cursor location */ + no_drop = 14, + /** \brief drag-and-drop: the requested action will not be carried out */ + not_allowed = 15, + /** \brief drag-and-drop: something can be grabbed */ + grab = 16, + /** \brief drag-and-drop: something is being grabbed */ + grabbing = 17, + /** \brief resizing: the east border is to be moved */ + e_resize = 18, + /** \brief resizing: the north border is to be moved */ + n_resize = 19, + /** \brief resizing: the north-east corner is to be moved */ + ne_resize = 20, + /** \brief resizing: the north-west corner is to be moved */ + nw_resize = 21, + /** \brief resizing: the south border is to be moved */ + s_resize = 22, + /** \brief resizing: the south-east corner is to be moved */ + se_resize = 23, + /** \brief resizing: the south-west corner is to be moved */ + sw_resize = 24, + /** \brief resizing: the west border is to be moved */ + w_resize = 25, + /** \brief resizing: the east and west borders are to be moved */ + ew_resize = 26, + /** \brief resizing: the north and south borders are to be moved */ + ns_resize = 27, + /** \brief resizing: the north-east and south-west corners are to be moved */ + nesw_resize = 28, + /** \brief resizing: the north-west and south-east corners are to be moved */ + nwse_resize = 29, + /** \brief resizing: that the item/column can be resized horizontally */ + col_resize = 30, + /** \brief resizing: that the item/row can be resized vertically */ + row_resize = 31, + /** \brief something can be scrolled in any direction */ + all_scroll = 32, + /** \brief something can be zoomed in */ + zoom_in = 33, + /** \brief something can be zoomed out */ + zoom_out = 34, + /** \brief drag-and-drop: the user will select which action will be carried out (non-css value) */ + dnd_ask = 35, + /** \brief resizing: something can be moved or resized in any direction (non-css value) */ + all_resize = 36 +}; + +/** \brief + + */ +enum class cursor_shape_device_v1_error : uint32_t + { + /** \brief the specified shape value is invalid */ + invalid_shape = 1 +}; + + + +} diff --git a/libraries/ZWidget/src/window/wayland/wl_xdg_toplevel_icon.cpp b/libraries/ZWidget/src/window/wayland/wl_xdg_toplevel_icon.cpp new file mode 100644 index 000000000..89fa5b3f7 --- /dev/null +++ b/libraries/ZWidget/src/window/wayland/wl_xdg_toplevel_icon.cpp @@ -0,0 +1,267 @@ +#include "wl_xdg_toplevel_icon.hpp" + +using namespace wayland; +using namespace wayland::detail; + +const wl_interface* xdg_toplevel_icon_manager_v1_interface_destroy_request[0] = { +}; + +const wl_interface* xdg_toplevel_icon_manager_v1_interface_create_icon_request[1] = { + &xdg_toplevel_icon_v1_interface, +}; + +const wl_interface* xdg_toplevel_icon_manager_v1_interface_set_icon_request[2] = { + &xdg_toplevel_interface, + &xdg_toplevel_icon_v1_interface, +}; + +const wl_interface* xdg_toplevel_icon_manager_v1_interface_icon_size_event[1] = { + nullptr, +}; + +const wl_interface* xdg_toplevel_icon_manager_v1_interface_done_event[0] = { +}; + +const wl_message xdg_toplevel_icon_manager_v1_interface_requests[3] = { + { + "destroy", + "", + xdg_toplevel_icon_manager_v1_interface_destroy_request, + }, + { + "create_icon", + "n", + xdg_toplevel_icon_manager_v1_interface_create_icon_request, + }, + { + "set_icon", + "o?o", + xdg_toplevel_icon_manager_v1_interface_set_icon_request, + }, +}; + +const wl_message xdg_toplevel_icon_manager_v1_interface_events[2] = { + { + "icon_size", + "i", + xdg_toplevel_icon_manager_v1_interface_icon_size_event, + }, + { + "done", + "", + xdg_toplevel_icon_manager_v1_interface_done_event, + }, +}; + +const wl_interface wayland::detail::xdg_toplevel_icon_manager_v1_interface = + { + "xdg_toplevel_icon_manager_v1", + 1, + 3, + xdg_toplevel_icon_manager_v1_interface_requests, + 2, + xdg_toplevel_icon_manager_v1_interface_events, + }; + +const wl_interface* xdg_toplevel_icon_v1_interface_destroy_request[0] = { +}; + +const wl_interface* xdg_toplevel_icon_v1_interface_set_name_request[1] = { + nullptr, +}; + +const wl_interface* xdg_toplevel_icon_v1_interface_add_buffer_request[2] = { + &buffer_interface, + nullptr, +}; + +const wl_message xdg_toplevel_icon_v1_interface_requests[3] = { + { + "destroy", + "", + xdg_toplevel_icon_v1_interface_destroy_request, + }, + { + "set_name", + "s", + xdg_toplevel_icon_v1_interface_set_name_request, + }, + { + "add_buffer", + "oi", + xdg_toplevel_icon_v1_interface_add_buffer_request, + }, +}; + +const wl_message xdg_toplevel_icon_v1_interface_events[0] = { +}; + +const wl_interface wayland::detail::xdg_toplevel_icon_v1_interface = + { + "xdg_toplevel_icon_v1", + 1, + 3, + xdg_toplevel_icon_v1_interface_requests, + 0, + xdg_toplevel_icon_v1_interface_events, + }; + +xdg_toplevel_icon_manager_v1_t::xdg_toplevel_icon_manager_v1_t(const proxy_t &p) + : proxy_t(p) +{ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&xdg_toplevel_icon_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return xdg_toplevel_icon_manager_v1_t(p); }); +} + +xdg_toplevel_icon_manager_v1_t::xdg_toplevel_icon_manager_v1_t() +{ + set_interface(&xdg_toplevel_icon_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return xdg_toplevel_icon_manager_v1_t(p); }); +} + +xdg_toplevel_icon_manager_v1_t::xdg_toplevel_icon_manager_v1_t(xdg_toplevel_icon_manager_v1 *p, wrapper_type t) + : proxy_t(reinterpret_cast (p), t){ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&xdg_toplevel_icon_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return xdg_toplevel_icon_manager_v1_t(p); }); +} + +xdg_toplevel_icon_manager_v1_t::xdg_toplevel_icon_manager_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/) + : proxy_t(wrapped_proxy, construct_proxy_wrapper_tag()){ + set_interface(&xdg_toplevel_icon_manager_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return xdg_toplevel_icon_manager_v1_t(p); }); +} + +xdg_toplevel_icon_manager_v1_t xdg_toplevel_icon_manager_v1_t::proxy_create_wrapper() +{ + return {*this, construct_proxy_wrapper_tag()}; +} + +const std::string xdg_toplevel_icon_manager_v1_t::interface_name = "xdg_toplevel_icon_manager_v1"; + +xdg_toplevel_icon_manager_v1_t::operator xdg_toplevel_icon_manager_v1*() const +{ + return reinterpret_cast (c_ptr()); +} + +xdg_toplevel_icon_v1_t xdg_toplevel_icon_manager_v1_t::create_icon() +{ + proxy_t p = marshal_constructor(1U, &xdg_toplevel_icon_v1_interface, nullptr); + return xdg_toplevel_icon_v1_t(p); +} + + +void xdg_toplevel_icon_manager_v1_t::set_icon(xdg_toplevel_t const& toplevel, xdg_toplevel_icon_v1_t const& icon) +{ + marshal(2U, toplevel.proxy_has_object() ? reinterpret_cast(toplevel.c_ptr()) : nullptr, icon.proxy_has_object() ? reinterpret_cast(icon.c_ptr()) : nullptr); +} + + +std::function &xdg_toplevel_icon_manager_v1_t::on_icon_size() +{ + return std::static_pointer_cast(get_events())->icon_size; +} + +std::function &xdg_toplevel_icon_manager_v1_t::on_done() +{ + return std::static_pointer_cast(get_events())->done; +} + +int xdg_toplevel_icon_manager_v1_t::dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e) +{ + std::shared_ptr events = std::static_pointer_cast(e); + switch(opcode) + { + case 0: + if(events->icon_size) events->icon_size(args[0].get()); + break; + case 1: + if(events->done) events->done(); + break; + } + return 0; +} + +xdg_toplevel_icon_v1_t::xdg_toplevel_icon_v1_t(const proxy_t &p) + : proxy_t(p) +{ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&xdg_toplevel_icon_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return xdg_toplevel_icon_v1_t(p); }); +} + +xdg_toplevel_icon_v1_t::xdg_toplevel_icon_v1_t() +{ + set_interface(&xdg_toplevel_icon_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return xdg_toplevel_icon_v1_t(p); }); +} + +xdg_toplevel_icon_v1_t::xdg_toplevel_icon_v1_t(xdg_toplevel_icon_v1 *p, wrapper_type t) + : proxy_t(reinterpret_cast (p), t){ + if(proxy_has_object() && get_wrapper_type() == wrapper_type::standard) + { + set_events(std::shared_ptr(new events_t), dispatcher); + set_destroy_opcode(0U); + } + set_interface(&xdg_toplevel_icon_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return xdg_toplevel_icon_v1_t(p); }); +} + +xdg_toplevel_icon_v1_t::xdg_toplevel_icon_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/) + : proxy_t(wrapped_proxy, construct_proxy_wrapper_tag()){ + set_interface(&xdg_toplevel_icon_v1_interface); + set_copy_constructor([] (const proxy_t &p) -> proxy_t + { return xdg_toplevel_icon_v1_t(p); }); +} + +xdg_toplevel_icon_v1_t xdg_toplevel_icon_v1_t::proxy_create_wrapper() +{ + return {*this, construct_proxy_wrapper_tag()}; +} + +const std::string xdg_toplevel_icon_v1_t::interface_name = "xdg_toplevel_icon_v1"; + +xdg_toplevel_icon_v1_t::operator xdg_toplevel_icon_v1*() const +{ + return reinterpret_cast (c_ptr()); +} + +void xdg_toplevel_icon_v1_t::set_name(std::string const& icon_name) +{ + marshal(1U, icon_name); +} + + +void xdg_toplevel_icon_v1_t::add_buffer(buffer_t const& buffer, int32_t scale) +{ + marshal(2U, buffer.proxy_has_object() ? reinterpret_cast(buffer.c_ptr()) : nullptr, scale); +} + + +int xdg_toplevel_icon_v1_t::dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e) +{ + return 0; +} + + + diff --git a/libraries/ZWidget/src/window/wayland/wl_xdg_toplevel_icon.hpp b/libraries/ZWidget/src/window/wayland/wl_xdg_toplevel_icon.hpp new file mode 100644 index 000000000..748d559a1 --- /dev/null +++ b/libraries/ZWidget/src/window/wayland/wl_xdg_toplevel_icon.hpp @@ -0,0 +1,244 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +struct xdg_toplevel_icon_manager_v1; +struct xdg_toplevel_icon_v1; + +namespace wayland +{ +class xdg_toplevel_icon_manager_v1_t; +class xdg_toplevel_icon_v1_t; +enum class xdg_toplevel_icon_v1_error : uint32_t; + +namespace detail +{ + extern const wl_interface xdg_toplevel_icon_manager_v1_interface; + extern const wl_interface xdg_toplevel_icon_v1_interface; +} + +/** \brief interface to manage toplevel icons + + This interface allows clients to create toplevel window icons and set + them on toplevel windows to be displayed to the user. + +*/ +class xdg_toplevel_icon_manager_v1_t : public proxy_t +{ +private: + struct events_t : public detail::events_base_t + { + std::function icon_size; + std::function done; + }; + + static int dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e); + + xdg_toplevel_icon_manager_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/); + +public: + xdg_toplevel_icon_manager_v1_t(); + explicit xdg_toplevel_icon_manager_v1_t(const proxy_t &proxy); + xdg_toplevel_icon_manager_v1_t(xdg_toplevel_icon_manager_v1 *p, wrapper_type t = wrapper_type::standard); + + xdg_toplevel_icon_manager_v1_t proxy_create_wrapper(); + + static const std::string interface_name; + + operator xdg_toplevel_icon_manager_v1*() const; + + /** \brief create a new icon instance + + Creates a new icon object. This icon can then be attached to a + xdg_toplevel via the 'set_icon' request. + + */ + xdg_toplevel_icon_v1_t create_icon(); + + /** \brief Minimum protocol version required for the \ref create_icon function + */ + static constexpr std::uint32_t create_icon_since_version = 1; + + /** \brief set an icon on a toplevel window + \param toplevel the toplevel to act on + \param icon + + This request assigns the icon 'icon' to 'toplevel', or clears the + toplevel icon if 'icon' was null. + This state is double-buffered and is applied on the next + wl_surface.commit of the toplevel. + + After making this call, the xdg_toplevel_icon_v1 provided as 'icon' + can be destroyed by the client without 'toplevel' losing its icon. + The xdg_toplevel_icon_v1 is immutable from this point, and any + future attempts to change it must raise the + 'xdg_toplevel_icon_v1.immutable' protocol error. + + The compositor must set the toplevel icon from either the pixel data + the icon provides, or by loading a stock icon using the icon name. + See the description of 'xdg_toplevel_icon_v1' for details. + + If 'icon' is set to null, the icon of the respective toplevel is reset + to its default icon (usually the icon of the application, derived from + its desktop-entry file, or a placeholder icon). + If this request is passed an icon with no pixel buffers or icon name + assigned, the icon must be reset just like if 'icon' was null. + + */ + void set_icon(xdg_toplevel_t const& toplevel, xdg_toplevel_icon_v1_t const& icon); + + /** \brief Minimum protocol version required for the \ref set_icon function + */ + static constexpr std::uint32_t set_icon_since_version = 1; + + /** \brief describes a supported & preferred icon size + \param size the edge size of the square icon in surface-local coordinates, e.g. 64 + + This event indicates an icon size the compositor prefers to be + available if the client has scalable icons and can render to any size. + + When the 'xdg_toplevel_icon_manager_v1' object is created, the + compositor may send one or more 'icon_size' events to describe the list + of preferred icon sizes. If the compositor has no size preference, it + may not send any 'icon_size' event, and it is up to the client to + decide a suitable icon size. + + A sequence of 'icon_size' events must be finished with a 'done' event. + If the compositor has no size preferences, it must still send the + 'done' event, without any preceding 'icon_size' events. + + */ + std::function &on_icon_size(); + + /** \brief all information has been sent + + This event is sent after all 'icon_size' events have been sent. + + */ + std::function &on_done(); + +}; + + +/** \brief a toplevel window icon + + This interface defines a toplevel icon. + An icon can have a name, and multiple buffers. + In order to be applied, the icon must have either a name, or at least + one buffer assigned. Applying an empty icon (with no buffer or name) to + a toplevel should reset its icon to the default icon. + + It is up to compositor policy whether to prefer using a buffer or loading + an icon via its name. See 'set_name' and 'add_buffer' for details. + +*/ +class xdg_toplevel_icon_v1_t : public proxy_t +{ +private: + struct events_t : public detail::events_base_t + { + }; + + static int dispatcher(uint32_t opcode, const std::vector& args, const std::shared_ptr& e); + + xdg_toplevel_icon_v1_t(proxy_t const &wrapped_proxy, construct_proxy_wrapper_tag /*unused*/); + +public: + xdg_toplevel_icon_v1_t(); + explicit xdg_toplevel_icon_v1_t(const proxy_t &proxy); + xdg_toplevel_icon_v1_t(xdg_toplevel_icon_v1 *p, wrapper_type t = wrapper_type::standard); + + xdg_toplevel_icon_v1_t proxy_create_wrapper(); + + static const std::string interface_name; + + operator xdg_toplevel_icon_v1*() const; + + /** \brief set an icon name + \param icon_name + + This request assigns an icon name to this icon. + Any previously set name is overridden. + + The compositor must resolve 'icon_name' according to the lookup rules + described in the XDG icon theme specification[1] using the + environment's current icon theme. + + If the compositor does not support icon names or cannot resolve + 'icon_name' according to the XDG icon theme specification it must + fall back to using pixel buffer data instead. + + If this request is made after the icon has been assigned to a toplevel + via 'set_icon', a 'immutable' error must be raised. + + [1]: https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html + + */ + void set_name(std::string const& icon_name); + + /** \brief Minimum protocol version required for the \ref set_name function + */ + static constexpr std::uint32_t set_name_since_version = 1; + + /** \brief add icon data from a pixel buffer + \param buffer + \param scale the scaling factor of the icon, e.g. 1 + + This request adds pixel data supplied as wl_buffer to the icon. + + The client should add pixel data for all icon sizes and scales that + it can provide, or which are explicitly requested by the compositor + via 'icon_size' events on xdg_toplevel_icon_manager_v1. + + The wl_buffer supplying pixel data as 'buffer' must be backed by wl_shm + and must be a square (width and height being equal). + If any of these buffer requirements are not fulfilled, a 'invalid_buffer' + error must be raised. + + If this icon instance already has a buffer of the same size and scale + from a previous 'add_buffer' request, data from the last request + overrides the preexisting pixel data. + + The wl_buffer must be kept alive for as long as the xdg_toplevel_icon + it is associated with is not destroyed, otherwise a 'no_buffer' error + is raised. The buffer contents must not be modified after it was + assigned to the icon. As a result, the region of the wl_shm_pool's + backing storage used for the wl_buffer must not be modified after this + request is sent. The wl_buffer.release event is unused. + + If this request is made after the icon has been assigned to a toplevel + via 'set_icon', a 'immutable' error must be raised. + + */ + void add_buffer(buffer_t const& buffer, int32_t scale); + + /** \brief Minimum protocol version required for the \ref add_buffer function + */ + static constexpr std::uint32_t add_buffer_since_version = 1; + +}; + +/** \brief + + */ +enum class xdg_toplevel_icon_v1_error : uint32_t + { + /** \brief the provided buffer does not satisfy requirements */ + invalid_buffer = 1, + /** \brief the icon has already been assigned to a toplevel and must not be changed */ + immutable = 2, + /** \brief the provided buffer has been destroyed before the toplevel icon */ + no_buffer = 3 +}; + + + +} diff --git a/libraries/ZWidget/src/window/win32/win32_display_window.cpp b/libraries/ZWidget/src/window/win32/win32_display_window.cpp index 30027e1cc..fabb5ec12 100644 --- a/libraries/ZWidget/src/window/win32/win32_display_window.cpp +++ b/libraries/ZWidget/src/window/win32/win32_display_window.cpp @@ -1,5 +1,6 @@ #include "win32_display_window.h" +#include #include #include #include @@ -30,11 +31,11 @@ // Code for delay loading DPI related functions, needed for continued Windows 7 compatibility. typedef BOOL (WINAPI *PFN_AdjustWindowRectExForDpi)( - LPRECT lpRect, - DWORD dwStyle, - BOOL bMenu, - DWORD dwExStyle, - UINT dpi + LPRECT lpRect, + DWORD dwStyle, + BOOL bMenu, + DWORD dwExStyle, + UINT dpi ); typedef UINT (WINAPI *PFN_GetDpiForWindow)(HWND hwnd); @@ -44,39 +45,36 @@ static PFN_GetDpiForWindow pGetDpiForWindow = nullptr; static void DPIDelayLoad() { - HMODULE hUser32 = GetModuleHandleW(L"user32.dll"); - if (hUser32) { - pAdjustWindowRectExForDpi = (PFN_AdjustWindowRectExForDpi) - GetProcAddress(hUser32, "AdjustWindowRectExForDpi"); - pGetDpiForWindow = (PFN_GetDpiForWindow) - GetProcAddress(hUser32, "GetDpiForWindow"); - } + HMODULE hUser32 = GetModuleHandleW(L"user32.dll"); + if (hUser32) + { + pAdjustWindowRectExForDpi = (PFN_AdjustWindowRectExForDpi)GetProcAddress(hUser32, "AdjustWindowRectExForDpi"); + pGetDpiForWindow = (PFN_GetDpiForWindow)GetProcAddress(hUser32, "GetDpiForWindow"); + } } -static BOOL DelayLoadAdjustWindowRectExForDpi( - LPRECT lpRect, - DWORD dwStyle, - BOOL bMenu, - DWORD dwExStyle, - HWND hwnd -) { +static BOOL DelayLoadAdjustWindowRectExForDpi(LPRECT lpRect, DWORD dwStyle, BOOL bMenu, DWORD dwExStyle, HWND hwnd) +{ DPIDelayLoad(); - if (pAdjustWindowRectExForDpi) { - return pAdjustWindowRectExForDpi(lpRect, dwStyle, bMenu, dwExStyle, - pGetDpiForWindow(hwnd)); - } else { - return AdjustWindowRectEx(lpRect, dwStyle, bMenu, dwExStyle); - } + if (pAdjustWindowRectExForDpi) + { + return pAdjustWindowRectExForDpi(lpRect, dwStyle, bMenu, dwExStyle, + pGetDpiForWindow(hwnd)); + } + else + { + return AdjustWindowRectEx(lpRect, dwStyle, bMenu, dwExStyle); + } } static double DelayLoadGetDpiScale(HWND hwnd) { DPIDelayLoad(); - if (pGetDpiForWindow) { - return pGetDpiForWindow(hwnd) / 96.0; - } else { - return 1.0; - } + if (pGetDpiForWindow) { + return pGetDpiForWindow(hwnd) / 96.0; + } else { + return 1.0; + } } Win32DisplayWindow::Win32DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, Win32DisplayWindow* owner, RenderAPI renderAPI) : WindowHost(windowHost), PopupWindow(popupWindow) @@ -120,6 +118,11 @@ Win32DisplayWindow::~Win32DisplayWindow() WindowHandle.hwnd = 0; } + if (SmallIcon) + DestroyCursor(SmallIcon); + if (LargeIcon) + DestroyCursor(LargeIcon); + Windows.erase(WindowsIterator); } @@ -128,6 +131,95 @@ void Win32DisplayWindow::SetWindowTitle(const std::string& text) SetWindowText(WindowHandle.hwnd, to_utf16(text).c_str()); } +static HICON CreateIconFromImageList(HDC hdc, const std::vector>& images, int desiredSize) +{ + if (images.empty()) + return 0; + + Image* image = images.front().get(); + for (const auto& i : images) + { + int curdist = std::abs(image->GetWidth() - desiredSize); + int dist = std::abs(i->GetWidth() - desiredSize); + if (dist < curdist) + image = i.get(); + } + + int width = image->GetWidth(); + int height = image->GetHeight(); + std::vector pixels(width * height * 4); + + if (image->GetFormat() == ImageFormat::R8G8B8A8) + { + int count = width * height; + const uint32_t* src = (const uint32_t*)image->GetData(); + uint32_t* dest = pixels.data(); + for (int i = 0; i < count; i++) + { + uint32_t r = src[i] & 0xff; + uint32_t g = (src[i] >> 8) & 0xff; + uint32_t b = (src[i] >> 16) & 0xff; + uint32_t a = (src[i] >> 24) & 0xff; + dest[i] = (a << 24) | (r << 16) | (g << 8) | b; + } + } + else if (image->GetFormat() == ImageFormat::B8G8R8A8) + { + memcpy(pixels.data(), image->GetData(), width * height * 4); + } + else + { + return 0; + } + + BITMAPV5HEADER bmp_header = {}; + bmp_header.bV5Size = sizeof(BITMAPV5HEADER); + bmp_header.bV5Width = width; + bmp_header.bV5Height = height; + bmp_header.bV5Planes = 1; + bmp_header.bV5BitCount = 32; + bmp_header.bV5Compression = BI_RGB; + + HBITMAP bitmap = CreateDIBitmap(hdc, (BITMAPINFOHEADER*)&bmp_header, CBM_INIT, pixels.data(), (BITMAPINFO*)&bmp_header, DIB_RGB_COLORS); + if (!bitmap) + return 0; + + ICONINFO iconinfo = {}; + iconinfo.fIcon = TRUE; + iconinfo.hbmColor = bitmap; + iconinfo.hbmMask = bitmap; + HICON icon = CreateIconIndirect(&iconinfo); + DeleteObject(bitmap); + return icon; +} + +void Win32DisplayWindow::SetWindowIcon(const std::vector>& images) +{ + double dpiScale = GetDpiScale(); + + if (SmallIcon) + { + DestroyCursor(SmallIcon); + SmallIcon = {}; + } + if (LargeIcon) + { + DestroyCursor(LargeIcon); + LargeIcon = {}; + } + + HDC hdc = GetDC(WindowHandle.hwnd); + if (hdc) + { + SmallIcon = CreateIconFromImageList(hdc, images, (int)std::round(16 * dpiScale)); + LargeIcon = CreateIconFromImageList(hdc, images, (int)std::round(32 * dpiScale)); + ReleaseDC(WindowHandle.hwnd, hdc); + } + + SendMessage(WindowHandle.hwnd, WM_SETICON, ICON_SMALL, (LPARAM)SmallIcon); + SendMessage(WindowHandle.hwnd, WM_SETICON, ICON_BIG, (LPARAM)LargeIcon); +} + void Win32DisplayWindow::SetBorderColor(uint32_t bgra8) { bgra8 = bgra8 & 0x00ffffff; diff --git a/libraries/ZWidget/src/window/win32/win32_display_window.h b/libraries/ZWidget/src/window/win32/win32_display_window.h index 7666274bd..431f9ed65 100644 --- a/libraries/ZWidget/src/window/win32/win32_display_window.h +++ b/libraries/ZWidget/src/window/win32/win32_display_window.h @@ -14,6 +14,7 @@ public: ~Win32DisplayWindow(); void SetWindowTitle(const std::string& text) override; + void SetWindowIcon(const std::vector>& images) override; void SetWindowFrame(const Rect& box) override; void SetClientFrame(const Rect& box) override; void Show() override; @@ -90,5 +91,8 @@ public: HDC PaintDC = 0; + HICON SmallIcon = {}; + HICON LargeIcon = {}; + StandardCursor CurrentCursor = StandardCursor::arrow; }; diff --git a/libraries/ZWidget/src/window/x11/x11_connection.cpp b/libraries/ZWidget/src/window/x11/x11_connection.cpp new file mode 100644 index 000000000..8c8b25a06 --- /dev/null +++ b/libraries/ZWidget/src/window/x11/x11_connection.cpp @@ -0,0 +1,261 @@ + +#include "x11_connection.h" +#include "x11_display_window.h" +#include +#include + +X11Connection* GetX11Connection() +{ + static X11Connection connection; + return &connection; +} + +X11Connection::X11Connection() +{ + // This is required by vulkan + XInitThreads(); + + display = XOpenDisplay(nullptr); + if (!display) + throw std::runtime_error("Could not open X11 display"); + + // Make auto-repeat keys detectable + Bool supports_detectable_autorepeat = {}; + XkbSetDetectableAutoRepeat(display, True, &supports_detectable_autorepeat); + + // Loads the XMODIFIERS environment variable to see what IME to use + XSetLocaleModifiers(""); + xim = XOpenIM(display, 0, 0, 0); + if (!xim) + { + // fallback to internal input method + XSetLocaleModifiers("@im=none"); + xim = XOpenIM(display, 0, 0, 0); + } + + // Look for XInput support + int event = 0, error = 0; + if (XQueryExtension(display, "XInputExtension", &XInputOpcode, &event, &error)) + { + // We need XInput 2.0 support + int major = 2, minor = 0; + if (XIQueryVersion(display, &major, &minor) != BadRequest) + { + // And we need a master pointer ID + int ndevices = 0; + XIDeviceInfo *devices = XIQueryDevice(display, XIAllDevices, &ndevices); + if (devices) + { + for (int i = 0; i < ndevices; i++) + { + if (devices[i].use == XIMasterPointer) + { + MasterPointerID = devices[i].deviceid; + XInput2Supported = true; + break; + } + } + XIFreeDeviceInfo(devices); + } + } + } + + // This XInput API is the gift that just keeps on giving. Instead of routing events to the + // focus window it sends it to the root window. Thanks! + if (XInput2Supported) + { + unsigned char mask[3] = { 0 }; + XISetMask(mask, XI_RawMotion); + XIEventMask eventmask; + eventmask.deviceid = MasterPointerID; + eventmask.mask_len = sizeof(mask); + eventmask.mask = mask; + Window root = XRootWindow(display, XDefaultScreen(display)); + XISelectEvents(display, root, &eventmask, 1); + } +} + +X11Connection::~X11Connection() +{ + for (auto& it : standardCursors) + XFreeCursor(display, it.second); + if (xim) + XCloseIM(xim); + XCloseDisplay(display); +} + +Atom X11Connection::GetAtom(const std::string& name) +{ + auto it = atoms.find(name); + if (it != atoms.end()) + return it->second; + + Atom atom = XInternAtom(display, name.c_str(), True); + atoms[name] = atom; + return atom; +} + +void X11Connection::ProcessEvents() +{ + CheckNeedsUpdate(); + while (XPending(display) > 0) + { + XEvent event = {}; + XNextEvent(display, &event); + DispatchEvent(&event); + } + CheckTimers(); +} + +void X11Connection::RunLoop() +{ + X11Connection* connection = GetX11Connection(); + connection->ExitRunLoop = false; + while (!connection->ExitRunLoop && !connection->windows.empty()) + { + ProcessEvents(); + WaitForEvents(GetTimerTimeout()); + } +} + +void X11Connection::ExitLoop() +{ + X11Connection* connection = GetX11Connection(); + connection->ExitRunLoop = true; +} + +bool X11Connection::WaitForEvents(int timeout) +{ + int fd = XConnectionNumber(display); + + struct timeval tv; + if (timeout > 0) + { + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) / 1000; + } + + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + int result = select(fd + 1, &rfds, nullptr, nullptr, timeout >= 0 ? &tv : nullptr); + return result > 0 && FD_ISSET(fd, &rfds); +} + +void X11Connection::CheckNeedsUpdate() +{ + for (auto& it : windows) + { + if (it.second->needsUpdate) + { + it.second->needsUpdate = false; + it.second->windowHost->OnWindowPaint(); + } + } +} + +void X11Connection::DispatchEvent(XEvent* event) +{ + if (XInput2Supported) + { + // XInput sends all raw input to the root window. We want it routed to the focused window + if (event->xcookie.type == GenericEvent && + event->xcookie.extension == XInputOpcode && + XGetEventData(display, &event->xcookie)) + { + for (auto& it : windows) + { + X11DisplayWindow* window = it.second; + if (window->OnXInputEvent(event)) + break; + } + XFreeEventData(display, &event->xcookie); + } + } + + auto it = windows.find(event->xany.window); + if (it != windows.end()) + { + X11DisplayWindow* window = it->second; + window->OnEvent(event); + } +} + +Size X11Connection::GetScreenSize() +{ + int screen = XDefaultScreen(display); + + int disp_width_px = XDisplayWidth(display, screen); + int disp_height_px = XDisplayHeight(display, screen); + int disp_width_mm = XDisplayWidthMM(display, screen); + double ppi = (disp_width_mm < 24) ? 96.0 : (25.4 * static_cast(disp_width_px) / static_cast(disp_width_mm)); + double dpiScale = ppi / 96.0; + + return Size(disp_width_px / dpiScale, disp_height_px / dpiScale); +} + +static int64_t GetTimePoint() +{ + using namespace std::chrono; + return (int64_t)(duration_cast(steady_clock::now().time_since_epoch()).count()); +} + +void* X11Connection::StartTimer(int timeoutMilliseconds, std::function onTimer) +{ + timeoutMilliseconds = std::max(timeoutMilliseconds, 1); + timers.push_back(std::make_shared(timeoutMilliseconds, onTimer, GetTimePoint() + timeoutMilliseconds)); + return timers.back().get(); +} + +void X11Connection::StopTimer(void* timerID) +{ + for (auto it = timers.begin(); it != timers.end(); ++it) + { + if (it->get() == timerID) + { + timers.erase(it); + return; + } + } +} + +void X11Connection::CheckTimers() +{ + int64_t now = GetTimePoint(); + + // The callback may stop timers. Iterators might invalidate. + while (true) + { + std::shared_ptr foundTimer; + for (auto& timer : timers) + { + if (timer->nextTime < now) + { + foundTimer = timer; + break; + } + } + + if (!foundTimer) + break; + + // Not very precise, but these aren't high precision timers + foundTimer->nextTime = now + foundTimer->timeoutMilliseconds; + foundTimer->onTimer(); + } +} + +int X11Connection::GetTimerTimeout() +{ + if (timers.empty()) + return 0; + + int64_t nextTime = timers.front()->nextTime; + for (auto& timer : timers) + { + nextTime = std::min(nextTime, timer->nextTime); + } + + int64_t now = GetTimePoint(); + return (int)std::max(nextTime - now, (int64_t)1); +} diff --git a/libraries/ZWidget/src/window/x11/x11_connection.h b/libraries/ZWidget/src/window/x11/x11_connection.h new file mode 100644 index 000000000..8c0a46eda --- /dev/null +++ b/libraries/ZWidget/src/window/x11/x11_connection.h @@ -0,0 +1,65 @@ +#pragma once + +#include "window/window.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class X11DisplayWindow; + +class X11Timer +{ +public: + X11Timer(int timeoutMilliseconds, std::function onTimer, int64_t nextTime) : timeoutMilliseconds(timeoutMilliseconds), onTimer(onTimer), nextTime(nextTime) {} + + int timeoutMilliseconds = 0; + std::function onTimer; + int64_t nextTime = 0; +}; + +class X11Connection +{ +public: + X11Connection(); + ~X11Connection(); + + void ProcessEvents(); + void RunLoop(); + void ExitLoop(); + bool WaitForEvents(int timeout); + + void* StartTimer(int timeoutMilliseconds, std::function onTimer); + void StopTimer(void* timerID); + + Size GetScreenSize(); + + Atom GetAtom(const std::string& name); + + Display* display = nullptr; + std::map atoms; + std::map windows; + std::map standardCursors; + bool ExitRunLoop = false; + + XIM xim = nullptr; + bool XInput2Supported = false; + int XInputOpcode = 0; + int MasterPointerID = 0; + +private: + void DispatchEvent(XEvent* event); + void CheckNeedsUpdate(); + void CheckTimers(); + int GetTimerTimeout(); + + std::vector> timers; +}; + +X11Connection* GetX11Connection(); diff --git a/libraries/ZWidget/src/window/x11/x11_display_backend.cpp b/libraries/ZWidget/src/window/x11/x11_display_backend.cpp index b97760b3a..4ca267d90 100644 --- a/libraries/ZWidget/src/window/x11/x11_display_backend.cpp +++ b/libraries/ZWidget/src/window/x11/x11_display_backend.cpp @@ -1,6 +1,8 @@ #include "x11_display_backend.h" #include "x11_display_window.h" +#include "x11_connection.h" +#include #ifdef USE_DBUS #include "window/dbus/dbus_open_file_dialog.h" @@ -15,32 +17,32 @@ std::unique_ptr X11DisplayBackend::Create(DisplayWindowHost* wind void X11DisplayBackend::ProcessEvents() { - X11DisplayWindow::ProcessEvents(); + GetX11Connection()->ProcessEvents(); } void X11DisplayBackend::RunLoop() { - X11DisplayWindow::RunLoop(); + GetX11Connection()->RunLoop(); } void X11DisplayBackend::ExitLoop() { - X11DisplayWindow::ExitLoop(); + GetX11Connection()->ExitLoop(); } Size X11DisplayBackend::GetScreenSize() { - return X11DisplayWindow::GetScreenSize(); + return GetX11Connection()->GetScreenSize(); } void* X11DisplayBackend::StartTimer(int timeoutMilliseconds, std::function onTimer) { - return X11DisplayWindow::StartTimer(timeoutMilliseconds, std::move(onTimer)); + return GetX11Connection()->StartTimer(timeoutMilliseconds, onTimer); } void X11DisplayBackend::StopTimer(void* timerID) { - X11DisplayWindow::StopTimer(timerID); + GetX11Connection()->StopTimer(timerID); } #ifdef USE_DBUS diff --git a/libraries/ZWidget/src/window/x11/x11_display_window.cpp b/libraries/ZWidget/src/window/x11/x11_display_window.cpp index 756f34a2c..16235b30d 100644 --- a/libraries/ZWidget/src/window/x11/x11_display_window.cpp +++ b/libraries/ZWidget/src/window/x11/x11_display_window.cpp @@ -1,5 +1,7 @@ #include "x11_display_window.h" +#include "x11_connection.h" +#include #include #include #include @@ -10,113 +12,6 @@ #include #include -class X11Connection -{ -public: - X11Connection() - { - // This is required by vulkan - XInitThreads(); - - display = XOpenDisplay(nullptr); - if (!display) - throw std::runtime_error("Could not open X11 display"); - - // Make auto-repeat keys detectable - Bool supports_detectable_autorepeat = {}; - XkbSetDetectableAutoRepeat(display, True, &supports_detectable_autorepeat); - - // Loads the XMODIFIERS environment variable to see what IME to use - XSetLocaleModifiers(""); - xim = XOpenIM(display, 0, 0, 0); - if (!xim) - { - // fallback to internal input method - XSetLocaleModifiers("@im=none"); - xim = XOpenIM(display, 0, 0, 0); - } - - // Look for XInput support - int event = 0, error = 0; - if (XQueryExtension(display, "XInputExtension", &XInputOpcode, &event, &error)) - { - // We need XInput 2.0 support - int major = 2, minor = 0; - if (XIQueryVersion(display, &major, &minor) != BadRequest) - { - // And we need a master pointer ID - int ndevices = 0; - XIDeviceInfo *devices = XIQueryDevice(display, XIAllDevices, &ndevices); - if (devices) - { - for (int i = 0; i < ndevices; i++) - { - if (devices[i].use == XIMasterPointer) - { - MasterPointerID = devices[i].deviceid; - XInput2Supported = true; - break; - } - } - XIFreeDeviceInfo(devices); - } - } - } - - // This XInput API is the gift that just keeps on giving. Instead of routing events to the - // focus window it sends it to the root window. Thanks! - if (XInput2Supported) - { - unsigned char mask[3] = { 0 }; - XISetMask(mask, XI_RawMotion); - XIEventMask eventmask; - eventmask.deviceid = MasterPointerID; - eventmask.mask_len = sizeof(mask); - eventmask.mask = mask; - Window root = XRootWindow(display, XDefaultScreen(display)); - XISelectEvents(display, root, &eventmask, 1); - } - } - - ~X11Connection() - { - for (auto& it : standardCursors) - XFreeCursor(display, it.second); - if (xim) - XCloseIM(xim); - XCloseDisplay(display); - } - - Display* display = nullptr; - std::map atoms; - std::map windows; - std::map standardCursors; - bool ExitRunLoop = false; - - XIM xim = nullptr; - bool XInput2Supported = false; - int XInputOpcode = 0; - int MasterPointerID = 0; -}; - -static X11Connection* GetX11Connection() -{ - static X11Connection connection; - return &connection; -} - -static Atom GetAtom(const std::string& name) -{ - auto connection = GetX11Connection(); - auto it = connection->atoms.find(name); - if (it != connection->atoms.end()) - return it->second; - - Atom atom = XInternAtom(connection->display, name.c_str(), True); - connection->atoms[name] = atom; - return atom; -} - X11DisplayWindow::X11DisplayWindow(DisplayWindowHost* windowHost, bool popupWindow, X11DisplayWindow* owner, RenderAPI renderAPI) : windowHost(windowHost), owner(owner) { auto connection = GetX11Connection(); @@ -175,51 +70,51 @@ X11DisplayWindow::X11DisplayWindow(DisplayWindowHost* windowHost, bool popupWind } // Tell window manager which process this window came from - if (GetAtom("_NET_WM_PID") != None) + if (connection->GetAtom("_NET_WM_PID") != None) { int32_t pid = getpid(); if (pid != 0) { - XChangeProperty(display, window, GetAtom("_NET_WM_PID"), XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1); + XChangeProperty(display, window, connection->GetAtom("_NET_WM_PID"), XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1); } } // Tell window manager which machine this window came from - if (GetAtom("WM_CLIENT_MACHINE") != None) + if (connection->GetAtom("WM_CLIENT_MACHINE") != None) { std::vector hostname(256); if (gethostname(hostname.data(), hostname.size()) >= 0) { hostname.push_back(0); - XChangeProperty(display, window, GetAtom("WM_CLIENT_MACHINE"), XA_STRING, 8, PropModeReplace, (unsigned char *)hostname.data(), strlen(hostname.data())); + XChangeProperty(display, window, connection->GetAtom("WM_CLIENT_MACHINE"), XA_STRING, 8, PropModeReplace, (unsigned char *)hostname.data(), strlen(hostname.data())); } } // Tell window manager we want to listen to close events - if (GetAtom("WM_DELETE_WINDOW") != None) + if (connection->GetAtom("WM_DELETE_WINDOW") != None) { - Atom protocol = GetAtom("WM_DELETE_WINDOW"); + Atom protocol = connection->GetAtom("WM_DELETE_WINDOW"); XSetWMProtocols(display, window, &protocol, 1); } // Tell window manager what type of window we are - if (GetAtom("_NET_WM_WINDOW_TYPE") != None) + if (connection->GetAtom("_NET_WM_WINDOW_TYPE") != None) { Atom type = None; if (popupWindow) { - type = GetAtom("_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"); + type = connection->GetAtom("_NET_WM_WINDOW_TYPE_DROPDOWN_MENU"); if (type == None) - type = GetAtom("_NET_WM_WINDOW_TYPE_POPUP_MENU"); + type = connection->GetAtom("_NET_WM_WINDOW_TYPE_POPUP_MENU"); if (type == None) - type = GetAtom("_NET_WM_WINDOW_TYPE_COMBO"); + type = connection->GetAtom("_NET_WM_WINDOW_TYPE_COMBO"); } if (type == None) - type = GetAtom("_NET_WM_WINDOW_TYPE_NORMAL"); + type = connection->GetAtom("_NET_WM_WINDOW_TYPE_NORMAL"); if (type != None) { - XChangeProperty(display, window, GetAtom("_NET_WM_WINDOW_TYPE"), XA_ATOM, 32, PropModeReplace, (unsigned char *)&type, 1); + XChangeProperty(display, window, connection->GetAtom("_NET_WM_WINDOW_TYPE"), XA_ATOM, 32, PropModeReplace, (unsigned char *)&type, 1); } } @@ -254,6 +149,87 @@ void X11DisplayWindow::SetWindowTitle(const std::string& text) XSetStandardProperties(display, window, text.c_str(), text.c_str(), None, nullptr, 0, nullptr); } +void X11DisplayWindow::SetWindowIcon(const std::vector>& images) +{ + if (images.empty()) + return; + + double dpiscale = GetDpiScale(); + int desiredSize = (int)std::round(32 * dpiscale); + + Image* image = images.front().get(); + for (const auto& i : images) + { + int curdist = std::abs(image->GetWidth() - desiredSize); + int dist = std::abs(i->GetWidth() - desiredSize); + if (dist < curdist) + image = i.get(); + } + + int width = image->GetWidth(); + int height = image->GetHeight(); + const uint32_t* s = (const uint32_t*)image->GetData(); + + unsigned int size = (width * height) + 2; // header is 2 ints + unsigned long* data = (unsigned long*)malloc(size * sizeof(unsigned long)); + + // set header + data[0] = width; + data[1] = height; + + // on 64bit systems, the destination buffer is 64 bit per pixel + // thus, we have to copy each pixel individually (no memcpy) + + // icon data is expected as ARGB + + if (image->GetFormat() == ImageFormat::R8G8B8A8) + { + for (int y = 0; y < height; ++y) + { + const uint32_t* src = s + y * width; + unsigned long* dst = &data[2 + (y * width)]; + for (int x = 0; x < width; ++x) + { + uint32_t r = src[x] & 0xff; + uint32_t g = (src[x] >> 8) & 0xff; + uint32_t b = (src[x] >> 16) & 0xff; + uint32_t a = (src[x] >> 24) & 0xff; + dst[x] = (a << 24) | (r << 16) | (g << 8) | b; + } + } + } + else if (image->GetFormat() == ImageFormat::B8G8R8A8) + { + for (int y = 0; y < height; ++y) + { + const uint32_t* src = s + y * width; + unsigned long* dst = &data[2 + (y * width)]; + for (int x = 0; x < width; ++x) + { + dst[x] = src[x]; + } + } + } + else + { + free(data); + return; + } + + // set icon geometry + unsigned long* geom = (unsigned long*)malloc(4 * sizeof(unsigned long)); + geom[0] = geom[1] = 0; // x, y + geom[2] = width; + geom[3] = height; + + Atom propertyGeom = XInternAtom(display, "_NET_WM_ICON_GEOMETRY", 0); + XChangeProperty(display, window, propertyGeom, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)geom, 4); + + // set icon data + Atom property = XInternAtom(display, "_NET_WM_ICON", 0); + XChangeProperty(display, window, property, XA_CARDINAL, 32, PropModeReplace, (unsigned char*)data, size); +} + void X11DisplayWindow::SetWindowFrame(const Rect& box) { // To do: this requires cooperation with the window manager @@ -292,10 +268,11 @@ void X11DisplayWindow::ShowFullscreen() { Show(); - if (GetAtom("_NET_WM_STATE") != None && GetAtom("_NET_WM_STATE_FULLSCREEN") != None) + auto connection = GetX11Connection(); + if (connection->GetAtom("_NET_WM_STATE") != None && connection->GetAtom("_NET_WM_STATE_FULLSCREEN") != None) { - Atom state = GetAtom("_NET_WM_STATE_FULLSCREEN"); - XChangeProperty(display, window, GetAtom("_NET_WM_STATE"), XA_ATOM, 32, PropModeReplace, (unsigned char *)&state, 1); + Atom state = connection->GetAtom("_NET_WM_STATE_FULLSCREEN"); + XChangeProperty(display, window, connection->GetAtom("_NET_WM_STATE"), XA_ATOM, 32, PropModeReplace, (unsigned char *)&state, 1); isFullscreen = true; } } @@ -582,7 +559,8 @@ std::vector X11DisplayWindow::GetWindowProperty(Atom property, Atom &ac std::string X11DisplayWindow::GetClipboardText() { - Atom clipboard = GetAtom("CLIPBOARD"); + auto connection = GetX11Connection(); + Atom clipboard = connection->GetAtom("CLIPBOARD"); if (clipboard == None) return {}; @@ -595,7 +573,7 @@ std::string X11DisplayWindow::GetClipboardText() { if (XCheckTypedWindowEvent(display, window, SelectionNotify, &event)) break; - if (!WaitForEvents(500)) + if (!connection->WaitForEvents(500)) return {}; } @@ -614,7 +592,8 @@ void X11DisplayWindow::SetClipboardText(const std::string& text) { clipboardText = text; - Atom clipboard = GetAtom("CLIPBOARD"); + auto connection = GetX11Connection(); + Atom clipboard = connection->GetAtom("CLIPBOARD"); if (clipboard == None) return; @@ -653,96 +632,6 @@ void* X11DisplayWindow::GetNativeHandle() return reinterpret_cast(window); } -bool X11DisplayWindow::WaitForEvents(int timeout) -{ - Display* display = GetX11Connection()->display; - int fd = XConnectionNumber(display); - - struct timeval tv; - if (timeout > 0) - { - tv.tv_sec = timeout / 1000; - tv.tv_usec = (timeout % 1000) / 1000; - } - - fd_set rfds; - FD_ZERO(&rfds); - FD_SET(fd, &rfds); - int result = select(fd + 1, &rfds, nullptr, nullptr, timeout >= 0 ? &tv : nullptr); - return result > 0 && FD_ISSET(fd, &rfds); -} - -void X11DisplayWindow::CheckNeedsUpdate() -{ - for (auto& it : GetX11Connection()->windows) - { - if (it.second->needsUpdate) - { - it.second->needsUpdate = false; - it.second->windowHost->OnWindowPaint(); - } - } -} - -void X11DisplayWindow::ProcessEvents() -{ - CheckNeedsUpdate(); - Display* display = GetX11Connection()->display; - while (XPending(display) > 0) - { - XEvent event = {}; - XNextEvent(display, &event); - DispatchEvent(&event); - } -} - -void X11DisplayWindow::RunLoop() -{ - X11Connection* connection = GetX11Connection(); - connection->ExitRunLoop = false; - while (!connection->ExitRunLoop && !connection->windows.empty()) - { - CheckNeedsUpdate(); - XEvent event = {}; - XNextEvent(connection->display, &event); - DispatchEvent(&event); - } -} - -void X11DisplayWindow::ExitLoop() -{ - X11Connection* connection = GetX11Connection(); - connection->ExitRunLoop = true; -} - -void X11DisplayWindow::DispatchEvent(XEvent* event) -{ - X11Connection* connection = GetX11Connection(); - - if (connection->XInput2Supported) - { - // XInput sends all raw input to the root window. We want it routed to the focused window - if (event->xcookie.type == GenericEvent && - event->xcookie.extension == connection->XInputOpcode && - XGetEventData(connection->display, &event->xcookie)) - { - for (auto& it : connection->windows) - { - X11DisplayWindow* window = it.second; - window->OnXInputEvent(event); - } - XFreeEventData(connection->display, &event->xcookie); - } - } - - auto it = connection->windows.find(event->xany.window); - if (it != connection->windows.end()) - { - X11DisplayWindow* window = it->second; - window->OnEvent(event); - } -} - void X11DisplayWindow::OnEvent(XEvent* event) { if (event->type == ConfigureNotify) @@ -786,11 +675,12 @@ void X11DisplayWindow::OnConfigureNotify(XEvent* event) void X11DisplayWindow::OnClientMessage(XEvent* event) { - Atom protocolsAtom = GetAtom("WM_PROTOCOLS"); + auto connection = GetX11Connection(); + Atom protocolsAtom = connection->GetAtom("WM_PROTOCOLS"); if (protocolsAtom != None && event->xclient.message_type == protocolsAtom) { - Atom deleteAtom = GetAtom("WM_DELETE_WINDOW"); - Atom pingAtom = GetAtom("_NET_WM_PING"); + Atom deleteAtom = connection->GetAtom("WM_DELETE_WINDOW"); + Atom pingAtom = connection->GetAtom("_NET_WM_PING"); Atom protocol = event->xclient.data.l[0]; if (deleteAtom != None && protocol == deleteAtom) @@ -1087,14 +977,14 @@ void X11DisplayWindow::OnMotionNotify(XEvent* event) } } -void X11DisplayWindow::OnXInputEvent(XEvent* event) +bool X11DisplayWindow::OnXInputEvent(XEvent* event) { // This API is so horrible it makes Win32 look attractive! if (event->xcookie.evtype == XI_ButtonPress || event->xcookie.evtype == XI_ButtonRelease) { auto deviceEvent = (XIDeviceEvent*)event->xcookie.data; if (deviceEvent->event != window) - return; + return false; InputKey key = {}; switch (deviceEvent->detail) @@ -1106,7 +996,7 @@ void X11DisplayWindow::OnXInputEvent(XEvent* event) case 5: key = InputKey::MouseWheelDown; break; // case 6: key = InputKey::XButton1; break; // case 7: key = InputKey::XButton2; break; - default: return; + default: return false; } double dpiScale = GetDpiScale(); @@ -1132,12 +1022,14 @@ void X11DisplayWindow::OnXInputEvent(XEvent* event) keyState[key] = false; windowHost->OnWindowMouseUp(mousePos, key); } + + return true; } else if (event->xcookie.evtype == XI_Motion) { auto deviceEvent = (XIDeviceEvent*)event->xcookie.data; if (deviceEvent->event != window) - return; + return false; if (isCursorEnabled) { @@ -1160,6 +1052,8 @@ void X11DisplayWindow::OnXInputEvent(XEvent* event) XWarpPointer(display, window, window, 0, 0, ClientSizeX, ClientSizeY, centerX, centerY); } } + + return true; } else if (!isCursorEnabled && RawInput.Focused && event->xcookie.evtype == XI_RawMotion) { @@ -1185,7 +1079,10 @@ void X11DisplayWindow::OnXInputEvent(XEvent* event) if (dx != 0 || dy != 0) windowHost->OnWindowRawMouseMove(dx, dy); } + + return true; } + return false; } void X11DisplayWindow::OnLeaveNotify(XEvent* event) @@ -1209,8 +1106,9 @@ void X11DisplayWindow::OnSelectionRequest(XEvent* event) if (requestor == window) return; - Atom targetsAtom = GetAtom("TARGETS"); - Atom multipleAtom = GetAtom("MULTIPLE"); + X11Connection* connection = GetX11Connection(); + Atom targetsAtom = connection->GetAtom("TARGETS"); + Atom multipleAtom = connection->GetAtom("MULTIPLE"); struct Request { Window target; Atom property; }; std::vector requests; @@ -1267,31 +1165,6 @@ void X11DisplayWindow::OnSelectionRequest(XEvent* event) } } -Size X11DisplayWindow::GetScreenSize() -{ - X11Connection* connection = GetX11Connection(); - Display* display = connection->display; - int screen = XDefaultScreen(display); - - int disp_width_px = XDisplayWidth(display, screen); - int disp_height_px = XDisplayHeight(display, screen); - int disp_width_mm = XDisplayWidthMM(display, screen); - double ppi = (disp_width_mm < 24) ? 96.0 : (25.4 * static_cast(disp_width_px) / static_cast(disp_width_mm)); - double dpiScale = ppi / 96.0; - - return Size(disp_width_px / dpiScale, disp_height_px / dpiScale); -} - -void* X11DisplayWindow::StartTimer(int timeoutMilliseconds, std::function onTimer) -{ - throw std::logic_error("unimplemented: X11DisplayWindow::StartTimer"); -} - -void X11DisplayWindow::StopTimer(void* timerID) -{ - throw std::logic_error("unimplemented: X11DisplayWindow::StopTimer"); -} - // This is to avoid needing all the Vulkan headers and the volk binding library just for this: #ifndef VK_VERSION_1_0 diff --git a/libraries/ZWidget/src/window/x11/x11_display_window.h b/libraries/ZWidget/src/window/x11/x11_display_window.h index 174c95238..188fc2b18 100644 --- a/libraries/ZWidget/src/window/x11/x11_display_window.h +++ b/libraries/ZWidget/src/window/x11/x11_display_window.h @@ -17,6 +17,7 @@ public: ~X11DisplayWindow(); void SetWindowTitle(const std::string& text) override; + void SetWindowIcon(const std::vector>& images) override; void SetWindowFrame(const Rect& box) override; void SetClientFrame(const Rect& box) override; void Show() override; @@ -60,13 +61,6 @@ public: std::vector GetVulkanInstanceExtensions() override; VkSurfaceKHR CreateVulkanSurface(VkInstance instance) override; - static void ProcessEvents(); - static void RunLoop(); - static void ExitLoop(); - static Size GetScreenSize(); - static void* StartTimer(int timeoutMilliseconds, std::function onTimer); - static void StopTimer(void* timerID); - private: void UpdateCursor(); @@ -86,7 +80,7 @@ private: void OnSelectionClear(XEvent* event); void OnSelectionNotify(XEvent* event); void OnSelectionRequest(XEvent* event); - void OnXInputEvent(XEvent* event); + bool OnXInputEvent(XEvent* event); void CreateBackbuffer(int width, int height); void DestroyBackbuffer(); @@ -94,11 +88,6 @@ private: InputKey GetInputKey(XEvent* event); Point GetMousePos(XEvent* event); - static bool WaitForEvents(int timeout); - static void DispatchEvent(XEvent* event); - - static void CheckNeedsUpdate(); - std::vector GetWindowProperty(Atom property, Atom &actual_type, int &actual_format, unsigned long &item_count); DisplayWindowHost* windowHost = nullptr; @@ -145,5 +134,6 @@ private: bool needsUpdate = false; + friend class X11Connection; friend class X11DisplayBackend; };