Skip to content

Commit

Permalink
Qt on Wayland
Browse files Browse the repository at this point in the history
  • Loading branch information
rcaelers committed Feb 15, 2025
1 parent 0ff5092 commit 7ec8727
Show file tree
Hide file tree
Showing 31 changed files with 942 additions and 164 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
36 changes: 27 additions & 9 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ if ("${WITH_UI}" STREQUAL "Gtk+3")
set (WITH_UI_GTK TRUE)
endif()

option(WITH_WAYLAND "Enable Wayland support" ON)

#----------------------------------------------------------------------------------------------------
# Locations

Expand Down Expand Up @@ -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")

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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")

Expand Down Expand Up @@ -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")

Expand Down
2 changes: 1 addition & 1 deletion libs/config/src/QtSettingsConfigurator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ QtSettingsConfigurator::set_value(const std::string &key, const ConfigValue &val
}
else if constexpr (!std::is_same_v<std::monostate, T>)
{
QVariant qval = value;
QVariant qval = QVariant::fromValue(value);
settings.setValue(qkey, qval);
}
},
Expand Down
30 changes: 19 additions & 11 deletions libs/input-monitor/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
159 changes: 159 additions & 0 deletions libs/input-monitor/src/unix/QtWaylandInputMonitor.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// Copyright (C) 2024 Rob Caelers <[email protected]>
// 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 <http://www.gnu.org/licenses/>.
//

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "QtWaylandInputMonitor.hh"

#include <memory>
#include <wayland-client-protocol.h>
#include <QGuiApplication>

#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<QNativeInterface::QWaylandApplication>();
auto *wl_display = app->display();
auto *wl_seat = app->seat();

wl_registry = wl_display_get_registry(wl_display);

wl_registry_add_listener(wl_registry, &registry_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<std::thread>([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<QtWaylandInputMonitor *>(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<ext_idle_notifier_v1 *>(
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<QtWaylandInputMonitor *>(data);
self->idle = true;
}

void
QtWaylandInputMonitor::notification_resumed(void *data, struct ext_idle_notification_v1 *notification)
{
auto *self = static_cast<QtWaylandInputMonitor *>(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));
}
}
}
56 changes: 56 additions & 0 deletions libs/input-monitor/src/unix/QtWaylandInputMonitor.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (C) 2024 Rob Caelers <[email protected]>
// 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 <http://www.gnu.org/licenses/>.
//

#ifndef QTWAYLANDINPUTMONITOR_HH
#define QTWAYLANDINPUTMONITOR_HH

#include "InputMonitor.hh"

#include <atomic>
#include <condition_variable>
#include <thread>

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<std::thread> monitor_thread;
std::mutex mutex;
std::condition_variable cond;
std::atomic<bool> idle{false};
struct wl_registry *wl_registry{};
struct ext_idle_notifier_v1 *wl_notifier{};
};

#endif // QTWAYLANDINPUTMONITOR_HH
Loading

0 comments on commit 7ec8727

Please sign in to comment.