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 000000000..e79210f1c Binary files /dev/null and b/libraries/ZWidget/example/surreal-engine-icon-128.png differ diff --git a/libraries/ZWidget/example/surreal-engine-icon-16.png b/libraries/ZWidget/example/surreal-engine-icon-16.png new file mode 100644 index 000000000..18915d886 Binary files /dev/null and b/libraries/ZWidget/example/surreal-engine-icon-16.png differ 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 000000000..e7d197b54 Binary files /dev/null and b/libraries/ZWidget/example/surreal-engine-icon-24.png differ 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 000000000..813ecc534 Binary files /dev/null and b/libraries/ZWidget/example/surreal-engine-icon-256.png differ 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 000000000..7f7518482 Binary files /dev/null and b/libraries/ZWidget/example/surreal-engine-icon-32.png differ diff --git a/libraries/ZWidget/example/surreal-engine-icon-48.png b/libraries/ZWidget/example/surreal-engine-icon-48.png new file mode 100644 index 000000000..ca3a90828 Binary files /dev/null and b/libraries/ZWidget/example/surreal-engine-icon-48.png differ 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 000000000..790cb9892 Binary files /dev/null and b/libraries/ZWidget/example/surreal-engine-icon-64.png differ 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; };