diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 44cf5e33a..af4924977 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -276,6 +276,13 @@ jobs: enable: INDICATOR,GNOME45,GNOME_CLASSIC_PANEL,GSTREAMER,PULSE,TESTS disable: COVERAGE,TRACING + - image: ubuntu-plucky + os: ubuntu-24.04 + compiler: gcc + ui: Qt + enable: INDICATOR,GNOME45,GNOME_CLASSIC_PANEL,GSTREAMER,PULSE,TESTS + disable: COVERAGE,TRACING + # - image: mingw-fedora-rawhide # os: ubuntu-24.04 # compiler: clang diff --git a/CMakeLists.txt b/CMakeLists.txt index 821ca1486..8107c53df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -200,6 +200,8 @@ if ("${WITH_UI}" STREQUAL "Gtk+3") set (WITH_UI_GTK TRUE) endif() +option(WITH_WAYLAND "Enable Wayland support" ON) + #---------------------------------------------------------------------------------------------------- # Locations @@ -305,6 +307,16 @@ endif() #---------------------------------------------------------------------------------------------------- # GTK +pkg_check_modules(GTK3 gtk+-3.0>=3.24.0) +if (GTK3_FOUND) + set (HAVE_GTK3 ON) +endif() + +pkg_check_modules(GTK4 gtk4>=4.10.0) +if (GTK4_FOUND) + set (HAVE_GTK4 ON) +endif() + if ("${WITH_UI}" STREQUAL "Gtk+3") message(STATUS "Checking for: Gtk 3") @@ -368,10 +380,22 @@ endif() # Qt if ("${WITH_UI}" STREQUAL "Qt") - message(STATUS "Checking for: Qt") + set(EXTRA_QT_COMPONENTS "") + message(STATUS "Checking for Wayland ${WITH_WAYLAND}") + if (WITH_WAYLAND) + find_package(ECM 5.68.0 NO_MODULE) + set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH}) + + list(APPEND EXTRA_QT_COMPONENTS WaylandClient) + message(STATUS "Checking for: Wayland Client") + find_package(Wayland COMPONENTS Client) + set(HAVE_WAYLAND ON) + endif() + + message(STATUS "Checking for: Qt") find_package(QT NAMES Qt6 COMPONENTS Core REQUIRED) - find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Core Gui Svg LinguistTools Xml REQUIRED) + find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Core Gui Svg LinguistTools Xml WaylandClient ${EXTRA_QT_COMPONENTS} REQUIRED) qt6_standard_project_setup() set(CMAKE_AUTORCC ON) @@ -539,10 +563,6 @@ if (UNIX AND NOT APPLE) option(WITH_INDICATOR "Enable Ayatana indicator support" ON) option(LOCALINSTALL "Install file locally instead of to system location" OFF) - pkg_check_modules( - GTK3 - gtk+-3.0>=3.10.0) - if (WITH_INDICATOR AND HAVE_DBUSMENU) message(STATUS "Checking for: Ayatana Indicators") @@ -714,9 +734,7 @@ endif() #---------------------------------------------------------------------------------------------------- # Wayland -option(WITH_WAYLAND "Enable Wayland support" ON) - -if (UNIX AND NOT APPLE) +if (UNIX AND (NOT APPLE) AND WITH_UI_GTK) if (WITH_WAYLAND) message(STATUS "Checking for: Wayland") diff --git a/libs/config/src/QtSettingsConfigurator.cc b/libs/config/src/QtSettingsConfigurator.cc index 198929844..74ef3b051 100644 --- a/libs/config/src/QtSettingsConfigurator.cc +++ b/libs/config/src/QtSettingsConfigurator.cc @@ -115,7 +115,7 @@ QtSettingsConfigurator::set_value(const std::string &key, const ConfigValue &val } else if constexpr (!std::is_same_v) { - QVariant qval = value; + QVariant qval = QVariant::fromValue(value); settings.setValue(qkey, qval); } }, diff --git a/libs/input-monitor/src/CMakeLists.txt b/libs/input-monitor/src/CMakeLists.txt index 0f06c8fc8..e02250c5c 100644 --- a/libs/input-monitor/src/CMakeLists.txt +++ b/libs/input-monitor/src/CMakeLists.txt @@ -20,18 +20,26 @@ if (PLATFORM_OS_UNIX) endif() if (HAVE_WAYLAND) - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/ext-idle-notify-v1.c ${CMAKE_CURRENT_BINARY_DIR}/ext-idle-notify-v1-client.h - COMMAND ${WAYLAND_SCANNER} private-code ${CMAKE_CURRENT_SOURCE_DIR}/unix/protocols/ext-idle-notify-v1.xml ${CMAKE_CURRENT_BINARY_DIR}/ext-idle-notify-v1.c - COMMAND ${WAYLAND_SCANNER} client-header ${CMAKE_CURRENT_SOURCE_DIR}/unix/protocols/ext-idle-notify-v1.xml ${CMAKE_CURRENT_BINARY_DIR}/ext-idle-notify-v1-client.h - DEPENDS unix/protocols/ext-idle-notify-v1.xml - VERBATIM - ) - target_sources(workrave-libs-input-monitor PRIVATE - unix/WaylandInputMonitor.cc - ${CMAKE_CURRENT_BINARY_DIR}/ext-idle-notify-v1.c + if (HAVE_APP_GTK) + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/ext-idle-notify-v1.c ${CMAKE_CURRENT_BINARY_DIR}/ext-idle-notify-v1-client.h + COMMAND ${WAYLAND_SCANNER} private-code ${CMAKE_CURRENT_SOURCE_DIR}/unix/protocols/ext-idle-notify-v1.xml ${CMAKE_CURRENT_BINARY_DIR}/ext-idle-notify-v1.c + COMMAND ${WAYLAND_SCANNER} client-header ${CMAKE_CURRENT_SOURCE_DIR}/unix/protocols/ext-idle-notify-v1.xml ${CMAKE_CURRENT_BINARY_DIR}/ext-idle-notify-v1-client.h + DEPENDS unix/protocols/ext-idle-notify-v1.xml + VERBATIM ) - target_link_libraries(workrave-libs-input-monitor ${WAYLAND_CLIENT_LIBRARIES}) + target_sources(workrave-libs-input-monitor PRIVATE + unix/WaylandInputMonitor.cc + ${CMAKE_CURRENT_BINARY_DIR}/ext-idle-notify-v1.c + ) + target_link_libraries(workrave-libs-input-monitor ${WAYLAND_CLIENT_LIBRARIES}) + elseif(HAVE_APP_QT) + qt_generate_wayland_protocol_client_sources(workrave-libs-input-monitor + FILES ${CMAKE_CURRENT_SOURCE_DIR}/unix/protocols/ext-idle-notify-v1.xml + PRIVATE_CODE) + target_sources(workrave-libs-input-monitor PRIVATE unix/QtWaylandInputMonitor.cc) + target_link_libraries(workrave-libs-input-monitor Qt::WaylandClient Wayland::Client) + endif() endif() target_include_directories(workrave-libs-input-monitor PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/libs/input-monitor/src/unix/QtWaylandInputMonitor.cc b/libs/input-monitor/src/unix/QtWaylandInputMonitor.cc new file mode 100644 index 000000000..50e17fcf2 --- /dev/null +++ b/libs/input-monitor/src/unix/QtWaylandInputMonitor.cc @@ -0,0 +1,159 @@ +// Copyright (C) 2024 Rob Caelers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "QtWaylandInputMonitor.hh" + +#include +#include +#include + +#include "wayland-ext-idle-notify-v1-client-protocol.h" +#include "debug.hh" + +static const struct wl_registry_listener registry_listener = { + .global = QtWaylandInputMonitor::registry_global, + .global_remove = QtWaylandInputMonitor::registry_global_remove, +}; + +static const struct ext_idle_notification_v1_listener idle_notification_listener = { + .idled = QtWaylandInputMonitor::notification_idled, + .resumed = QtWaylandInputMonitor::notification_resumed, +}; + +QtWaylandInputMonitor::~QtWaylandInputMonitor() +{ + TRACE_ENTRY(); + if (monitor_thread) + { + monitor_thread->join(); + } +} + +bool +QtWaylandInputMonitor::init() +{ + TRACE_ENTRY(); + + auto *app = qGuiApp->nativeInterface(); + auto *wl_display = app->display(); + auto *wl_seat = app->seat(); + + wl_registry = wl_display_get_registry(wl_display); + + wl_registry_add_listener(wl_registry, ®istry_listener, this); + wl_display_roundtrip(wl_display); + + if (wl_notifier == nullptr) + { + TRACE_MSG("ext-idle-notify-v1 protocol unsupported"); + return false; + } + + auto *wl_notification = ext_idle_notifier_v1_get_idle_notification(wl_notifier, timeout, wl_seat); + + ext_idle_notification_v1_add_listener(wl_notification, &idle_notification_listener, this); + monitor_thread = std::make_shared([this] { run(); }); + + TRACE_MSG("ext-idle-notify-v1 protocol supported"); + return true; +} + +void +QtWaylandInputMonitor::terminate() +{ + TRACE_ENTRY(); + mutex.lock(); + abort = true; + cond.notify_all(); + mutex.unlock(); + + if (monitor_thread) + { + monitor_thread->join(); + } + + if (wl_notifier != nullptr) + { + ext_idle_notifier_v1_destroy(wl_notifier); + } + wl_registry_destroy(wl_registry); +} + +void +QtWaylandInputMonitor::registry_global(void *data, + struct wl_registry *registry, + uint32_t id, + const char *interface, + uint32_t version) +{ + TRACE_ENTRY(); + auto *self = static_cast(data); + if (strcmp(ext_idle_notifier_v1_interface.name, interface) == 0) + { + if (self->wl_notifier != nullptr) + { + ext_idle_notifier_v1_destroy(self->wl_notifier); + } + + self->wl_notifier = static_cast( + wl_registry_bind(self->wl_registry, + id, + &ext_idle_notifier_v1_interface, + std::min((uint32_t)ext_idle_notifier_v1_interface.version, version))); + } +} + +void +QtWaylandInputMonitor::registry_global_remove(void *data, struct wl_registry *registry, uint32_t id) +{ +} + +void +QtWaylandInputMonitor::notification_idled(void *data, struct ext_idle_notification_v1 *notification) +{ + auto *self = static_cast(data); + self->idle = true; +} + +void +QtWaylandInputMonitor::notification_resumed(void *data, struct ext_idle_notification_v1 *notification) +{ + auto *self = static_cast(data); + self->idle = false; +} + +void +QtWaylandInputMonitor::run() +{ + TRACE_ENTRY(); + { + std::unique_lock lock(mutex); + while (!abort) + { + if (!idle) + { + fire_action(); + } + + cond.wait_for(lock, std::chrono::milliseconds(timeout)); + } + } +} diff --git a/libs/input-monitor/src/unix/QtWaylandInputMonitor.hh b/libs/input-monitor/src/unix/QtWaylandInputMonitor.hh new file mode 100644 index 000000000..8de5962d5 --- /dev/null +++ b/libs/input-monitor/src/unix/QtWaylandInputMonitor.hh @@ -0,0 +1,56 @@ +// Copyright (C) 2024 Rob Caelers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#ifndef QTWAYLANDINPUTMONITOR_HH +#define QTWAYLANDINPUTMONITOR_HH + +#include "InputMonitor.hh" + +#include +#include +#include + +class QtWaylandInputMonitor : public InputMonitor +{ +public: + QtWaylandInputMonitor() = default; + ~QtWaylandInputMonitor() override; + + bool init() override; + void terminate() override; + void run(); + +public: + static void registry_global(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version); + static void registry_global_remove(void *data, struct wl_registry *registry, uint32_t id); + static void notification_idled(void *data, struct ext_idle_notification_v1 *notification); + static void notification_resumed(void *data, struct ext_idle_notification_v1 *notification); + +private: + static constexpr int timeout = 1000; + +private: + bool abort{false}; + std::shared_ptr monitor_thread; + std::mutex mutex; + std::condition_variable cond; + std::atomic idle{false}; + struct wl_registry *wl_registry{}; + struct ext_idle_notifier_v1 *wl_notifier{}; +}; + +#endif // QTWAYLANDINPUTMONITOR_HH diff --git a/libs/input-monitor/src/unix/UnixInputMonitorFactory.cc b/libs/input-monitor/src/unix/UnixInputMonitorFactory.cc index 7994bb8de..dcfdfa7da 100644 --- a/libs/input-monitor/src/unix/UnixInputMonitorFactory.cc +++ b/libs/input-monitor/src/unix/UnixInputMonitorFactory.cc @@ -36,7 +36,11 @@ #include "MutterInputMonitor.hh" #if defined(HAVE_WAYLAND) -# include "WaylandInputMonitor.hh" +# if defined(HAVE_APP_GTK) +# include "WaylandInputMonitor.hh" +# elif defined(HAVE_APP_QT) +# include "QtWaylandInputMonitor.hh" +# endif #endif using namespace std; @@ -115,24 +119,30 @@ UnixInputMonitorFactory::create_monitor(MonitorCapability capability) monitor = IInputMonitor::Ptr(new MutterInputMonitor()); } #if defined(HAVE_WAYLAND) +# if defined(HAVE_APP_GTK) else if (monitor_method == "wayland") { monitor = IInputMonitor::Ptr(new WaylandInputMonitor()); } +# elif defined(HAVE_APP_QT) + else if (monitor_method == "wayland") + { + monitor = IInputMonitor::Ptr(new QtWaylandInputMonitor()); + } +# endif #endif - if (monitor) - { - initialized = monitor->init(); + { + initialized = monitor->init(); - if (initialized) - { - TRACE_MSG("Success"); - break; - } + if (initialized) + { + TRACE_MSG("Success"); + break; + } - monitor.reset(); - } + monitor.reset(); + } loop++; if (loop == available_monitors.end()) diff --git a/libs/input-monitor/src/unix/X11InputMonitor.cc b/libs/input-monitor/src/unix/X11InputMonitor.cc index e67f0eb9b..a64e3bb21 100644 --- a/libs/input-monitor/src/unix/X11InputMonitor.cc +++ b/libs/input-monitor/src/unix/X11InputMonitor.cc @@ -55,7 +55,9 @@ errorHandler(Display *dpy, XErrorEvent *error) (void)dpy; if (error->error_code == BadWindow || error->error_code == BadDrawable) - return 0; + { + return 0; + } return 0; } #endif diff --git a/libs/input-monitor/src/unix/XScreenSaverMonitor.hh b/libs/input-monitor/src/unix/XScreenSaverMonitor.hh index 27e46e65a..071b061b2 100644 --- a/libs/input-monitor/src/unix/XScreenSaverMonitor.hh +++ b/libs/input-monitor/src/unix/XScreenSaverMonitor.hh @@ -46,7 +46,7 @@ private: std::shared_ptr monitor_thread; XScreenSaverInfo *screen_saver_info{nullptr}; Display *xdisplay{nullptr}; - Drawable root; + Drawable root{}; std::mutex mutex; std::condition_variable cond; diff --git a/libs/utils/src/CMakeLists.txt b/libs/utils/src/CMakeLists.txt index 37f90cc21..aaade8121 100644 --- a/libs/utils/src/CMakeLists.txt +++ b/libs/utils/src/CMakeLists.txt @@ -42,6 +42,7 @@ endif() if (HAVE_QT) target_link_libraries(workrave-libs-utils PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Gui) + target_link_libraries(workrave-libs-utils PRIVATE Qt::GuiPrivate) endif() target_link_libraries(workrave-libs-utils PUBLIC fmt::fmt spdlog::spdlog PRIVATE Boost::boost) diff --git a/libs/utils/src/Paths.cc b/libs/utils/src/Paths.cc index bb49bdc04..c9fbf3069 100644 --- a/libs/utils/src/Paths.cc +++ b/libs/utils/src/Paths.cc @@ -51,10 +51,20 @@ namespace { using namespace std::string_view_literals; -#ifdef HAVE_APP_QT +#ifdef PLATFORM_OS_WINDOWS +# ifdef HAVE_APP_QT constexpr std::string_view app_name = "WorkraveQt"sv; -#else +# else constexpr std::string_view app_name = "Workrave"sv; +# endif +#else +# ifdef HAVE_APP_QT + constexpr std::string_view app_name = "workrave-qt"sv; + constexpr std::string_view dot_app_name = ".workrave-qt"sv; +# else + constexpr std::string_view app_name = "workrave-qt"sv; + constexpr std::string_view dot_app_name = ".Workrave"sv; +# endif #endif std::filesystem::path portable_directory; @@ -197,7 +207,7 @@ Paths::get_data_directories() #if defined(HAVE_GLIB) const gchar *user_data_dir = g_get_user_data_dir(); - directories.push_back(std::filesystem::path(user_data_dir) / "workrave"); + directories.push_back(std::filesystem::path(user_data_dir) / app_name); const char *const *system_data_dirs = g_get_system_data_dirs(); for (int i = 0; system_data_dirs && system_data_dirs[i]; ++i) @@ -244,11 +254,11 @@ Paths::get_config_directories() #if defined(HAVE_GLIB) const gchar *user_config_dir = g_get_user_config_dir(); - directories.push_back(std::filesystem::path(user_config_dir) / "workrave"); + directories.push_back(std::filesystem::path(user_config_dir) / app_name); #endif #if defined(PLATFORM_OS_UNIX) || defined(PLATFORM_OS_MACOS) - directories.push_back(get_home_directory() / ".workrave"); + directories.push_back(get_home_directory() / dot_app_name); #endif } catch (std::exception &e) @@ -280,16 +290,16 @@ Paths::get_state_directories() #if defined(HAVE_GLIB) # if GLIB_CHECK_VERSION(2, 72, 0) const gchar *user_state_dir = g_get_user_state_dir(); - directories.push_back(std::filesystem::path(user_state_dir) / "workrave"); + directories.push_back(std::filesystem::path(user_state_dir) / app_name); # endif const gchar *user_data_dir = g_get_user_data_dir(); - directories.push_back(std::filesystem::path(user_data_dir) / "workrave"); + directories.push_back(std::filesystem::path(user_data_dir) / app_name); const gchar *user_config_dir = g_get_user_config_dir(); - directories.push_back(std::filesystem::path(user_config_dir) / "workrave"); + directories.push_back(std::filesystem::path(user_config_dir) / app_name); #endif #if defined(PLATFORM_OS_UNIX) || defined(PLATFORM_OS_MACOS) - directories.push_back(get_home_directory() / ".workrave"); + directories.push_back(get_home_directory() / dot_app_name); #endif } catch (std::exception &e) @@ -393,9 +403,9 @@ Paths::get_state_directory() # else const gchar *user_state_dir = g_get_user_data_dir(); # endif - ret = std::filesystem::path(user_state_dir) / "workrave"; + ret = std::filesystem::path(user_state_dir) / app_name; #else - ret = get_home_directory() / ".workrave"; + ret = get_home_directory() / fot_app_name; #endif } @@ -466,7 +476,7 @@ Paths::get_log_directory() return "/tmp"; } - return dir / "workrave"; + return dir / app_name; } #elif defined(PLATFORM_OS_MACOS) diff --git a/libs/utils/src/Platform-unix.cc b/libs/utils/src/Platform-unix.cc index 518250247..ff25eb890 100644 --- a/libs/utils/src/Platform-unix.cc +++ b/libs/utils/src/Platform-unix.cc @@ -35,6 +35,7 @@ #if defined(HAVE_QT) # include # include +# include # if defined(PLATFORM_OS_UNIX) # include @@ -57,7 +58,7 @@ Platform::get_default_display() } # elif defined(HAVE_QT) QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); - if (native != NULL) + if (native != nullptr) { xdisplay = native->nativeResourceForScreen("display", QGuiApplication::primaryScreen()); } @@ -84,14 +85,17 @@ Platform::get_default_display_name() } } # elif defined(HAVE_QT) - QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); - if (native != NULL) + const auto *x11app = qGuiApp->nativeInterface(); + if (x11app != nullptr) { - void *xdisplay = native->nativeResourceForScreen("display", QGuiApplication::primaryScreen()); - char *name = XDisplayString(static_cast(xdisplay)); - if (name != NULL) + Display *dpy = x11app->display(); + if (dpy != nullptr) { - ret = name; + char *name = XDisplayString(dpy); + if (name != nullptr) + { + ret = name; + } } } # else @@ -106,9 +110,19 @@ Platform::get_default_root_window() # if defined(HAVE_GTK) return gdk_x11_get_default_root_xwindow(); # elif defined(HAVE_QT) - QDesktopWidget *desktop = QApplication::desktop(); - QWindow *window = desktop->windowHandle(); - return window->winId(); + const auto *x11app = qGuiApp->nativeInterface(); + if (x11app == nullptr) + { + return 0; + } + + Display *dpy = x11app->display(); + if (dpy == nullptr) + { + return 0; + } + + return XDefaultRootWindow(dpy); # else # error Platform unsupported # endif diff --git a/ui/app/Application.cc b/ui/app/Application.cc index cbefbd3ce..e60b4a858 100644 --- a/ui/app/Application.cc +++ b/ui/app/Application.cc @@ -654,7 +654,7 @@ std::string Application::get_timers_tooltip() { // FIXME: duplicate - const std::array labels = {_("Micro-break"), _("Rest break"), _("Daily limit")}; + const std::array labels = {_("Micro-break"), _("Rest break"), _("Daily limit")}; std::string tip; OperationMode mode = core->get_regular_operation_mode(); diff --git a/ui/app/toolkits/gtkmm/platforms/unix/protocols/wlr-layer-shell-unstable-v1.xml b/ui/app/platforms/unix/protocols/wlr-layer-shell-unstable-v1.xml similarity index 62% rename from ui/app/toolkits/gtkmm/platforms/unix/protocols/wlr-layer-shell-unstable-v1.xml rename to ui/app/platforms/unix/protocols/wlr-layer-shell-unstable-v1.xml index 6b1fd7e04..283f391d7 100644 --- a/ui/app/toolkits/gtkmm/platforms/unix/protocols/wlr-layer-shell-unstable-v1.xml +++ b/ui/app/platforms/unix/protocols/wlr-layer-shell-unstable-v1.xml @@ -25,7 +25,7 @@ THIS SOFTWARE. - + Clients can use this interface to assign the surface_layer role to wl_surfaces. Such surfaces are assigned to a "layer" of the output and @@ -47,6 +47,12 @@ or manipulate a buffer prior to the first layer_surface.configure call must also be treated as errors. + After creating a layer_surface object and setting it up, the client + must perform an initial commit without any buffer attached. + The compositor will reply with a layer_surface.configure event. + The client must acknowledge it and is then allowed to attach a buffer + to map the surface. + You may pass NULL for output to allow the compositor to decide which output to use. Generally this will be the one that the user most recently interacted with. @@ -82,17 +88,35 @@ + + + + + + This request indicates that the client will not use the layer_shell + object any more. Objects that have been created through this instance + are not affected. + + - + An interface that may be implemented by a wl_surface, for surfaces that are designed to be rendered as a layer of a stacked desktop-like environment. - Layer surface state (size, anchor, exclusive zone, margin, interactivity) - is double-buffered, and will be applied at the time wl_surface.commit of - the corresponding wl_surface is called. + Layer surface state (layer, size, anchor, exclusive zone, + margin, interactivity) is double-buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + Attaching a null buffer to a layer surface unmaps it. + + Unmapping a layer_surface means that the surface cannot be shown by the + compositor until it is explicitly mapped again. The layer_surface + returns to the state it had right after layer_shell.get_layer_surface. + The client can re-map the surface by performing a commit without any + buffer attached, waiting for a configure event and handling it as usual. @@ -115,7 +139,7 @@ Requests that the compositor anchor the surface to the specified edges - and corners. If two orthoginal edges are specified (e.g. 'top' and + and corners. If two orthogonal edges are specified (e.g. 'top' and 'left'), then the anchor point will be the intersection of the edges (e.g. the top left corner of the output); otherwise the anchor point will be centered on that edge, or in the center if none is specified. @@ -127,19 +151,24 @@ - Requests that the compositor avoids occluding an area of the surface - with other surfaces. The compositor's use of this information is + Requests that the compositor avoids occluding an area with other + surfaces. The compositor's use of this information is implementation-dependent - do not assume that this region will not actually be occluded. - A positive value is only meaningful if the surface is anchored to an - edge, rather than a corner. The zone is the number of surface-local - coordinates from the edge that are considered exclusive. + A positive value is only meaningful if the surface is anchored to one + edge or an edge and both perpendicular edges. If the surface is not + anchored, anchored to only two perpendicular edges (a corner), anchored + to only two parallel edges or anchored to all edges, a positive value + will be treated the same as zero. + + A positive zone is the distance from the edge in surface-local + coordinates to consider exclusive. Surfaces that do not wish to have an exclusive zone may instead specify how they should interact with surfaces that do. If set to zero, the surface indicates that it would like to be moved to avoid occluding - surfaces with a positive excluzive zone. If set to -1, the surface + surfaces with a positive exclusive zone. If set to -1, the surface indicates that it would not like to be moved to accommodate for other surfaces, and the compositor should extend it all the way to the edges it is anchored to. @@ -174,21 +203,85 @@ + + + Types of keyboard interaction possible for layer shell surfaces. The + rationale for this is twofold: (1) some applications are not interested + in keyboard events and not allowing them to be focused can improve the + desktop experience; (2) some applications will want to take exclusive + keyboard focus. + + + + + This value indicates that this surface is not interested in keyboard + events and the compositor should never assign it the keyboard focus. + + This is the default value, set for newly created layer shell surfaces. + + This is useful for e.g. desktop widgets that display information or + only have interaction with non-keyboard input devices. + + + + + Request exclusive keyboard focus if this surface is above the shell surface layer. + + For the top and overlay layers, the seat will always give + exclusive keyboard focus to the top-most layer which has keyboard + interactivity set to exclusive. If this layer contains multiple + surfaces with keyboard interactivity set to exclusive, the compositor + determines the one receiving keyboard events in an implementation- + defined manner. In this case, no guarantee is made when this surface + will receive keyboard focus (if ever). + + For the bottom and background layers, the compositor is allowed to use + normal focus semantics. + + This setting is mainly intended for applications that need to ensure + they receive all keyboard events, such as a lock screen or a password + prompt. + + + + + This requests the compositor to allow this surface to be focused and + unfocused by the user in an implementation-defined manner. The user + should be able to unfocus this surface even regardless of the layer + it is on. + + Typically, the compositor will want to use its normal mechanism to + manage keyboard focus between layer shell surfaces with this setting + and regular toplevels on the desktop layer (e.g. click to focus). + Nevertheless, it is possible for a compositor to require a special + interaction to focus or unfocus layer shell surfaces (e.g. requiring + a click even if focus follows the mouse normally, or providing a + keybinding to switch focus between layers). + + This setting is mainly intended for desktop shell components (e.g. + panels) that allow keyboard interaction. Using this option can allow + implementing a desktop shell that can be fully usable without the + mouse. + + + + - Set to 1 to request that the seat send keyboard events to this layer - surface. For layers below the shell surface layer, the seat will use - normal focus semantics. For layers above the shell surface layers, the - seat will always give exclusive keyboard focus to the top-most layer - which has keyboard interactivity set to true. + Set how keyboard events are delivered to this surface. By default, + layer shell surfaces do not receive keyboard events; this request can + be used to change this. + + This setting is inherited by child surfaces set by the get_popup + request. Layer surfaces receive pointer, touch, and tablet events normally. If you do not want to receive them, set the input region on your surface to an empty region. - Events is double-buffered, see wl_surface.commit. + Keyboard interactivity is double-buffered, see wl_surface.commit. - + @@ -273,6 +366,8 @@ + + @@ -281,5 +376,32 @@ + + + + + + Change the layer that the surface is rendered on. + + Layer is double-buffered, see wl_surface.commit. + + + + + + + + + Requests an edge for the exclusive zone to apply. The exclusive + edge will be automatically deduced from anchor points when possible, + but when the surface is anchored to a corner, it will be necessary + to set it explicitly to disambiguate, as it is not possible to deduce + which one of the two corner edges should be used. + + The edge must be one the surface is anchored to, otherwise the + invalid_exclusive_edge protocol error will be raised. + + + diff --git a/ui/app/toolkits/gtkmm/platforms/unix/protocols/xdg-shell.xml b/ui/app/platforms/unix/protocols/xdg-shell.xml similarity index 100% rename from ui/app/toolkits/gtkmm/platforms/unix/protocols/xdg-shell.xml rename to ui/app/platforms/unix/protocols/xdg-shell.xml diff --git a/ui/app/toolkits/gtkmm/CMakeLists.txt b/ui/app/toolkits/gtkmm/CMakeLists.txt index 29114331a..6867545c5 100644 --- a/ui/app/toolkits/gtkmm/CMakeLists.txt +++ b/ui/app/toolkits/gtkmm/CMakeLists.txt @@ -53,17 +53,17 @@ if (PLATFORM_OS_UNIX) if (HAVE_WAYLAND) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/wlr-layer-shell-unstable-v1.c ${CMAKE_CURRENT_BINARY_DIR}/wlr-layer-shell-unstable-v1-client.h - COMMAND ${WAYLAND_SCANNER} private-code ${CMAKE_CURRENT_SOURCE_DIR}/platforms/unix/protocols/wlr-layer-shell-unstable-v1.xml ${CMAKE_CURRENT_BINARY_DIR}/wlr-layer-shell-unstable-v1.c - COMMAND ${WAYLAND_SCANNER} client-header ${CMAKE_CURRENT_SOURCE_DIR}/platforms/unix/protocols/wlr-layer-shell-unstable-v1.xml ${CMAKE_CURRENT_BINARY_DIR}/wlr-layer-shell-unstable-v1-client.h - DEPENDS platforms/unix/protocols/wlr-layer-shell-unstable-v1.xml + COMMAND ${WAYLAND_SCANNER} private-code ${CMAKE_SOURCE_DIR}/ui/app/platforms/unix/protocols/wlr-layer-shell-unstable-v1.xml ${CMAKE_CURRENT_BINARY_DIR}/wlr-layer-shell-unstable-v1.c + COMMAND ${WAYLAND_SCANNER} client-header ${CMAKE_SOURCE_DIR}/ui/app/platforms/unix/protocols/wlr-layer-shell-unstable-v1.xml ${CMAKE_CURRENT_BINARY_DIR}/wlr-layer-shell-unstable-v1-client.h + DEPENDS ${CMAKE_SOURCE_DIR}/ui/app/platforms/unix/protocols/wlr-layer-shell-unstable-v1.xml VERBATIM ) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell.c ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell-client.h - COMMAND ${WAYLAND_SCANNER} private-code ${CMAKE_CURRENT_SOURCE_DIR}/platforms/unix/protocols/xdg-shell.xml ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell.c - COMMAND ${WAYLAND_SCANNER} client-header ${CMAKE_CURRENT_SOURCE_DIR}/platforms/unix/protocols/xdg-shell.xml ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell-client.h - DEPENDS platforms/unix/protocols/xdg-shell.xml + COMMAND ${WAYLAND_SCANNER} private-code ${CMAKE_SOURCE_DIR}/ui/app/platforms/unix/protocols/xdg-shell.xml ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell.c + COMMAND ${WAYLAND_SCANNER} client-header ${CMAKE_SOURCE_DIR}/ui/app/platforms/unix/protocols/xdg-shell.xml ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell-client.h + DEPENDS ${CMAKE_SOURCE_DIR}/ui/app/platforms/unix/protocols/xdg-shell.xml VERBATIM ) @@ -82,9 +82,7 @@ if (PLATFORM_OS_UNIX) endif() target_include_directories(workrave-toolkit-gtkmm PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/platforms/unix ${CMAKE_CURRENT_SOURCE_DIR}/platforms/unix/protocols) - - - target_include_directories(workrave-toolkit-gtkmm PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/platforms/unix/) + target_include_directories(workrave-toolkit-gtkmm PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) endif() if (PLATFORM_OS_WINDOWS) diff --git a/ui/app/toolkits/gtkmm/platforms/unix/WaylandWindowManager.cc b/ui/app/toolkits/gtkmm/platforms/unix/WaylandWindowManager.cc index 3e1d1bf11..185968d83 100644 --- a/ui/app/toolkits/gtkmm/platforms/unix/WaylandWindowManager.cc +++ b/ui/app/toolkits/gtkmm/platforms/unix/WaylandWindowManager.cc @@ -155,14 +155,18 @@ LayerSurface::LayerSurface(struct zwlr_layer_shell_v1 *layer_shell, | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT); zwlr_layer_surface_v1_set_size(layer_surface, 0, 0); zwlr_layer_surface_v1_set_margin(layer_surface, 0, 0, 0, 0); - zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface, keyboard_focus ? TRUE : FALSE); + zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, -1); + zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface, + keyboard_focus ? ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE + : ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE); zwlr_layer_surface_v1_add_listener(layer_surface, &layer_surface_listener, this); wl_surface_commit(surface); wl_display_roundtrip(display); } -LayerSurface::~LayerSurface() { +LayerSurface::~LayerSurface() +{ zwlr_layer_surface_v1_destroy(this->layer_surface); } diff --git a/ui/app/toolkits/qt/BreakWindow.cc b/ui/app/toolkits/qt/BreakWindow.cc index 3189ef8fc..9d87ddb38 100644 --- a/ui/app/toolkits/qt/BreakWindow.cc +++ b/ui/app/toolkits/qt/BreakWindow.cc @@ -30,6 +30,7 @@ #include "core/ICore.hh" #include "utils/AssetPath.hh" +#include "utils/Platform.hh" #include "debug.hh" #include "UiUtil.hh" diff --git a/ui/app/toolkits/qt/BreakWindow.hh b/ui/app/toolkits/qt/BreakWindow.hh index c48a28a65..ca4fd9170 100644 --- a/ui/app/toolkits/qt/BreakWindow.hh +++ b/ui/app/toolkits/qt/BreakWindow.hh @@ -33,6 +33,10 @@ #include "SizeGroup.hh" #include "ui/IApplicationContext.hh" +#if defined(HAVE_WAYLAND) +# include "WaylandWindowManager.hh" +#endif + class BreakWindow : public QWidget , public IBreakWindow diff --git a/ui/app/toolkits/qt/CMakeLists.txt b/ui/app/toolkits/qt/CMakeLists.txt index 3a7ed7802..f5edcf817 100644 --- a/ui/app/toolkits/qt/CMakeLists.txt +++ b/ui/app/toolkits/qt/CMakeLists.txt @@ -35,29 +35,29 @@ target_sources(workrave-toolkit-qt PRIVATE widgets/TimerBoxView.cc ) + if (PLATFORM_OS_UNIX) - target_sources(workrave-toolkit-qt PRIVATE ToolkitLinux.cc) + target_sources(workrave-toolkit-qt PRIVATE ToolkitUnix.cc platforms/unix/UnixLocker.cc platforms/unix/WaylandWindowManager.cc) if (HAVE_WAYLAND) - qt_generate_wayland_protocol_client_sources(workrave-toolkit-qt FILES - platforms/unix/protocols/wlr-layer-shell-unstable-v1.xml - platforms/unix/protocols/xdg-shell.xml) + qt_generate_wayland_protocol_client_sources(workrave-toolkit-qt + FILES ${CMAKE_SOURCE_DIR}/ui/app/platforms/unix/protocols/wlr-layer-shell-unstable-v1.xml + ${CMAKE_SOURCE_DIR}/ui/app/platforms/unix/protocols/xdg-shell.xml) target_sources(workrave-toolkit-qt PRIVATE - platforms/unix/WaylandWindowManager.cc - ${CMAKE_CURRENT_BINARY_DIR}/wlr-layer-shell-unstable-v1.c - ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell.c) + platforms/unix/WaylandWindowManager.cc) endif() - if (HAVE_DBUSMENU) - target_sources(workrave-toolkit-qt PRIVATE platforms/unix/DbusMenu.cc) - endif() + # if (HAVE_DBUSMENU) + # target_sources(workrave-toolkit-qt PRIVATE platforms/unix/DbusMenu.cc) + # endif() - if (HAVE_APPINDICATOR) - target_sources(workrave-toolkit-qt PRIVATE platforms/unix/AppIndicatorMenu.cc) - endif() + # if (HAVE_APPINDICATOR) + # target_sources(workrave-toolkit-qt PRIVATE platforms/unix/AppIndicatorMenu.cc) + # endif() target_include_directories(workrave-toolkit-qt PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/platforms/unix ${CMAKE_CURRENT_SOURCE_DIR}/platforms/unix/protocols) - target_include_directories(workrave-toolkit-qt PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/platforms/unix) + target_include_directories(workrave-toolkit-qt PRIVATE ${CMAKE_BINARY_DIR}/ui/app/platforms/unix) + target_link_libraries(workrave-toolkit-qt PUBLIC Qt::WaylandClient Wayland::Client Qt::WaylandClientPrivate) endif() if (PLATFORM_OS_WINDOWS) @@ -76,6 +76,7 @@ if (PLATFORM_OS_MACOS) target_include_directories(workrave-toolkit-qt PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/platforms/macos) endif() + set(MOC_SOURCES AboutDialog.hh BreakWindow.hh diff --git a/ui/app/toolkits/qt/PreludeWindow.cc b/ui/app/toolkits/qt/PreludeWindow.cc index 7359c820a..3fffe4821 100644 --- a/ui/app/toolkits/qt/PreludeWindow.cc +++ b/ui/app/toolkits/qt/PreludeWindow.cc @@ -28,6 +28,7 @@ #include "core/IApp.hh" #include "utils/AssetPath.hh" +#include "utils/Platform.hh" #include "UiUtil.hh" #include "qformat.hh" @@ -140,7 +141,7 @@ PreludeWindow::start() #if defined(HAVE_WAYLAND) if (window_manager) { - window_manager->init_surface(*this, head.get_monitor(), false); + window_manager->init_surface(this, screen, false); } #endif diff --git a/ui/app/toolkits/qt/PreludeWindow.hh b/ui/app/toolkits/qt/PreludeWindow.hh index 67bd196a1..ddd4e76a2 100644 --- a/ui/app/toolkits/qt/PreludeWindow.hh +++ b/ui/app/toolkits/qt/PreludeWindow.hh @@ -28,6 +28,10 @@ #include "ui/IPreludeWindow.hh" #include "TimeBar.hh" +#if defined(HAVE_WAYLAND) +# include "WaylandWindowManager.hh" +#endif + #if defined(PLATFORM_OS_MACOS) # include "MouseMonitor.hh" #endif diff --git a/ui/app/toolkits/qt/ToolkitUnix.cc b/ui/app/toolkits/qt/ToolkitUnix.cc index 8595047c6..f23d5bc48 100644 --- a/ui/app/toolkits/qt/ToolkitUnix.cc +++ b/ui/app/toolkits/qt/ToolkitUnix.cc @@ -23,10 +23,13 @@ #include -#include "BreakWindow.hh" - -#include "config/IConfigurator.hh" #include "debug.hh" +#include "ui/GUIConfig.hh" +// #include "GnomeSession.hh" + +#if defined(HAVE_INDICATOR) +# include "IndicatorAppletMenu.hh" +#endif ToolkitUnix::ToolkitUnix(int argc, char **argv) : Toolkit(argc, argv) @@ -54,24 +57,11 @@ ToolkitUnix::init(std::shared_ptr app) Toolkit::init(app); - Glib::VariantType String(Glib::VARIANT_TYPE_STRING); - gapp->add_action_with_parameter("confirm-notification", String, [this](const Glib::VariantBase &value) { - Glib::Variant s = Glib::VariantBase::cast_dynamic>(value); - notify_confirm(s.get()); - }); -} - -IBreakWindow::Ptr -ToolkitUnix::create_break_window(int screen_index, workrave::BreakId break_id, BreakFlags break_flags) -{ - auto ret = Toolkit::create_break_window(screen_index, break_id, break_flags); - // FIXME: remove hack - if (auto break_window = std::dynamic_pointer_cast(ret); break_window) - { - auto *gdk_window = break_window->get_window()->gobj(); - locker->set_window(gdk_window); - } - return ret; + // Glib::VariantType String(Glib::VARIANT_TYPE_STRING); + // gapp->add_action_with_parameter("confirm-notification", String, [this](const Glib::VariantBase &value) { + // Glib::Variant s = Glib::VariantBase::cast_dynamic>(value); + // notify_confirm(s.get()); + // }); } std::shared_ptr @@ -80,17 +70,23 @@ ToolkitUnix::get_locker() return locker; } +auto +ToolkitUnix::get_desktop_image() -> QPixmap +{ + return {}; +} + void ToolkitUnix::show_notification(const std::string &id, const std::string &title, const std::string &balloon, std::function func) { - notify_add_confirm_function(id, func); - auto notification = Gio::Notification::create("Workrave"); - notification->set_body(balloon); - notification->set_default_action_variant("app.confirm-notification", Glib::Variant::create(id)); - auto icon = Gio::ThemedIcon::create("dialog-information"); - notification->set_icon(icon); - gapp->send_notification(id, notification); + // notify_add_confirm_function(id, func); + // auto notification = Gio::Notification::create("Workrave"); + // notification->set_body(balloon); + // notification->set_default_action_variant("app.confirm-notification", Glib::Variant::create(id)); + // auto icon = Gio::ThemedIcon::create("dialog-information"); + // notification->set_icon(icon); + // gapp->send_notification(id, notification); } diff --git a/ui/app/toolkits/qt/ToolkitUnix.hh b/ui/app/toolkits/qt/ToolkitUnix.hh index 2621d1a3c..ae491a662 100644 --- a/ui/app/toolkits/qt/ToolkitUnix.hh +++ b/ui/app/toolkits/qt/ToolkitUnix.hh @@ -31,13 +31,13 @@ public: // IToolkit void preinit(std::shared_ptr config) override; void init(std::shared_ptr app) override; - IBreakWindow::Ptr create_break_window(int screen_index, workrave::BreakId break_id, BreakFlags break_flags) override; std::shared_ptr get_locker() override; void show_notification(const std::string &id, const std::string &title, const std::string &balloon, std::function func) override; + auto get_desktop_image() -> QPixmap override; private: std::shared_ptr locker; diff --git a/ui/app/toolkits/qt/platforms/unix/UnixLocker.cc b/ui/app/toolkits/qt/platforms/unix/UnixLocker.cc new file mode 100644 index 000000000..84b03ea14 --- /dev/null +++ b/ui/app/toolkits/qt/platforms/unix/UnixLocker.cc @@ -0,0 +1,54 @@ +// Copyright (C) 2001 - 2021 Rob Caelers & Raymond Penners +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#include +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "UnixLocker.hh" +#include "utils/Platform.hh" + +using namespace workrave::utils; + +bool +UnixLocker::can_lock() +{ + return !Platform::running_on_wayland(); +} + +void +UnixLocker::prepare_lock() +{ +} + +void +UnixLocker::lock() +{ + spdlog::info("UnixLocker::lock()"); + if (!Platform::running_on_wayland()) + { + } +} + +void +UnixLocker::unlock() +{ + if (!Platform::running_on_wayland()) + { + } +} diff --git a/ui/app/toolkits/qt/platforms/unix/UnixLocker.hh b/ui/app/toolkits/qt/platforms/unix/UnixLocker.hh new file mode 100644 index 000000000..d647f4122 --- /dev/null +++ b/ui/app/toolkits/qt/platforms/unix/UnixLocker.hh @@ -0,0 +1,41 @@ +// Copyright (C) 2001 - 2021 Rob Caelers & Raymond Penners +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#ifndef UNIXLOCKER_HH +#define UNIXLOCKER_HH + +#include "ui/Locker.hh" + +namespace Gtk +{ + class Window; +} + +class UnixLocker : public Locker +{ +public: + UnixLocker() = default; + + bool can_lock() override; + void prepare_lock() override; + void lock() override; + void unlock() override; + +private: +}; + +#endif // UNIXLOCKER_HH diff --git a/ui/app/toolkits/qt/platforms/unix/WaylandWindowManager.cc b/ui/app/toolkits/qt/platforms/unix/WaylandWindowManager.cc new file mode 100644 index 000000000..45cdea1df --- /dev/null +++ b/ui/app/toolkits/qt/platforms/unix/WaylandWindowManager.cc @@ -0,0 +1,185 @@ +// Copyright (C) 2024 Rob Caelers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "WaylandWindowManager.hh" + +#include +#include + +#include "debug.hh" + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +static const struct wl_registry_listener registry_listener = { + .global = WaylandWindowManager::registry_global, + .global_remove = WaylandWindowManager::registry_global_remove, +}; + +WaylandWindowManager::~WaylandWindowManager() +{ + if (layer_shell != nullptr) + { + zwlr_layer_shell_v1_destroy(layer_shell); + } + wl_registry_destroy(wl_registry); + TRACE_ENTRY(); +} + +bool +WaylandWindowManager::init() +{ + TRACE_ENTRY(); + + auto *app = qGuiApp->nativeInterface(); + auto *wl_display = app->display(); + + wl_registry = wl_display_get_registry(wl_display); + + wl_registry_add_listener(wl_registry, ®istry_listener, this); + wl_display_roundtrip(wl_display); + + if (layer_shell == nullptr) + { + TRACE_MSG("zwlr-layer-surface protocol unsupported"); + spdlog::warn( + "Your Wayland compositor does not support the wlr layer shell protocol. Workrave will not be able to properly position its break windows."); + return false; + } + + TRACE_MSG("ext-idle-notify-v1 protocol supported"); + return true; +} + +void +WaylandWindowManager::registry_global(void *data, + struct wl_registry *registry, + uint32_t id, + const char *interface, + uint32_t version) +{ + TRACE_ENTRY(); + TRACE_MSG("interface: {} {}", interface, version); + auto *self = static_cast(data); + if (strcmp(zwlr_layer_shell_v1_interface.name, interface) == 0) + { + if (self->layer_shell != nullptr) + { + zwlr_layer_shell_v1_destroy(self->layer_shell); + } + + self->layer_shell = static_cast( + wl_registry_bind(self->wl_registry, + id, + &zwlr_layer_shell_v1_interface, + std::min((uint32_t)zwlr_layer_shell_v1_interface.version, version))); + } +} + +void +WaylandWindowManager::registry_global_remove(void *data, struct wl_registry *registry, uint32_t id) +{ +} + +void +WaylandWindowManager::init_surface(QWidget *window, QScreen *screen, bool keyboard_focus) +{ + TRACE_ENTRY(); + if (layer_shell != nullptr) + { + auto layer = std::make_shared(layer_shell, window, screen, keyboard_focus); + surfaces.push_back(layer); + } +} + +void +WaylandWindowManager::clear_surfaces() +{ + TRACE_ENTRY(); + surfaces.clear(); +} + +LayerSurface::LayerSurface(struct zwlr_layer_shell_v1 *layer_shell, QWidget *window, QScreen *screen, bool keyboard_focus) + : layer_shell(layer_shell) + , keyboard_focus(keyboard_focus) + +{ + TRACE_ENTRY(); + auto *wayland_window = window->windowHandle()->nativeInterface(); + auto *wayland_screen = screen->nativeInterface(); + + auto *output = wayland_screen->output(); + auto *surface = wayland_window->surface(); + layer_surface = zwlr_layer_shell_v1_get_layer_surface(layer_shell, + surface, + output, + ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, + "workrave"); + + zwlr_layer_surface_v1_set_anchor(layer_surface, + static_cast(ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM + | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT); + zwlr_layer_surface_v1_set_size(layer_surface, 0, 0); + zwlr_layer_surface_v1_set_margin(layer_surface, 0, 0, 0, 0); + zwlr_layer_surface_v1_set_exclusive_zone(layer_surface, -1); + zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface, + keyboard_focus ? ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE + : ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_NONE); + zwlr_layer_surface_v1_add_listener(layer_surface, &layer_surface_listener, this); + + wl_surface_commit(surface); + wl_display_roundtrip(display); +} + +LayerSurface::~LayerSurface() +{ + zwlr_layer_surface_v1_destroy(this->layer_surface); +} + +void +LayerSurface::layer_surface_configure(void *data, + struct zwlr_layer_surface_v1 *surface, + // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) + uint32_t serial, + uint32_t width, + uint32_t height) +{ + TRACE_ENTRY(); + auto *self = static_cast(data); + zwlr_layer_surface_v1_ack_configure(surface, serial); + wl_display_roundtrip(self->display); +} + +void +LayerSurface::layer_surface_closed(void *data, struct zwlr_layer_surface_v1 *surface) +{ + TRACE_ENTRY(); +} diff --git a/ui/app/toolkits/qt/platforms/unix/WaylandWindowManager.hh b/ui/app/toolkits/qt/platforms/unix/WaylandWindowManager.hh new file mode 100644 index 000000000..d661cda32 --- /dev/null +++ b/ui/app/toolkits/qt/platforms/unix/WaylandWindowManager.hh @@ -0,0 +1,77 @@ +// Copyright (C) 2024 Rob Caelers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// + +#ifndef WAYLANDWINDOWMANAGER_HH +#define WAYLANDWINDOWMANAGER_HH + +#include +#include + +#include +#include + +#include "wayland-wlr-layer-shell-unstable-v1-client-protocol.h" + +class LayerSurface +{ +public: + LayerSurface(struct zwlr_layer_shell_v1 *layer_shell, QWidget *window, QScreen *screen, bool keyboard_focus); + ~LayerSurface(); + +private: + static void layer_surface_configure(void *data, + struct zwlr_layer_surface_v1 *surface, + uint32_t serial, + uint32_t width, + uint32_t height); + + static void layer_surface_closed(void *data, struct zwlr_layer_surface_v1 *surface); + +private: + struct zwlr_layer_shell_v1 *layer_shell{}; + struct zwlr_layer_surface_v1 *layer_surface{}; + struct wl_display *display{}; + bool keyboard_focus{true}; + + static constexpr const struct zwlr_layer_surface_v1_listener layer_surface_listener = { + .configure = LayerSurface::layer_surface_configure, + .closed = LayerSurface::layer_surface_closed, + }; +}; + +class WaylandWindowManager +{ +public: + WaylandWindowManager() = default; + ~WaylandWindowManager(); + + bool init(); + + void init_surface(QWidget *window, QScreen *screen, bool keyboard_focus); + void clear_surfaces(); + +public: + static void registry_global(void *data, struct wl_registry *registry, uint32_t id, const char *interface, uint32_t version); + static void registry_global_remove(void *data, struct wl_registry *registry, uint32_t id); + +private: + struct wl_registry *wl_registry{}; + struct zwlr_layer_shell_v1 *layer_shell{}; + std::list> surfaces; +}; + +#endif // WAYLANDWINDOWMANAGER_HH diff --git a/ui/app/toolkits/qt/preferences/TimerBoxPreferencesPanel.cc b/ui/app/toolkits/qt/preferences/TimerBoxPreferencesPanel.cc index a83cd1299..343fe907d 100644 --- a/ui/app/toolkits/qt/preferences/TimerBoxPreferencesPanel.cc +++ b/ui/app/toolkits/qt/preferences/TimerBoxPreferencesPanel.cc @@ -29,6 +29,7 @@ #include "ui/GUIConfig.hh" #include "core/CoreConfig.hh" +#include "utils/Platform.hh" #include "Ui.hh" #include "UiUtil.hh" diff --git a/ui/applets/common/src/CMakeLists.txt b/ui/applets/common/src/CMakeLists.txt index 89af40000..439d1ff02 100644 --- a/ui/applets/common/src/CMakeLists.txt +++ b/ui/applets/common/src/CMakeLists.txt @@ -5,28 +5,29 @@ set(SRC utils.c ) -if (HAVE_GTK AND UNIX AND NOT APPLE) +if (UNIX AND NOT APPLE) - add_library(workrave-private-1.0 SHARED ${SRC}) + if (HAVE_GTK3) + add_library(workrave-private-1.0 SHARED ${SRC}) - target_include_directories(workrave-private-1.0 - PRIVATE - ${CMAKE_SOURCE_DIR}/ui/applets/common/include - ${CMAKE_SOURCE_DIR}/libs/utils/include - ${CMAKE_SOURCE_DIR}/libs/config/include - ${GTK_INCLUDE_DIRS} - INTERFACE - ${CMAKE_SOURCE_DIR}/ui/applets/common/include - ) + target_include_directories(workrave-private-1.0 + PRIVATE + ${CMAKE_SOURCE_DIR}/ui/applets/common/include + ${CMAKE_SOURCE_DIR}/libs/utils/include + ${CMAKE_SOURCE_DIR}/libs/config/include + ${GTK3_INCLUDE_DIRS} + INTERFACE + ${CMAKE_SOURCE_DIR}/ui/applets/common/include + ) - target_link_directories(workrave-private-1.0 PRIVATE ${GTK_LIBPATH}) - target_link_libraries(workrave-private-1.0 ${GTK_LIBRARIES}) + target_link_directories(workrave-private-1.0 PRIVATE ${GTK3_LIBPATH}) + target_link_libraries(workrave-private-1.0 ${GTK3_LIBRARIES}) - set_target_properties(workrave-private-1.0 PROPERTIES VERSION 0.0.0 SOVERSION 0) - install(TARGETS workrave-private-1.0 RUNTIME DESTINATION bin LIBRARY DESTINATION ${LIBDIR} ARCHIVE DESTINATION ${LIBDIR} LIBRARY NAMELINK_SKIP) + set_target_properties(workrave-private-1.0 PROPERTIES VERSION 0.0.0 SOVERSION 0) + install(TARGETS workrave-private-1.0 RUNTIME DESTINATION bin LIBRARY DESTINATION ${LIBDIR} ARCHIVE DESTINATION ${LIBDIR} LIBRARY NAMELINK_SKIP) + endif() - pkg_check_modules(GTK4 gtk4>=4.10.0) - if (GTK4_FOUND) + if (HAVE_GTK4) add_library(workrave-gtk4-private-1.0 SHARED ${SRC}) target_include_directories(workrave-gtk4-private-1.0 @@ -71,22 +72,25 @@ if (HAVE_GTK AND UNIX AND NOT APPLE) set(INTROSPECTION_SCANNER_ARGS --add-include-path=${CMAKE_CURRENT_SOURCE_DIR} --add-include-path=${CMAKE_CURRENT_SOURCE_DIR}/include --warn-all) set(INTROSPECTION_COMPILER_ARGS --includedir=${CMAKE_CURRENT_SOURCE_DIR} --includedir=${CMAKE_CURRENT_SOURCE_DIR}/include) - filter_cflags("${GTK_CFLAGS}" GTK_CFLAGS_FILTERED) + if (HAVE_GTK3) + filter_cflags("${GTK_CFLAGS}" GTK_CFLAGS_FILTERED) - set(introspection_files ${SRC} ../include/timerbox.h ../include/timebar.h) - set(Workrave_1_0_gir "workrave-private") - set(Workrave_1_0_gir_INCLUDES GObject-2.0 Gtk-3.0 cairo-1.0) - set(Workrave_1_0_gir_CFLAGS ${GTK_CFLAGS_FILTERED} -I${CMAKE_CURRENT_SOURCE_DIR}/include) - set(Workrave_1_0_gir_LIBS workrave-private-1.0) - set(Workrave_1_0_gir_VERSION "1.0") - _list_prefix(_abs_introspection_files introspection_files "${CMAKE_CURRENT_SOURCE_DIR}/") - set(Workrave_1_0_gir_FILES ${_abs_introspection_files}) - set(Workrave_1_0_gir_EXPORT_PACKAGES workrave-private) + set(introspection_files ${SRC} ../include/timerbox.h ../include/timebar.h) + set(Workrave_1_0_gir "workrave-private") + set(Workrave_1_0_gir_INCLUDES GObject-2.0 Gtk-3.0 cairo-1.0) + set(Workrave_1_0_gir_CFLAGS ${GTK_CFLAGS_FILTERED} -I${CMAKE_CURRENT_SOURCE_DIR}/include) + set(Workrave_1_0_gir_LIBS workrave-private-1.0) + set(Workrave_1_0_gir_VERSION "1.0") + _list_prefix(_abs_introspection_files introspection_files "${CMAKE_CURRENT_SOURCE_DIR}/") + set(Workrave_1_0_gir_FILES ${_abs_introspection_files}) + set(Workrave_1_0_gir_EXPORT_PACKAGES workrave-private) - list(APPEND INTROSPECTION_GIRS Workrave-1.0.gir) + list(APPEND INTROSPECTION_GIRS Workrave-1.0.gir) + endif() - if (GTK4_FOUND) + if (HAVE_GTK4) filter_cflags("${GTK4_CFLAGS}" GTK4_CFLAGS_FILTERED) + set(introspection_files ${SRC} ../include/timerbox.h ../include/timebar.h) set(Workrave_2_0_gir "workrave-gtk4-private") set(Workrave_2_0_gir_INCLUDES GObject-2.0 Gtk-4.0 cairo-1.0) @@ -98,8 +102,8 @@ if (HAVE_GTK AND UNIX AND NOT APPLE) set(Workrave_2_0_gir_EXPORT_PACKAGES workrave-gtk4-private) list(APPEND INTROSPECTION_GIRS Workrave-2.0.gir) - endif() + gir_add_introspections(INTROSPECTION_GIRS) endif () endif() diff --git a/ui/applets/xfce/src/main.c b/ui/applets/xfce/src/main.c index 95c876ac8..301b48916 100644 --- a/ui/applets/xfce/src/main.c +++ b/ui/applets/xfce/src/main.c @@ -27,10 +27,9 @@ #include "control.h" #include "utils.h" #include "commonui/credits.h" -#include "commonui/nls.h" #include "commonui/MenuDefs.hh" -typedef struct _WorkraveApplet +typedef struct WorkraveApplet { XfcePanelPlugin *plugin; @@ -83,7 +82,8 @@ static gboolean menu_items_lookup_id_by_menu_item(WorkraveApplet *applet, GtkMenuItem *item, uint32_t *id) { GHashTableIter iter; - gpointer key, value; + gpointer key = NULL; + gpointer value = NULL; g_hash_table_iter_init(&iter, applet->menus); while (g_hash_table_iter_next(&iter, &key, &value))