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_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(HYPRLAND "Support hyprland specific features" ON)
|
||||
option(SERVICE_STATUS_NOTIFIER "StatusNotifierItem service" ON)
|
||||
|
||||
message(STATUS "Quickshell configuration")
|
||||
|
@ -27,6 +28,7 @@ if (WAYLAND)
|
|||
endif ()
|
||||
message(STATUS " Services")
|
||||
message(STATUS " StatusNotifier: ${SERVICE_STATUS_NOTIFIER}")
|
||||
message(STATUS " Hyprland: ${HYPRLAND}")
|
||||
|
||||
if (NOT DEFINED GIT_REVISION)
|
||||
execute_process(
|
||||
|
|
|
@ -88,6 +88,7 @@ void ProxyWindowBase::createWindow() {
|
|||
}
|
||||
|
||||
void ProxyWindowBase::deleteWindow() {
|
||||
if (this->window != nullptr) emit this->windowDestroyed();
|
||||
if (auto* window = this->disownWindow()) {
|
||||
if (auto* generation = EngineGeneration::findObjectGeneration(this)) {
|
||||
generation->deregisterIncubationController(window->incubationController());
|
||||
|
|
|
@ -102,6 +102,7 @@ public:
|
|||
|
||||
signals:
|
||||
void windowConnected();
|
||||
void windowDestroyed();
|
||||
void visibleChanged();
|
||||
void backerVisibilityChanged();
|
||||
void xChanged();
|
||||
|
|
|
@ -68,6 +68,10 @@ if (WAYLAND_SESSION_LOCK)
|
|||
add_subdirectory(session_lock)
|
||||
endif()
|
||||
|
||||
if (HYPRLAND)
|
||||
add_subdirectory(hyprland)
|
||||
endif()
|
||||
|
||||
target_link_libraries(quickshell-wayland 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