forked from quickshell/quickshell
		
	hyprland/focus_grab: add HyprlandFocusGrab
This commit is contained in:
		
							parent
							
								
									e7cfb5cf37
								
							
						
					
					
						commit
						87a884ca36
					
				
					 15 changed files with 607 additions and 0 deletions
				
			
		| 
						 | 
				
			
			@ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue