Skip to content

Commit 6705e2d

Browse files
committed
wm: add WindowManager module with ext-workspace support
1 parent 9e8eecf commit 6705e2d

22 files changed

Lines changed: 1337 additions & 0 deletions

changelog/next.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ set shell id.
2626
- Added Quickshell version checking and version gated preprocessing.
2727
- Added a way to detect if an icon is from the system icon theme or not.
2828
- Added vulkan support to screencopy.
29+
- Added generic WindowManager interface implementing ext-workspace.
2930

3031
## Other Changes
3132

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ add_subdirectory(window)
1111
add_subdirectory(io)
1212
add_subdirectory(widgets)
1313
add_subdirectory(ui)
14+
add_subdirectory(windowmanager)
1415

1516
if (CRASH_HANDLER)
1617
add_subdirectory(crash)

src/wayland/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ list(APPEND WAYLAND_MODULES Quickshell.Wayland._IdleNotify)
123123
add_subdirectory(shortcuts_inhibit)
124124
list(APPEND WAYLAND_MODULES Quickshell.Wayland._ShortcutsInhibitor)
125125

126+
add_subdirectory(windowmanager)
127+
126128
# widgets for qmenu
127129
target_link_libraries(quickshell-wayland PRIVATE
128130
Qt::Quick Qt::Widgets Qt::WaylandClient Qt::WaylandClientPrivate
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
qt_add_library(quickshell-wayland-windowsystem STATIC
2+
windowmanager.cpp
3+
windowset.cpp
4+
ext_workspace.cpp
5+
)
6+
7+
add_library(quickshell-wayland-windowsystem-init OBJECT init.cpp)
8+
target_link_libraries(quickshell-wayland-windowsystem-init PRIVATE Qt::Quick)
9+
10+
wl_proto(wlp-ext-workspace ext-workspace-v1 "${WAYLAND_PROTOCOLS}/staging/ext-workspace")
11+
12+
target_link_libraries(quickshell-wayland-windowsystem PRIVATE
13+
Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client
14+
wlp-ext-workspace
15+
)
16+
17+
qs_pch(quickshell-wayland-windowsystem SET large)
18+
19+
target_link_libraries(quickshell PRIVATE quickshell-wayland-windowsystem quickshell-wayland-windowsystem-init)
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
#include "ext_workspace.hpp"
2+
3+
#include <qcontainerfwd.h>
4+
#include <qlogging.h>
5+
#include <qloggingcategory.h>
6+
#include <qtmetamacros.h>
7+
#include <qtypes.h>
8+
#include <qwayland-ext-workspace-v1.h>
9+
#include <qwaylandclientextension.h>
10+
#include <wayland-ext-workspace-v1-client-protocol.h>
11+
#include <wayland-util.h>
12+
13+
#include "../../core/logcat.hpp"
14+
15+
namespace qs::wayland::workspace {
16+
17+
QS_LOGGING_CATEGORY(logWorkspace, "quickshell.wm.wayland.workspace", QtWarningMsg);
18+
19+
WorkspaceManager::WorkspaceManager(): QWaylandClientExtensionTemplate(1) { this->initialize(); }
20+
21+
WorkspaceManager* WorkspaceManager::instance() {
22+
static auto* instance = new WorkspaceManager();
23+
return instance;
24+
}
25+
26+
void WorkspaceManager::ext_workspace_manager_v1_workspace_group(
27+
::ext_workspace_group_handle_v1* handle
28+
) {
29+
auto* group = new WorkspaceGroup(handle);
30+
qCDebug(logWorkspace) << "Created group" << group;
31+
this->mGroups.insert(handle, group);
32+
emit this->groupCreated(group);
33+
}
34+
35+
void WorkspaceManager::ext_workspace_manager_v1_workspace(::ext_workspace_handle_v1* handle) {
36+
auto* workspace = new Workspace(handle);
37+
qCDebug(logWorkspace) << "Created workspace" << workspace;
38+
this->mWorkspaces.insert(handle, workspace);
39+
emit this->workspaceCreated(workspace);
40+
};
41+
42+
void WorkspaceManager::destroyWorkspace(Workspace* workspace) {
43+
this->mWorkspaces.remove(workspace->object());
44+
this->destroyedWorkspaces.append(workspace);
45+
emit this->workspaceDestroyed(workspace);
46+
}
47+
48+
void WorkspaceManager::destroyGroup(WorkspaceGroup* group) {
49+
this->mGroups.remove(group->object());
50+
this->destroyedGroups.append(group);
51+
emit this->groupDestroyed(group);
52+
}
53+
54+
void WorkspaceManager::ext_workspace_manager_v1_done() {
55+
qCDebug(logWorkspace) << "Workspace changes done";
56+
emit this->serverCommit();
57+
58+
for (auto* workspace: this->destroyedWorkspaces) delete workspace;
59+
for (auto* group: this->destroyedGroups) delete group;
60+
this->destroyedWorkspaces.clear();
61+
this->destroyedGroups.clear();
62+
}
63+
64+
void WorkspaceManager::ext_workspace_manager_v1_finished() {
65+
qCWarning(logWorkspace) << "ext_workspace_manager_v1.finished() was received";
66+
}
67+
68+
Workspace::~Workspace() {
69+
if (this->isInitialized()) this->destroy();
70+
}
71+
72+
void Workspace::ext_workspace_handle_v1_id(const QString& id) {
73+
qCDebug(logWorkspace) << "Updated id for workspace" << this << "to" << id;
74+
this->id = id;
75+
}
76+
77+
void Workspace::ext_workspace_handle_v1_name(const QString& name) {
78+
qCDebug(logWorkspace) << "Updated name for workspace" << this << "to" << name;
79+
this->name = name;
80+
}
81+
82+
void Workspace::ext_workspace_handle_v1_coordinates(wl_array* coordinates) {
83+
this->coordinates.clear();
84+
85+
auto* data = static_cast<qint32*>(coordinates->data);
86+
auto size = static_cast<qsizetype>(coordinates->size / sizeof(qint32));
87+
88+
for (auto i = 0; i != size; ++i) {
89+
this->coordinates.append(data[i]); // NOLINT
90+
}
91+
92+
qCDebug(logWorkspace) << "Updated coordinates for workspace" << this << "to" << this->coordinates;
93+
}
94+
95+
void Workspace::ext_workspace_handle_v1_state(quint32 state) {
96+
this->active = state & ext_workspace_handle_v1::state_active;
97+
this->urgent = state & ext_workspace_handle_v1::state_urgent;
98+
this->hidden = state & ext_workspace_handle_v1::state_hidden;
99+
100+
qCDebug(logWorkspace).nospace() << "Updated state for workspace " << this
101+
<< " to [active: " << this->active << ", urgent: " << this->urgent
102+
<< ", hidden: " << this->hidden << ']';
103+
}
104+
105+
void Workspace::ext_workspace_handle_v1_capabilities(quint32 capabilities) {
106+
this->canActivate = capabilities & ext_workspace_handle_v1::workspace_capabilities_activate;
107+
this->canDeactivate = capabilities & ext_workspace_handle_v1::workspace_capabilities_deactivate;
108+
this->canRemove = capabilities & ext_workspace_handle_v1::workspace_capabilities_remove;
109+
this->canAssign = capabilities & ext_workspace_handle_v1::workspace_capabilities_assign;
110+
111+
qCDebug(logWorkspace).nospace() << "Updated capabilities for workspace " << this
112+
<< " to [activate: " << this->canActivate
113+
<< ", deactivate: " << this->canDeactivate
114+
<< ", remove: " << this->canRemove
115+
<< ", assign: " << this->canAssign << ']';
116+
}
117+
118+
void Workspace::ext_workspace_handle_v1_removed() {
119+
qCDebug(logWorkspace) << "Destroyed workspace" << this;
120+
WorkspaceManager::instance()->destroyWorkspace(this);
121+
this->destroy();
122+
}
123+
124+
void Workspace::enterGroup(WorkspaceGroup* group) { this->group = group; }
125+
126+
void Workspace::leaveGroup(WorkspaceGroup* group) {
127+
if (this->group == group) this->group = nullptr;
128+
}
129+
130+
WorkspaceGroup::~WorkspaceGroup() {
131+
if (this->isInitialized()) this->destroy();
132+
}
133+
134+
void WorkspaceGroup::ext_workspace_group_handle_v1_capabilities(quint32 capabilities) {
135+
this->canCreateWorkspace =
136+
capabilities & ext_workspace_group_handle_v1::group_capabilities_create_workspace;
137+
138+
qCDebug(logWorkspace).nospace() << "Updated capabilities for group " << this
139+
<< " to [create_workspace: " << this->canCreateWorkspace << ']';
140+
}
141+
142+
void WorkspaceGroup::ext_workspace_group_handle_v1_output_enter(::wl_output* output) {
143+
qCDebug(logWorkspace) << "Output" << output << "added to group" << this;
144+
this->screens.addOutput(output);
145+
}
146+
147+
void WorkspaceGroup::ext_workspace_group_handle_v1_output_leave(::wl_output* output) {
148+
qCDebug(logWorkspace) << "Output" << output << "removed from group" << this;
149+
this->screens.removeOutput(output);
150+
}
151+
152+
void WorkspaceGroup::ext_workspace_group_handle_v1_workspace_enter(
153+
::ext_workspace_handle_v1* handle
154+
) {
155+
auto* workspace = WorkspaceManager::instance()->mWorkspaces.value(handle);
156+
qCDebug(logWorkspace) << "Workspace" << workspace << "added to group" << this;
157+
158+
if (workspace) workspace->enterGroup(this);
159+
}
160+
161+
void WorkspaceGroup::ext_workspace_group_handle_v1_workspace_leave(
162+
::ext_workspace_handle_v1* handle
163+
) {
164+
auto* workspace = WorkspaceManager::instance()->mWorkspaces.value(handle);
165+
qCDebug(logWorkspace) << "Workspace" << workspace << "removed from group" << this;
166+
167+
if (workspace) workspace->leaveGroup(this);
168+
}
169+
170+
void WorkspaceGroup::ext_workspace_group_handle_v1_removed() {
171+
qCDebug(logWorkspace) << "Destroyed group" << this;
172+
WorkspaceManager::instance()->destroyGroup(this);
173+
this->destroy();
174+
}
175+
176+
} // namespace qs::wayland::workspace
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#pragma once
2+
3+
#include <qcontainerfwd.h>
4+
#include <qlist.h>
5+
#include <qloggingcategory.h>
6+
#include <qscreen.h>
7+
#include <qtclasshelpermacros.h>
8+
#include <qtmetamacros.h>
9+
#include <qtypes.h>
10+
#include <qwayland-ext-workspace-v1.h>
11+
#include <qwaylandclientextension.h>
12+
#include <wayland-ext-workspace-v1-client-protocol.h>
13+
14+
#include "../../core/logcat.hpp"
15+
#include "../output_tracking.hpp"
16+
17+
namespace qs::wayland::workspace {
18+
19+
QS_DECLARE_LOGGING_CATEGORY(logWorkspace);
20+
21+
class WorkspaceGroup;
22+
class Workspace;
23+
24+
class WorkspaceManager
25+
: public QWaylandClientExtensionTemplate<WorkspaceManager>
26+
, public QtWayland::ext_workspace_manager_v1 {
27+
Q_OBJECT;
28+
29+
public:
30+
static WorkspaceManager* instance();
31+
32+
[[nodiscard]] QList<Workspace*> workspaces() { return this->mWorkspaces.values(); }
33+
34+
signals:
35+
void serverCommit();
36+
void workspaceCreated(Workspace* workspace);
37+
void workspaceDestroyed(Workspace* workspace);
38+
void groupCreated(WorkspaceGroup* group);
39+
void groupDestroyed(WorkspaceGroup* group);
40+
41+
protected:
42+
void ext_workspace_manager_v1_workspace_group(::ext_workspace_group_handle_v1* handle) override;
43+
void ext_workspace_manager_v1_workspace(::ext_workspace_handle_v1* handle) override;
44+
void ext_workspace_manager_v1_done() override;
45+
void ext_workspace_manager_v1_finished() override;
46+
47+
private:
48+
WorkspaceManager();
49+
50+
void destroyGroup(WorkspaceGroup* group);
51+
void destroyWorkspace(Workspace* workspace);
52+
53+
QHash<::ext_workspace_handle_v1*, Workspace*> mWorkspaces;
54+
QHash<::ext_workspace_group_handle_v1*, WorkspaceGroup*> mGroups;
55+
QList<WorkspaceGroup*> destroyedGroups;
56+
QList<Workspace*> destroyedWorkspaces;
57+
58+
friend class Workspace;
59+
friend class WorkspaceGroup;
60+
};
61+
62+
class Workspace: public QtWayland::ext_workspace_handle_v1 {
63+
public:
64+
Workspace(::ext_workspace_handle_v1* handle): QtWayland::ext_workspace_handle_v1(handle) {}
65+
~Workspace() override;
66+
Q_DISABLE_COPY_MOVE(Workspace);
67+
68+
QString id;
69+
QString name;
70+
QList<qint32> coordinates;
71+
WorkspaceGroup* group = nullptr;
72+
73+
bool active : 1 = false;
74+
bool urgent : 1 = false;
75+
bool hidden : 1 = false;
76+
77+
bool canActivate : 1 = false;
78+
bool canDeactivate : 1 = false;
79+
bool canRemove : 1 = false;
80+
bool canAssign : 1 = false;
81+
82+
protected:
83+
void ext_workspace_handle_v1_id(const QString& id) override;
84+
void ext_workspace_handle_v1_name(const QString& name) override;
85+
void ext_workspace_handle_v1_coordinates(wl_array* coordinates) override;
86+
void ext_workspace_handle_v1_state(quint32 state) override;
87+
void ext_workspace_handle_v1_capabilities(quint32 capabilities) override;
88+
void ext_workspace_handle_v1_removed() override;
89+
90+
private:
91+
void enterGroup(WorkspaceGroup* group);
92+
void leaveGroup(WorkspaceGroup* group);
93+
94+
friend class WorkspaceGroup;
95+
};
96+
97+
class WorkspaceGroup: public QtWayland::ext_workspace_group_handle_v1 {
98+
public:
99+
WorkspaceGroup(::ext_workspace_group_handle_v1* handle)
100+
: QtWayland::ext_workspace_group_handle_v1(handle) {}
101+
102+
~WorkspaceGroup() override;
103+
Q_DISABLE_COPY_MOVE(WorkspaceGroup);
104+
105+
WlOutputTracker screens;
106+
bool canCreateWorkspace : 1 = false;
107+
108+
protected:
109+
void ext_workspace_group_handle_v1_capabilities(quint32 capabilities) override;
110+
void ext_workspace_group_handle_v1_output_enter(::wl_output* output) override;
111+
void ext_workspace_group_handle_v1_output_leave(::wl_output* output) override;
112+
void ext_workspace_group_handle_v1_workspace_enter(::ext_workspace_handle_v1* handle) override;
113+
void ext_workspace_group_handle_v1_workspace_leave(::ext_workspace_handle_v1* handle) override;
114+
void ext_workspace_group_handle_v1_removed() override;
115+
};
116+
117+
} // namespace qs::wayland::workspace

src/wayland/windowmanager/init.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include <qcontainerfwd.h>
2+
#include <qguiapplication.h>
3+
#include <qlist.h>
4+
5+
#include "../../core/plugin.hpp"
6+
7+
namespace qs::wm::wayland {
8+
void installWmProvider();
9+
}
10+
11+
namespace {
12+
13+
class WaylandWmPlugin: public QsEnginePlugin {
14+
QList<QString> dependencies() override { return {"window"}; }
15+
16+
bool applies() override { return QGuiApplication::platformName() == "wayland"; }
17+
18+
void init() override { qs::wm::wayland::installWmProvider(); }
19+
};
20+
21+
QS_REGISTER_PLUGIN(WaylandWmPlugin);
22+
23+
} // namespace
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#include "windowmanager.hpp"
2+
3+
#include "../../windowmanager/windowmanager.hpp"
4+
#include "windowset.hpp"
5+
6+
namespace qs::wm::wayland {
7+
8+
WaylandWindowManager* WaylandWindowManager::instance() {
9+
static auto* instance = []() {
10+
auto* wm = new WaylandWindowManager();
11+
WindowsetManager::instance();
12+
return wm;
13+
}();
14+
return instance;
15+
}
16+
17+
void installWmProvider() { // NOLINT (misc-use-internal-linkage)
18+
qs::wm::WindowManager::setProvider([]() { return WaylandWindowManager::instance(); });
19+
}
20+
21+
} // namespace qs::wm::wayland

0 commit comments

Comments
 (0)