diff --git a/CMakeLists.txt b/CMakeLists.txt index f6bba915..2611fb5d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,6 +107,7 @@ if (LINUX) cmake_dependent_option(WITH_MVIZ "Build with music visualizer." ON "WITH_ANIMATIONS;WITH_GUI" OFF) cmake_dependent_option(USE_XCB "Build with features that require XCB." ON "WITH_GUI" OFF) cmake_dependent_option(USE_DBUS_MENU "Build with DBus support for the tray." ON "WITH_GUI" OFF) + cmake_dependent_option(USE_WAYLAND "Build with features that require Wayland." ON "WITH_GUI" OFF) option(USE_PORTAUDIO "Use portaudio for music visualizer." OFF) else () cmake_dependent_option(WITH_MVIZ "Build with music visualizer." OFF "WITH_ANIMATIONS;WITH_GUI" OFF) diff --git a/cmake/modules/FindWayland.cmake b/cmake/modules/FindWayland.cmake new file mode 100644 index 00000000..18a792a7 --- /dev/null +++ b/cmake/modules/FindWayland.cmake @@ -0,0 +1,208 @@ +# SPDX-FileCopyrightText: 2014 Alex Merry +# SPDX-FileCopyrightText: 2014 Martin Gräßlin +# SPDX-FileCopyrightText: 2025 Trần Nam Tuấn +# +# SPDX-License-Identifier: BSD-3-Clause + +#[=======================================================================[.rst: +FindWayland +----------- + +Try to find Wayland. + +This is a component-based find module, which makes use of the COMPONENTS +and OPTIONAL_COMPONENTS arguments to find_module. The following components +are available:: + + Client Server Cursor Egl + +If no components are specified, this module will act as though all components +were passed to OPTIONAL_COMPONENTS. + +This module will define the following variables, independently of the +components searched for or found: + +``Wayland_FOUND`` + TRUE if (the requested version of) Wayland is available +``Wayland_VERSION`` + Found Wayland version +``Wayland_TARGETS`` + A list of all targets imported by this module (note that there may be more + than the components that were requested) +``Wayland_LIBRARIES`` + This can be passed to target_link_libraries() instead of the imported + targets +``Wayland_INCLUDE_DIRS`` + This should be passed to target_include_directories() if the targets are + not used for linking +``Wayland_DEFINITIONS`` + This should be passed to target_compile_options() if the targets are not + used for linking +``Wayland_DATADIR`` + The core wayland protocols data directory + +For each searched-for components, ``Wayland__FOUND`` will be set to +TRUE if the corresponding Wayland library was found, and FALSE otherwise. If +``Wayland__FOUND`` is TRUE, the imported target +``Wayland::`` will be defined. This module will also attempt to +determine ``Wayland_*_VERSION`` variables for each imported target, although +``Wayland_VERSION`` should normally be sufficient. + +In general we recommend using the imported targets, as they are easier to use +and provide more control. Bear in mind, however, that if any target is in the +link interface of an exported library, it must be made available by the +package config file. +#]=======================================================================] + +include(FindPackageHandleStandardArgs) + +set(Wayland_known_components + Client + Server + Cursor + Egl +) + +foreach(_comp ${Wayland_known_components}) + string(TOLOWER "${_comp}" _lc_comp) + set(Wayland_${_comp}_component_deps) + set(Wayland_${_comp}_pkg_config "wayland-${_lc_comp}") + set(Wayland_${_comp}_lib "wayland-${_lc_comp}") + set(Wayland_${_comp}_header "wayland-${_lc_comp}.h") +endforeach() +set(Wayland_Egl_component_deps Client) + +find_package(PkgConfig REQUIRED) + +foreach(_comp ${Wayland_known_components}) + # Handling components dependencies + foreach(_dep ${Wayland_${_comp}_component_deps}) + list(APPEND _dep_vars "Wayland_${_dep}_FOUND") + list(APPEND _dep_targets "Wayland::${_dep}") + endforeach() + + pkg_check_modules(PKG_Wayland_${_comp} QUIET ${Wayland_${_comp}_pkg_config}) + + find_path(Wayland_${_comp}_INCLUDE_DIR + NAMES ${Wayland_${_comp}_header} + HINTS ${PKG_Wayland_${_comp}_INCLUDE_DIRS} + ) + + find_library(Wayland_${_comp}_LIBRARY + NAMES ${Wayland_${_comp}_lib} + HINTS ${PKG_Wayland_${_comp}_LIBRARY_DIRS} + ) + + set(Wayland_${_comp}_VERSION "${PKG_Wayland_${_comp}_VERSION}") + if(NOT Wayland_VERSION) + set(Wayland_VERSION ${Wayland_${_comp}_VERSION}) + endif() + + set(FPHSA_NAME_MISMATCHED 1) + find_package_handle_standard_args(Wayland_${_comp} + FOUND_VAR + Wayland_${_comp}_FOUND + REQUIRED_VARS + Wayland_${_comp}_LIBRARY + Wayland_${_comp}_INCLUDE_DIR + ${_dep_vars} + VERSION_VAR + Wayland_${_comp}_VERSION + ) + unset(FPHSA_NAME_MISMATCHED) + + mark_as_advanced( + Wayland_${_comp}_LIBRARY + Wayland_${_comp}_INCLUDE_DIR + ) + + if(Wayland_${_comp}_FOUND) + list(APPEND Wayland_LIBRARIES + "${Wayland_${_comp}_LIBRARY}") + list(APPEND Wayland_INCLUDE_DIRS + "${Wayland_${_comp}_INCLUDE_DIR}") + set(Wayland_DEFINITIONS + ${Wayland_DEFINITIONS} + ${PKG_Wayland_${_comp}_DEFINITIONS}) + if(NOT TARGET Wayland::${_comp}) + add_library(Wayland::${_comp} UNKNOWN IMPORTED) + set_target_properties(Wayland::${_comp} PROPERTIES + IMPORTED_LOCATION "${Wayland_${_comp}_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${PKG_Wayland_${_comp}_DEFINITIONS}" + INTERFACE_INCLUDE_DIRECTORIES "${Wayland_${_comp}_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES "${_dep_targets}" + ) + endif() + list(APPEND Wayland_TARGETS + "Wayland::${_comp}") + endif() +endforeach() + +if(Wayland_LIBRARIES) + list(REMOVE_DUPLICATES Wayland_LIBRARIES) +endif() + +if(Wayland_INCLUDE_DIRS) + list(REMOVE_DUPLICATES Wayland_INCLUDE_DIRS) +endif() + +if(Wayland_DEFINITIONS) + list(REMOVE_DUPLICATES Wayland_DEFINITIONS) +endif() + +if(Wayland_TARGETS) + list(REMOVE_DUPLICATES Wayland_TARGETS) +endif() + +# If pkg-config didn't provide us with version information, +# try to extract it from wayland-version.h +# (Note that the version from wayland-egl.pc will probably be +# the Mesa version, rather than the Wayland version, but that +# version will be ignored as we always find wayland-client.pc +# first). +if(NOT Wayland_VERSION) + find_file(Wayland_VERSION_HEADER + NAMES wayland-version.h + HINTS ${Wayland_INCLUDE_DIRS} + ) + mark_as_advanced(Wayland_VERSION_HEADER) + if(Wayland_VERSION_HEADER) + file(READ ${Wayland_VERSION_HEADER} _wayland_version_header_contents) + string(REGEX REPLACE + "^.*[ \t]+WAYLAND_VERSION[ \t]+\"([0-9.]*)\".*$" + "\\1" + Wayland_VERSION + "${_wayland_version_header_contents}" + ) + unset(_wayland_version_header_contents) + endif() +endif() + +find_package_handle_standard_args(Wayland + FOUND_VAR + Wayland_FOUND + REQUIRED_VARS + Wayland_LIBRARIES + VERSION_VAR + Wayland_VERSION + HANDLE_COMPONENTS +) + +pkg_get_variable(Wayland_DATADIR wayland-scanner pkgdatadir) +if (CMAKE_CROSSCOMPILING AND (NOT EXISTS "${Wayland_DATADIR}/wayland.xml")) + # PKG_CONFIG_SYSROOT_DIR only applies to -I and -L flags, so pkg-config + # does not prepend CMAKE_SYSROOT when cross-compiling unless you pass + # --define-prefix explicitly. Therefore we have to manually do prepend + # it here when cross-compiling. + # See https://gitlab.kitware.com/cmake/cmake/-/issues/16647#note_844761 + set(Wayland_DATADIR ${CMAKE_SYSROOT}${Wayland_DATADIR}) +endif() +if (NOT EXISTS "${Wayland_DATADIR}/wayland.xml") + message(WARNING "Could not find wayland.xml in ${Wayland_DATADIR}") +endif() + +include(FeatureSummary) +set_package_properties(Wayland PROPERTIES + URL "https://wayland.freedesktop.org/" + DESCRIPTION "C library implementation of the Wayland protocol: a protocol for a compositor to talk to its clients" +) diff --git a/cmake/modules/FindWaylandProtocols.cmake b/cmake/modules/FindWaylandProtocols.cmake new file mode 100644 index 00000000..a4449885 --- /dev/null +++ b/cmake/modules/FindWaylandProtocols.cmake @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: 2019 Vlad Zahorodnii +# +# SPDX-License-Identifier: BSD-3-Clause + +#[=======================================================================[.rst: +FindWaylandProtocols +-------------------- + +Try to find wayland-protocols on a Unix system. + +This will define the following variables: + +``WaylandProtocols_FOUND`` + True if (the requested version of) wayland-protocols is available +``WaylandProtocols_VERSION`` + The version of wayland-protocols +``WaylandProtocols_DATADIR`` + The wayland protocols data directory +#]=======================================================================] + +find_package(PkgConfig QUIET) +pkg_check_modules(PKG_wayland_protocols QUIET wayland-protocols) + +set(WaylandProtocols_VERSION ${PKG_wayland_protocols_VERSION}) +pkg_get_variable(WaylandProtocols_DATADIR wayland-protocols pkgdatadir) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(WaylandProtocols + FOUND_VAR WaylandProtocols_FOUND + REQUIRED_VARS WaylandProtocols_DATADIR + VERSION_VAR WaylandProtocols_VERSION +) + +include(FeatureSummary) +set_package_properties(WaylandProtocols PROPERTIES + DESCRIPTION "Specifications of extended Wayland protocols" + URL "https://wayland.freedesktop.org/" +) diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 4b1ec385..26b6fa0a 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -411,6 +411,30 @@ elseif (LINUX) wayland/waylandutils.cpp wayland/waylandutils.h ) + + if (USE_WAYLAND) + find_package(Wayland COMPONENTS Client REQUIRED) + + target_compile_definitions( + ckb-next + PRIVATE + USE_WAYLAND + ) + + target_link_libraries( + ckb-next + PRIVATE + Wayland::Client + ) + + if (Qt6_VERSION VERSION_LESS "6.5.0") + target_link_libraries( + ckb-next + PRIVATE + Qt${QT_VERSION_MAJOR}::GuiPrivate # For QPlatformNativeInterface. Qt 6.5 and later has QNativeInterface::QWaylandApplication. + ) + endif () + endif () endif () if (MAC_LEGACY) diff --git a/src/gui/wayland/waylandutils.cpp b/src/gui/wayland/waylandutils.cpp index 94f1be0f..f4b0b39e 100644 --- a/src/gui/wayland/waylandutils.cpp +++ b/src/gui/wayland/waylandutils.cpp @@ -2,6 +2,22 @@ #include +#ifdef USE_WAYLAND +#include +#if QT_VERSION < QT_VERSION_CHECK(6, 5 ,0) +#include +#endif + +#include +#include + +#if QT_VERSION >= QT_VERSION_CHECK(6, 3 ,0) +#include +#else +#include +#endif +#endif + namespace WaylandUtils { @@ -22,4 +38,93 @@ bool isWayland() return false; } +#ifdef USE_WAYLAND +struct RegistryData { + QString interfaceName; + quint32 version; +}; + +class WaylandUtilsHelper { +public: + wl_display* m_display = nullptr; + wl_registry* m_registry = nullptr; + wl_seat *m_seat = nullptr; + + QList m_registerData; + + WaylandUtilsHelper() + { +#if QT_VERSION < QT_VERSION_CHECK(6, 5 ,0) + QPlatformNativeInterface *nativeInterface = qGuiApp->platformNativeInterface(); + if (!nativeInterface) { + return; + } + + m_display = static_cast(nativeInterface->nativeResourceForIntegration("wl_display")); + if (!m_display) { + return; + } + + m_seat = static_cast(nativeInterface->nativeResourceForIntegration("m_seat")); + if (!m_seat) { + m_display = nullptr; + return; + } +#else + auto nativeInterface = qGuiApp->nativeInterface(); + if (!nativeInterface) { + return; + } + + m_display = nativeInterface->display(); + m_seat = nativeInterface->seat(); +#endif + + m_registry = wl_display_get_registry(m_display); + if (!m_registry) { + return; + } + + auto global_handler = [](void* data_ptr, wl_registry*, uint32_t name, const char* interface, uint32_t version) { + auto registerData = static_cast*>(data_ptr); + registerData->append({QString::fromLatin1(interface), version}); + }; + + auto global_remover_handler = [](void*, wl_registry*, uint32_t) { }; + + wl_registry_listener registry_listener = { + global_handler, + global_remover_handler + }; + + wl_registry_add_listener(m_registry, ®istry_listener, &m_registerData); + + wl_display_dispatch(m_display); + wl_display_roundtrip(m_display); + } +}; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 3 ,0) +Q_APPLICATION_STATIC(WaylandUtilsHelper, g_helper,) +#else +Q_GLOBAL_STATIC(WaylandUtilsHelper, g_helper) +#endif + +bool hasInterface(const QString &name, quint32 version) +{ + auto it = std::find_if(g_helper->m_registerData.constBegin(), g_helper->m_registerData.constEnd(), [&name, &version](const RegistryData& data){ + return data.interfaceName == name && data.version == version; + }); + return it != g_helper->m_registerData.constEnd(); +} + +wl_display *display() { + return g_helper->m_display; +} + +wl_seat *seat() { + return g_helper->m_seat; +} +#endif + } // WaylandUtils diff --git a/src/gui/wayland/waylandutils.h b/src/gui/wayland/waylandutils.h index 12769e14..c5b31025 100644 --- a/src/gui/wayland/waylandutils.h +++ b/src/gui/wayland/waylandutils.h @@ -3,11 +3,23 @@ #include +#ifdef USE_WAYLAND +class wl_display; +class wl_seat; +#endif + namespace WaylandUtils { bool isWayland(); +#ifdef USE_WAYLAND +bool hasInterface(const QString &name, quint32 version); + +wl_display *display(); +wl_seat *seat(); +#endif + } // WaylandUtils #endif // WAYLANDUTILS_HPP