forked from quickshell/quickshell
hyprland/focus_grab: add HyprlandFocusGrab
This commit is contained in:
parent
e7cfb5cf37
commit
87a884ca36
|
@ -14,6 +14,7 @@ option(SOCKETS "Enable unix socket support" ON)
|
||||||
option(WAYLAND "Enable wayland support" ON)
|
option(WAYLAND "Enable wayland support" ON)
|
||||||
option(WAYLAND_WLR_LAYERSHELL "Support the zwlr_layer_shell_v1 wayland protocol" ON)
|
option(WAYLAND_WLR_LAYERSHELL "Support the zwlr_layer_shell_v1 wayland protocol" ON)
|
||||||
option(WAYLAND_SESSION_LOCK "Support the ext_session_lock_v1 wayland protocol" ON)
|
option(WAYLAND_SESSION_LOCK "Support the ext_session_lock_v1 wayland protocol" ON)
|
||||||
|
option(HYPRLAND "Support hyprland specific features" ON)
|
||||||
option(SERVICE_STATUS_NOTIFIER "StatusNotifierItem service" ON)
|
option(SERVICE_STATUS_NOTIFIER "StatusNotifierItem service" ON)
|
||||||
|
|
||||||
message(STATUS "Quickshell configuration")
|
message(STATUS "Quickshell configuration")
|
||||||
|
@ -27,6 +28,7 @@ if (WAYLAND)
|
||||||
endif ()
|
endif ()
|
||||||
message(STATUS " Services")
|
message(STATUS " Services")
|
||||||
message(STATUS " StatusNotifier: ${SERVICE_STATUS_NOTIFIER}")
|
message(STATUS " StatusNotifier: ${SERVICE_STATUS_NOTIFIER}")
|
||||||
|
message(STATUS " Hyprland: ${HYPRLAND}")
|
||||||
|
|
||||||
if (NOT DEFINED GIT_REVISION)
|
if (NOT DEFINED GIT_REVISION)
|
||||||
execute_process(
|
execute_process(
|
||||||
|
|
|
@ -88,6 +88,7 @@ void ProxyWindowBase::createWindow() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProxyWindowBase::deleteWindow() {
|
void ProxyWindowBase::deleteWindow() {
|
||||||
|
if (this->window != nullptr) emit this->windowDestroyed();
|
||||||
if (auto* window = this->disownWindow()) {
|
if (auto* window = this->disownWindow()) {
|
||||||
if (auto* generation = EngineGeneration::findObjectGeneration(this)) {
|
if (auto* generation = EngineGeneration::findObjectGeneration(this)) {
|
||||||
generation->deregisterIncubationController(window->incubationController());
|
generation->deregisterIncubationController(window->incubationController());
|
||||||
|
|
|
@ -102,6 +102,7 @@ public:
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void windowConnected();
|
void windowConnected();
|
||||||
|
void windowDestroyed();
|
||||||
void visibleChanged();
|
void visibleChanged();
|
||||||
void backerVisibilityChanged();
|
void backerVisibilityChanged();
|
||||||
void xChanged();
|
void xChanged();
|
||||||
|
|
|
@ -68,6 +68,10 @@ if (WAYLAND_SESSION_LOCK)
|
||||||
add_subdirectory(session_lock)
|
add_subdirectory(session_lock)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (HYPRLAND)
|
||||||
|
add_subdirectory(hyprland)
|
||||||
|
endif()
|
||||||
|
|
||||||
target_link_libraries(quickshell-wayland PRIVATE ${QT_DEPS})
|
target_link_libraries(quickshell-wayland PRIVATE ${QT_DEPS})
|
||||||
target_link_libraries(quickshell-wayland-init PRIVATE ${QT_DEPS})
|
target_link_libraries(quickshell-wayland-init PRIVATE ${QT_DEPS})
|
||||||
|
|
||||||
|
|
11
src/wayland/hyprland/CMakeLists.txt
Normal file
11
src/wayland/hyprland/CMakeLists.txt
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
qt_add_library(quickshell-hyprland STATIC)
|
||||||
|
qt_add_qml_module(quickshell-hyprland URI Quickshell.Hyprland VERSION 0.1)
|
||||||
|
|
||||||
|
add_subdirectory(focus_grab)
|
||||||
|
|
||||||
|
target_link_libraries(quickshell-hyprland PRIVATE ${QT_DEPS})
|
||||||
|
|
||||||
|
qs_pch(quickshell-hyprland)
|
||||||
|
qs_pch(quickshell-hyprlandplugin)
|
||||||
|
|
||||||
|
target_link_libraries(quickshell PRIVATE quickshell-hyprlandplugin)
|
19
src/wayland/hyprland/focus_grab/CMakeLists.txt
Normal file
19
src/wayland/hyprland/focus_grab/CMakeLists.txt
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
qt_add_library(quickshell-hyprland-focus-grab STATIC
|
||||||
|
manager.cpp
|
||||||
|
grab.cpp
|
||||||
|
qml.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
qt_add_qml_module(quickshell-hyprland-focus-grab URI Quickshell.Hyprland._FocusGrab VERSION 0.1)
|
||||||
|
|
||||||
|
add_library(quickshell-hyprland-focus-grab-init OBJECT init.cpp)
|
||||||
|
|
||||||
|
wl_proto(quickshell-hyprland-focus-grab hyprland-focus-grab-v1 "${CMAKE_CURRENT_SOURCE_DIR}/hyprland-focus-grab-v1.xml")
|
||||||
|
target_link_libraries(quickshell-hyprland-focus-grab PRIVATE ${QT_DEPS} wayland-client)
|
||||||
|
target_link_libraries(quickshell-hyprland-focus-grab-init PRIVATE ${QT_DEPS})
|
||||||
|
|
||||||
|
qs_pch(quickshell-hyprland-focus-grab)
|
||||||
|
qs_pch(quickshell-hyprland-focus-grabplugin)
|
||||||
|
qs_pch(quickshell-hyprland-focus-grab-init)
|
||||||
|
|
||||||
|
target_link_libraries(quickshell PRIVATE quickshell-hyprland-focus-grabplugin quickshell-hyprland-focus-grab-init)
|
78
src/wayland/hyprland/focus_grab/grab.cpp
Normal file
78
src/wayland/hyprland/focus_grab/grab.cpp
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
#include "grab.hpp"
|
||||||
|
|
||||||
|
#include <private/qwaylandwindow_p.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
#include <qwindow.h>
|
||||||
|
#include <wayland-hyprland-focus-grab-v1-client-protocol.h>
|
||||||
|
|
||||||
|
namespace qs::hyprland::focus_grab {
|
||||||
|
|
||||||
|
FocusGrab::FocusGrab(::hyprland_focus_grab_v1* grab) { this->init(grab); }
|
||||||
|
|
||||||
|
FocusGrab::~FocusGrab() {
|
||||||
|
if (this->isInitialized()) {
|
||||||
|
this->destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FocusGrab::isActive() const { return this->active; }
|
||||||
|
|
||||||
|
void FocusGrab::addWindow(QWindow* window) {
|
||||||
|
if (auto* waylandWindow = dynamic_cast<QWaylandWindow*>(window->handle())) {
|
||||||
|
this->addWaylandWindow(waylandWindow);
|
||||||
|
} else {
|
||||||
|
QObject::connect(window, &QWindow::visibleChanged, this, [this, window]() {
|
||||||
|
if (window->isVisible()) {
|
||||||
|
if (window->handle() == nullptr) {
|
||||||
|
window->create();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* waylandWindow = dynamic_cast<QWaylandWindow*>(window->handle());
|
||||||
|
this->addWaylandWindow(waylandWindow);
|
||||||
|
this->sync();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FocusGrab::removeWindow(QWindow* window) {
|
||||||
|
QObject::disconnect(window, nullptr, this, nullptr);
|
||||||
|
|
||||||
|
if (auto* waylandWindow = dynamic_cast<QWaylandWindow*>(window->handle())) {
|
||||||
|
this->pendingAdditions.removeAll(waylandWindow);
|
||||||
|
this->remove_surface(waylandWindow->surface());
|
||||||
|
this->commitRequired = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FocusGrab::addWaylandWindow(QWaylandWindow* window) {
|
||||||
|
this->add_surface(window->surface());
|
||||||
|
this->pendingAdditions.append(window);
|
||||||
|
this->commitRequired = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FocusGrab::sync() {
|
||||||
|
if (this->commitRequired) {
|
||||||
|
this->commit();
|
||||||
|
this->commitRequired = false;
|
||||||
|
|
||||||
|
// the protocol will always send cleared() when the grab is deactivated,
|
||||||
|
// even if it was due to window destruction, so we don't need to track it.
|
||||||
|
if (!this->pendingAdditions.isEmpty()) {
|
||||||
|
this->pendingAdditions.clear();
|
||||||
|
|
||||||
|
if (!this->active) {
|
||||||
|
this->active = true;
|
||||||
|
emit this->activated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FocusGrab::hyprland_focus_grab_v1_cleared() {
|
||||||
|
this->active = false;
|
||||||
|
emit this->cleared();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace qs::hyprland::focus_grab
|
46
src/wayland/hyprland/focus_grab/grab.hpp
Normal file
46
src/wayland/hyprland/focus_grab/grab.hpp
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <private/qwaylandwindow_p.h>
|
||||||
|
#include <qlist.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qtclasshelpermacros.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
#include <qwayland-hyprland-focus-grab-v1.h>
|
||||||
|
#include <qwindow.h>
|
||||||
|
#include <wayland-hyprland-focus-grab-v1-client-protocol.h>
|
||||||
|
|
||||||
|
namespace qs::hyprland::focus_grab {
|
||||||
|
using HyprlandFocusGrab = QtWayland::hyprland_focus_grab_v1;
|
||||||
|
|
||||||
|
class FocusGrab
|
||||||
|
: public QObject
|
||||||
|
, public HyprlandFocusGrab {
|
||||||
|
using QWaylandWindow = QtWaylandClient::QWaylandWindow;
|
||||||
|
|
||||||
|
Q_OBJECT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit FocusGrab(::hyprland_focus_grab_v1* grab);
|
||||||
|
~FocusGrab() override;
|
||||||
|
Q_DISABLE_COPY_MOVE(FocusGrab);
|
||||||
|
|
||||||
|
[[nodiscard]] bool isActive() const;
|
||||||
|
void addWindow(QWindow* window);
|
||||||
|
void removeWindow(QWindow* window);
|
||||||
|
void sync();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void activated();
|
||||||
|
void cleared();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void hyprland_focus_grab_v1_cleared() override;
|
||||||
|
|
||||||
|
void addWaylandWindow(QWaylandWindow* window);
|
||||||
|
|
||||||
|
QList<QWaylandWindow*> pendingAdditions;
|
||||||
|
bool commitRequired = false;
|
||||||
|
bool active = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace qs::hyprland::focus_grab
|
128
src/wayland/hyprland/focus_grab/hyprland-focus-grab-v1.xml
Normal file
128
src/wayland/hyprland/focus_grab/hyprland-focus-grab-v1.xml
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<protocol name="hyprland_focus_grab_v1">
|
||||||
|
<copyright>
|
||||||
|
Copyright © 2024 outfoxxed
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
</copyright>
|
||||||
|
|
||||||
|
<description summary="limit input focus to a set of surfaces">
|
||||||
|
This protocol allows clients to limit input focus to a specific set
|
||||||
|
of surfaces and receive a notification when the limiter is removed as
|
||||||
|
detailed below.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<interface name="hyprland_focus_grab_manager_v1" version="1">
|
||||||
|
<description summary="manager for focus grab objects">
|
||||||
|
This interface allows a client to create surface grab objects.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<request name="create_grab">
|
||||||
|
<description summary="create a focus grab object">
|
||||||
|
Create a surface grab object.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<arg name="grab" type="new_id" interface="hyprland_focus_grab_v1"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor">
|
||||||
|
<description summary="destroy the focus grab manager">
|
||||||
|
Destroy the focus grab manager.
|
||||||
|
This doesn't destroy existing focus grab objects.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
</interface>
|
||||||
|
|
||||||
|
<interface name="hyprland_focus_grab_v1" version="1">
|
||||||
|
<description summary="input focus limiter">
|
||||||
|
This interface restricts input focus to a specified whitelist of
|
||||||
|
surfaces as long as the focus grab object exists and has at least
|
||||||
|
one comitted surface.
|
||||||
|
|
||||||
|
Mouse and touch events inside a whitelisted surface will be passed
|
||||||
|
to the surface normally, while events outside of a whitelisted surface
|
||||||
|
will clear the grab object. Keyboard events will be passed to the client
|
||||||
|
and a compositor-picked surface in the whitelist will receive a
|
||||||
|
wl_keyboard::enter event if a whitelisted surface is not already entered.
|
||||||
|
|
||||||
|
Upon meeting implementation-defined criteria usually meaning a mouse or
|
||||||
|
touch input outside of any whitelisted surfaces, the compositor will
|
||||||
|
clear the whitelist, rendering the grab inert and sending the cleared
|
||||||
|
event. The same will happen if another focus grab or similar action
|
||||||
|
is started at the compositor's discretion.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<request name="add_surface">
|
||||||
|
<description summary="add a surface to the focus whitelist">
|
||||||
|
Add a surface to the whitelist. Destroying the surface is treated the
|
||||||
|
same as an explicit call to remove_surface and duplicate additions are
|
||||||
|
ignored.
|
||||||
|
|
||||||
|
Does not take effect until commit is called.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<arg name="surface" type="object" interface="wl_surface"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="remove_surface">
|
||||||
|
<description summary="remove a surface from the focus whitelist">
|
||||||
|
Remove a surface from the whitelist. Destroying the surface is treated
|
||||||
|
the same as an explicit call to this function.
|
||||||
|
|
||||||
|
If the grab was active and the removed surface was entered by the
|
||||||
|
keyboard, another surface will be entered on commit.
|
||||||
|
|
||||||
|
Does not take effect until commit is called.
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<arg name="surface" type="object" interface="wl_surface"/>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="commit">
|
||||||
|
<description summary="commit the focus whitelist">
|
||||||
|
Commit pending changes to the surface whitelist.
|
||||||
|
|
||||||
|
If the list previously had no entries and now has at least one, the grab
|
||||||
|
will start. If it previously had entries and now has none, the grab will
|
||||||
|
become inert.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<request name="destroy" type="destructor">
|
||||||
|
<description summary="destroy the focus grab">
|
||||||
|
Destroy the grab object and remove the grab if active.
|
||||||
|
</description>
|
||||||
|
</request>
|
||||||
|
|
||||||
|
<event name="cleared">
|
||||||
|
<description summary="the focus grab was cleared">
|
||||||
|
Sent when an active grab is cancelled by the compositor,
|
||||||
|
regardless of cause.
|
||||||
|
</description>
|
||||||
|
</event>
|
||||||
|
</interface>
|
||||||
|
</protocol>
|
20
src/wayland/hyprland/focus_grab/init.cpp
Normal file
20
src/wayland/hyprland/focus_grab/init.cpp
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
#include <qqml.h>
|
||||||
|
|
||||||
|
#include "../../../core/plugin.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class HyprlandFocusGrabPlugin: public QuickshellPlugin {
|
||||||
|
void registerTypes() override {
|
||||||
|
qmlRegisterModuleImport(
|
||||||
|
"Quickshell.Hyprland",
|
||||||
|
QQmlModuleImportModuleAny,
|
||||||
|
"Quickshell.Hyprland._FocusGrab",
|
||||||
|
QQmlModuleImportLatest
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QS_REGISTER_PLUGIN(HyprlandFocusGrabPlugin);
|
||||||
|
|
||||||
|
} // namespace
|
27
src/wayland/hyprland/focus_grab/manager.cpp
Normal file
27
src/wayland/hyprland/focus_grab/manager.cpp
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#include "manager.hpp"
|
||||||
|
|
||||||
|
#include <qwaylandclientextension.h>
|
||||||
|
|
||||||
|
#include "grab.hpp"
|
||||||
|
|
||||||
|
namespace qs::hyprland::focus_grab {
|
||||||
|
|
||||||
|
FocusGrabManager::FocusGrabManager(): QWaylandClientExtensionTemplate<FocusGrabManager>(1) {
|
||||||
|
this->initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FocusGrabManager::available() const { return this->isActive(); }
|
||||||
|
|
||||||
|
FocusGrab* FocusGrabManager::createGrab() { return new FocusGrab(this->create_grab()); }
|
||||||
|
|
||||||
|
FocusGrabManager* FocusGrabManager::instance() {
|
||||||
|
static FocusGrabManager* instance = nullptr; // NOLINT
|
||||||
|
|
||||||
|
if (instance == nullptr) {
|
||||||
|
instance = new FocusGrabManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace qs::hyprland::focus_grab
|
22
src/wayland/hyprland/focus_grab/manager.hpp
Normal file
22
src/wayland/hyprland/focus_grab/manager.hpp
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qwayland-hyprland-focus-grab-v1.h>
|
||||||
|
#include <qwaylandclientextension.h>
|
||||||
|
|
||||||
|
namespace qs::hyprland::focus_grab {
|
||||||
|
using HyprlandFocusGrabManager = QtWayland::hyprland_focus_grab_manager_v1;
|
||||||
|
class FocusGrab;
|
||||||
|
|
||||||
|
class FocusGrabManager
|
||||||
|
: public QWaylandClientExtensionTemplate<FocusGrabManager>
|
||||||
|
, public HyprlandFocusGrabManager {
|
||||||
|
public:
|
||||||
|
explicit FocusGrabManager();
|
||||||
|
|
||||||
|
[[nodiscard]] bool available() const;
|
||||||
|
[[nodiscard]] FocusGrab* createGrab();
|
||||||
|
|
||||||
|
static FocusGrabManager* instance();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace qs::hyprland::focus_grab
|
131
src/wayland/hyprland/focus_grab/qml.cpp
Normal file
131
src/wayland/hyprland/focus_grab/qml.cpp
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
#include "qml.hpp"
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <qlist.h>
|
||||||
|
#include <qlogging.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qqmllist.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
#include <qwindow.h>
|
||||||
|
|
||||||
|
#include "../../../core/proxywindow.hpp"
|
||||||
|
#include "../../../core/windowinterface.hpp"
|
||||||
|
#include "grab.hpp"
|
||||||
|
#include "manager.hpp"
|
||||||
|
|
||||||
|
namespace qs::hyprland {
|
||||||
|
using focus_grab::FocusGrab;
|
||||||
|
using focus_grab::FocusGrabManager;
|
||||||
|
|
||||||
|
void HyprlandFocusGrab::componentComplete() { this->tryActivate(); }
|
||||||
|
|
||||||
|
bool HyprlandFocusGrab::isActive() const { return this->grab != nullptr && this->grab->isActive(); }
|
||||||
|
|
||||||
|
void HyprlandFocusGrab::setActive(bool active) {
|
||||||
|
if (active == this->targetActive) return;
|
||||||
|
this->targetActive = active;
|
||||||
|
|
||||||
|
if (!active) {
|
||||||
|
delete this->grab;
|
||||||
|
this->grab = nullptr;
|
||||||
|
emit this->activeChanged();
|
||||||
|
} else {
|
||||||
|
this->tryActivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QObjectList HyprlandFocusGrab::windows() const { return this->windowObjects; }
|
||||||
|
|
||||||
|
void HyprlandFocusGrab::setWindows(QObjectList windows) {
|
||||||
|
if (windows == this->windowObjects) return;
|
||||||
|
this->windowObjects = std::move(windows);
|
||||||
|
this->syncWindows();
|
||||||
|
emit this->windowsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HyprlandFocusGrab::onGrabActivated() { emit this->activeChanged(); }
|
||||||
|
|
||||||
|
void HyprlandFocusGrab::onGrabCleared() {
|
||||||
|
emit this->cleared();
|
||||||
|
this->setActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HyprlandFocusGrab::onProxyConnected() {
|
||||||
|
if (this->grab != nullptr) {
|
||||||
|
this->grab->addWindow(qobject_cast<ProxyWindowBase*>(this->sender())->backingWindow());
|
||||||
|
this->grab->sync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HyprlandFocusGrab::tryActivate() {
|
||||||
|
if (!this->targetActive || this->isActive()) return;
|
||||||
|
|
||||||
|
auto* manager = FocusGrabManager::instance();
|
||||||
|
if (!manager->isActive()) {
|
||||||
|
qWarning() << "The active compositor does not support the hyprland_focus_grab_v1 protocol. "
|
||||||
|
"HyprlandFocusGrab will not work.";
|
||||||
|
qWarning() << "** Learn why $XDG_CURRENT_DESKTOP sucks and download a better compositor "
|
||||||
|
"today at https://hyprland.org";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->grab = manager->createGrab();
|
||||||
|
this->grab->setParent(this);
|
||||||
|
QObject::connect(this->grab, &FocusGrab::activated, this, &HyprlandFocusGrab::onGrabActivated);
|
||||||
|
QObject::connect(this->grab, &FocusGrab::cleared, this, &HyprlandFocusGrab::onGrabCleared);
|
||||||
|
|
||||||
|
for (auto* proxy: this->trackedProxies) {
|
||||||
|
if (proxy->backingWindow() != nullptr) {
|
||||||
|
this->grab->addWindow(proxy->backingWindow());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->grab->sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HyprlandFocusGrab::syncWindows() {
|
||||||
|
auto newProxy = QList<ProxyWindowBase*>();
|
||||||
|
for (auto* windowObject: this->windowObjects) {
|
||||||
|
auto* proxyWindow = qobject_cast<ProxyWindowBase*>(windowObject);
|
||||||
|
|
||||||
|
if (proxyWindow == nullptr) {
|
||||||
|
if (auto* iface = qobject_cast<WindowInterface*>(windowObject)) {
|
||||||
|
proxyWindow = iface->proxyWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proxyWindow != nullptr) {
|
||||||
|
newProxy.push_back(proxyWindow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto* oldWindow: this->trackedProxies) {
|
||||||
|
if (!newProxy.contains(oldWindow)) {
|
||||||
|
QObject::disconnect(oldWindow, nullptr, this, nullptr);
|
||||||
|
|
||||||
|
if (this->grab != nullptr && oldWindow->backingWindow() != nullptr) {
|
||||||
|
this->grab->removeWindow(oldWindow->backingWindow());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto* newProxy: newProxy) {
|
||||||
|
if (!this->trackedProxies.contains(newProxy)) {
|
||||||
|
QObject::connect(
|
||||||
|
newProxy,
|
||||||
|
&ProxyWindowBase::windowConnected,
|
||||||
|
this,
|
||||||
|
&HyprlandFocusGrab::onProxyConnected
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this->grab != nullptr && newProxy->backingWindow() != nullptr) {
|
||||||
|
this->grab->addWindow(newProxy->backingWindow());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->trackedProxies = newProxy;
|
||||||
|
if (this->grab != nullptr) this->grab->sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace qs::hyprland
|
111
src/wayland/hyprland/focus_grab/qml.hpp
Normal file
111
src/wayland/hyprland/focus_grab/qml.hpp
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qlist.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qqmlintegration.h>
|
||||||
|
#include <qqmllist.h>
|
||||||
|
#include <qqmlparserstatus.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
#include <qtypes.h>
|
||||||
|
#include <qwindow.h>
|
||||||
|
|
||||||
|
class ProxyWindowBase;
|
||||||
|
|
||||||
|
namespace qs::hyprland {
|
||||||
|
|
||||||
|
namespace focus_grab {
|
||||||
|
class FocusGrab;
|
||||||
|
}
|
||||||
|
|
||||||
|
///! Input focus grabber
|
||||||
|
/// Object for managing input focus grabs via the [hyprland_focus_grab_v1]
|
||||||
|
/// wayland protocol.
|
||||||
|
///
|
||||||
|
/// When enabled, all of the windows listed in the `windows` property will
|
||||||
|
/// receive input normally, and will retain keyboard focus even if the mouse
|
||||||
|
/// is moved off of them. When areas of the screen that are not part of a listed
|
||||||
|
/// window are clicked or touched, the grab will become inactive and emit the
|
||||||
|
/// cleared signal.
|
||||||
|
///
|
||||||
|
/// This is useful for implementing dismissal of popup type windows.
|
||||||
|
/// ```qml
|
||||||
|
/// import Quickshell
|
||||||
|
/// import Quickshell.Hyprland
|
||||||
|
/// import QtQuick.Controls
|
||||||
|
///
|
||||||
|
/// ShellRoot {
|
||||||
|
/// FloatingWindow {
|
||||||
|
/// id: window
|
||||||
|
///
|
||||||
|
/// Button {
|
||||||
|
/// anchors.centerIn: parent
|
||||||
|
/// text: grab.active ? "Remove exclusive focus" : "Take exclusive focus"
|
||||||
|
/// onClicked: grab.active = !grab.active
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// HyprlandFocusGrab {
|
||||||
|
/// id: grab
|
||||||
|
/// windows: [ window ]
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [hyprland_focus_grab_v1]: https://github.com/hyprwm/hyprland-protocols/blob/main/protocols/hyprland-global-shortcuts-v1.xml
|
||||||
|
class HyprlandFocusGrab
|
||||||
|
: public QObject
|
||||||
|
, public QQmlParserStatus {
|
||||||
|
Q_OBJECT;
|
||||||
|
/// If the focus grab is active. Defaults to false.
|
||||||
|
///
|
||||||
|
/// When set to true, an input grab will be created for the listed windows.
|
||||||
|
///
|
||||||
|
/// This property will change to false once the grab is dismissed.
|
||||||
|
/// It will not change to true until the grab begins, which requires
|
||||||
|
/// at least one visible window.
|
||||||
|
Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged);
|
||||||
|
/// The list of windows to whitelist for input.
|
||||||
|
Q_PROPERTY(QList<QObject*> windows READ windows WRITE setWindows NOTIFY windowsChanged);
|
||||||
|
QML_ELEMENT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit HyprlandFocusGrab(QObject* parent = nullptr): QObject(parent) {}
|
||||||
|
|
||||||
|
void classBegin() override {}
|
||||||
|
void componentComplete() override;
|
||||||
|
|
||||||
|
[[nodiscard]] bool isActive() const;
|
||||||
|
void setActive(bool active);
|
||||||
|
|
||||||
|
[[nodiscard]] QObjectList windows() const;
|
||||||
|
void setWindows(QObjectList windows);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
/// Sent whenever the compositor clears the focus grab.
|
||||||
|
///
|
||||||
|
/// This may be in response to all windows being removed
|
||||||
|
/// from the list or simultaneously hidden, in addition to
|
||||||
|
/// a normal clear.
|
||||||
|
void cleared();
|
||||||
|
|
||||||
|
void activeChanged();
|
||||||
|
void windowsChanged();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onGrabActivated();
|
||||||
|
void onGrabCleared();
|
||||||
|
void onProxyConnected();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void tryActivate();
|
||||||
|
void syncWindows();
|
||||||
|
|
||||||
|
bool targetActive = false;
|
||||||
|
QObjectList windowObjects;
|
||||||
|
QList<ProxyWindowBase*> trackedProxies;
|
||||||
|
QList<QWindow*> trackedWindows;
|
||||||
|
|
||||||
|
focus_grab::FocusGrab* grab = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
}; // namespace qs::hyprland
|
6
src/wayland/hyprland/module.md
Normal file
6
src/wayland/hyprland/module.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
name = "Quickshell.Hyprland"
|
||||||
|
description = "Hyprland specific Quickshell types"
|
||||||
|
headers = [
|
||||||
|
"focus_grab/qml.hpp"
|
||||||
|
]
|
||||||
|
-----
|
Loading…
Reference in a new issue