feat(compositor): migrate workspace tracking from niri IPC to ext-workspace protocol

This commit is contained in:
Lysec
2026-04-03 16:11:12 +02:00
parent 539b398944
commit d411809715
8 changed files with 225 additions and 437 deletions
+23 -1
View File
@@ -47,6 +47,8 @@ set(XDG_OUTPUT_XML
"${WAYLAND_PROTOCOLS_PKGDATADIR}/unstable/xdg-output/xdg-output-unstable-v1.xml")
set(XDG_SHELL_XML
"${WAYLAND_PROTOCOLS_PKGDATADIR}/stable/xdg-shell/xdg-shell.xml")
set(EXT_WORKSPACE_XML
"${WAYLAND_PROTOCOLS_PKGDATADIR}/staging/ext-workspace/ext-workspace-v1.xml")
set(XDG_OUTPUT_PROTOCOL_C
"${GENERATED_PROTOCOL_DIR}/xdg-output-unstable-v1-client-protocol.c")
@@ -56,6 +58,10 @@ set(XDG_SHELL_PROTOCOL_C
"${GENERATED_PROTOCOL_DIR}/xdg-shell-client-protocol.c")
set(XDG_SHELL_PROTOCOL_H
"${GENERATED_PROTOCOL_DIR}/xdg-shell-client-protocol.h")
set(EXT_WORKSPACE_PROTOCOL_C
"${GENERATED_PROTOCOL_DIR}/ext-workspace-v1-client-protocol.c")
set(EXT_WORKSPACE_PROTOCOL_H
"${GENERATED_PROTOCOL_DIR}/ext-workspace-v1-client-protocol.h")
set(WLR_LAYER_SHELL_PROTOCOL_C
"${GENERATED_PROTOCOL_DIR}/wlr-layer-shell-unstable-v1-client-protocol.c")
@@ -116,6 +122,20 @@ add_custom_command(
VERBATIM
)
add_custom_command(
OUTPUT "${EXT_WORKSPACE_PROTOCOL_C}"
COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" private-code "${EXT_WORKSPACE_XML}" "${EXT_WORKSPACE_PROTOCOL_C}"
DEPENDS "${EXT_WORKSPACE_XML}"
VERBATIM
)
add_custom_command(
OUTPUT "${EXT_WORKSPACE_PROTOCOL_H}"
COMMAND "${WAYLAND_SCANNER_EXECUTABLE}" client-header "${EXT_WORKSPACE_XML}" "${EXT_WORKSPACE_PROTOCOL_H}"
DEPENDS "${EXT_WORKSPACE_XML}"
VERBATIM
)
add_custom_target(noctalia_wayland_protocols
DEPENDS
"${WLR_LAYER_SHELL_PROTOCOL_C}"
@@ -124,6 +144,8 @@ add_custom_target(noctalia_wayland_protocols
"${XDG_OUTPUT_PROTOCOL_H}"
"${XDG_SHELL_PROTOCOL_C}"
"${XDG_SHELL_PROTOCOL_H}"
"${EXT_WORKSPACE_PROTOCOL_C}"
"${EXT_WORKSPACE_PROTOCOL_H}"
)
# --- Target ---
@@ -131,7 +153,6 @@ add_executable(noctalia
src/main.cpp
src/app/Application.cpp
src/app/MainLoop.cpp
src/compositor/niri/NiriCompositorService.cpp
src/system/SystemMonitorService.cpp
src/debug/DebugService.cpp
src/notification/InternalNotificationService.cpp
@@ -161,6 +182,7 @@ add_executable(noctalia
"${WLR_LAYER_SHELL_PROTOCOL_C}"
"${XDG_OUTPUT_PROTOCOL_C}"
"${XDG_SHELL_PROTOCOL_C}"
"${EXT_WORKSPACE_PROTOCOL_C}"
)
target_compile_definitions(noctalia PRIVATE NOCTALIA_HAVE_WLR_LAYER_SHELL=1)
+1 -1
View File
@@ -106,7 +106,7 @@ Everything else depends on these.
### Phase 2 -- Minimum viable bar
- [x] Compositor integration (Niri)
- [x] Compositor integration (ext-workspace)
- [ ] Compositor integration (Hyprland)
- [ ] Workspaces widget
- [ ] Clock widget
-10
View File
@@ -45,16 +45,6 @@ void Application::run() {
m_systemMonitor.reset();
}
try {
m_niriCompositor = std::make_unique<NiriService>();
if (m_niriCompositor->isRunning()) {
logInfo("niri compositor integration active");
}
} catch (const std::exception& e) {
logWarn("niri compositor integration disabled: {}", e.what());
m_niriCompositor.reset();
}
try {
m_bus = std::make_unique<SessionBus>();
logInfo("connected to session bus");
-2
View File
@@ -1,7 +1,6 @@
#pragma once
#include "app/MainLoop.hpp"
#include "compositor/niri/NiriCompositorService.hpp"
#include "debug/DebugService.hpp"
#include "dbus/SessionBus.hpp"
#include "dbus/mpris/MprisService.hpp"
@@ -27,7 +26,6 @@ private:
Bar m_bar;
std::unique_ptr<SessionBus> m_bus;
std::unique_ptr<SystemMonitorService> m_systemMonitor;
std::unique_ptr<NiriService> m_niriCompositor;
std::unique_ptr<DebugService> m_debugService;
std::unique_ptr<MprisService> m_mprisService;
NotificationManager m_manager;
@@ -1,374 +0,0 @@
#include "compositor/niri/NiriCompositorService.hpp"
#include "core/Log.hpp"
#include <array>
#include <cctype>
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <optional>
#include <string_view>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
namespace {
constexpr std::string_view k_event_stream_request = "\"EventStream\"\n";
} // namespace
NiriService::NiriService() {
if (!connectSocket()) {
return;
}
startReader();
}
NiriService::~NiriService() {
stopReader();
}
bool NiriService::isRunning() const noexcept {
return m_running.load();
}
bool NiriService::connectSocket() {
const char* socket_path_env = std::getenv("NIRI_SOCKET");
if (socket_path_env == nullptr || socket_path_env[0] == '\0') {
logInfo("niri integration disabled: NIRI_SOCKET is not set");
return false;
}
const std::string socket_path{socket_path_env};
if (socket_path.size() >= sizeof(sockaddr_un::sun_path)) {
logWarn("niri integration disabled: NIRI_SOCKET path is too long");
return false;
}
const int fd = ::socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
logWarn("niri integration disabled: failed to create unix socket: {}", std::strerror(errno));
return false;
}
sockaddr_un addr{};
addr.sun_family = AF_UNIX;
std::strncpy(addr.sun_path, socket_path.c_str(), sizeof(addr.sun_path) - 1);
if (::connect(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
logWarn("niri integration disabled: failed to connect to {}: {}", socket_path, std::strerror(errno));
::close(fd);
return false;
}
const ssize_t wrote = ::send(fd,
k_event_stream_request.data(),
k_event_stream_request.size(),
MSG_NOSIGNAL);
if (wrote != static_cast<ssize_t>(k_event_stream_request.size())) {
logWarn("niri integration disabled: failed to request event stream");
::close(fd);
return false;
}
m_socket_fd = fd;
logInfo("niri integration connected (event stream)");
return true;
}
void NiriService::startReader() {
if (m_socket_fd < 0 || m_running.load()) {
return;
}
m_running = true;
m_reader_thread = std::thread([this]() {
readerLoop();
});
}
void NiriService::stopReader() {
m_running = false;
if (m_socket_fd >= 0) {
::shutdown(m_socket_fd, SHUT_RDWR);
::close(m_socket_fd);
m_socket_fd = -1;
}
if (m_reader_thread.joinable()) {
m_reader_thread.join();
}
}
void NiriService::readerLoop() {
std::string line;
while (m_running.load() && readLine(m_socket_fd, line)) {
if (line.empty()) {
continue;
}
handleEventLine(line);
}
if (m_running.load()) {
logWarn("niri event stream closed");
}
}
bool NiriService::readLine(int fd, std::string& out) {
out.clear();
const auto emit_from_buffer = [&]() -> bool {
const std::size_t newline_pos = m_read_buffer.find('\n');
if (newline_pos == std::string::npos) {
return false;
}
out = m_read_buffer.substr(0, newline_pos);
m_read_buffer.erase(0, newline_pos + 1);
return true;
};
if (emit_from_buffer()) {
return true;
}
std::array<char, 256> buffer{};
while (true) {
const ssize_t n = ::recv(fd, buffer.data(), buffer.size(), 0);
if (n == 0) {
if (!m_read_buffer.empty()) {
out = std::move(m_read_buffer);
m_read_buffer.clear();
return true;
}
return false;
}
if (n < 0) {
if (errno == EINTR) {
continue;
}
return false;
}
m_read_buffer.append(buffer.data(), static_cast<std::size_t>(n));
if (emit_from_buffer()) {
return true;
}
}
}
void NiriService::handleEventLine(const std::string& line) {
if (line.find("\"Err\"") != std::string::npos) {
logWarn("niri event stream returned error: {}", line);
return;
}
if (line.find("\"WorkspacesChanged\"") != std::string::npos) {
handleWorkspacesChanged(line);
return;
}
if (line.find("\"WorkspaceActivated\"") != std::string::npos) {
handleWorkspaceActivated(line);
return;
}
}
void NiriService::handleWorkspacesChanged(const std::string& line) {
const std::size_t key_pos = line.find("\"workspaces\"");
if (key_pos == std::string::npos) {
return;
}
const std::size_t array_open = line.find('[', key_pos);
if (array_open == std::string::npos) {
return;
}
const std::size_t array_close = findMatchingBracket(line, array_open);
if (array_close == std::string::npos) {
return;
}
m_workspace_labels.clear();
std::optional<std::uint64_t> focused_id;
std::size_t cursor = array_open + 1;
while (cursor < array_close) {
const std::size_t obj_open = line.find('{', cursor);
if (obj_open == std::string::npos || obj_open >= array_close) {
break;
}
const std::size_t obj_close = findMatchingBrace(line, obj_open);
if (obj_close == std::string::npos || obj_close > array_close) {
break;
}
const auto id = parseJsonUintField(line, obj_open, "\"id\"");
const auto idx = parseJsonUintField(line, obj_open, "\"idx\"");
const auto name = parseJsonStringField(line, obj_open, "\"name\"");
const auto output = parseJsonStringField(line, obj_open, "\"output\"");
if (id.has_value()) {
std::string label;
if (name.has_value() && !name->empty()) {
label = *name;
} else if (idx.has_value()) {
label = std::format("{}", *idx);
} else {
label = std::format("id {}", *id);
}
if (output.has_value() && !output->empty()) {
label = std::format("{}@{}", label, *output);
}
m_workspace_labels[*id] = std::move(label);
}
const std::size_t focused_pos = line.find("\"is_focused\"", obj_open);
if (focused_pos != std::string::npos && focused_pos < obj_close) {
const std::size_t true_pos = line.find("true", focused_pos);
if (true_pos != std::string::npos && true_pos < obj_close && id.has_value()) {
focused_id = *id;
}
}
cursor = obj_close + 1;
}
if (focused_id.has_value()) {
logFocusedWorkspace(*focused_id);
}
}
void NiriService::handleWorkspaceActivated(const std::string& line) {
const std::size_t focused_pos = line.find("\"focused\"");
if (focused_pos == std::string::npos) {
return;
}
const std::size_t true_pos = line.find("true", focused_pos);
if (true_pos == std::string::npos) {
return;
}
const auto id = parseJsonUintField(line, 0, "\"id\"");
if (id.has_value()) {
logFocusedWorkspace(*id);
}
}
std::optional<std::string> NiriService::parseJsonStringField(const std::string& input,
std::size_t from,
const char* key) {
const std::size_t key_pos = input.find(key, from);
if (key_pos == std::string::npos) {
return std::nullopt;
}
const std::size_t colon = input.find(':', key_pos);
if (colon == std::string::npos) {
return std::nullopt;
}
std::size_t first = input.find_first_not_of(" \t", colon + 1);
if (first == std::string::npos) {
return std::nullopt;
}
if (input.compare(first, 4, "null") == 0) {
return std::string{};
}
if (input[first] != '"') {
return std::nullopt;
}
++first;
const std::size_t end = input.find('"', first);
if (end == std::string::npos) {
return std::nullopt;
}
return input.substr(first, end - first);
}
std::optional<std::uint64_t> NiriService::parseJsonUintField(const std::string& input,
std::size_t from,
const char* key) {
const std::size_t key_pos = input.find(key, from);
if (key_pos == std::string::npos) {
return std::nullopt;
}
const std::size_t colon = input.find(':', key_pos);
if (colon == std::string::npos) {
return std::nullopt;
}
std::size_t first = input.find_first_not_of(" \t", colon + 1);
if (first == std::string::npos || first >= input.size() || !std::isdigit(static_cast<unsigned char>(input[first]))) {
return std::nullopt;
}
std::size_t last = first;
while (last < input.size() && std::isdigit(static_cast<unsigned char>(input[last]))) {
++last;
}
try {
return static_cast<std::uint64_t>(std::stoull(input.substr(first, last - first)));
} catch (...) {
return std::nullopt;
}
}
std::size_t NiriService::findMatchingBracket(const std::string& input, std::size_t open_pos) {
int depth = 0;
for (std::size_t i = open_pos; i < input.size(); ++i) {
if (input[i] == '[') {
++depth;
} else if (input[i] == ']') {
--depth;
if (depth == 0) {
return i;
}
}
}
return std::string::npos;
}
std::size_t NiriService::findMatchingBrace(const std::string& input, std::size_t open_pos) {
int depth = 0;
for (std::size_t i = open_pos; i < input.size(); ++i) {
if (input[i] == '{') {
++depth;
} else if (input[i] == '}') {
--depth;
if (depth == 0) {
return i;
}
}
}
return std::string::npos;
}
void NiriService::logFocusedWorkspace(std::uint64_t id) {
if (m_last_focused_workspace.has_value() && *m_last_focused_workspace == id) {
return;
}
m_last_focused_workspace = id;
const auto it = m_workspace_labels.find(id);
if (it != m_workspace_labels.end()) {
logInfo("niri focused workspace {}", it->second);
return;
}
logInfo("niri focused workspace id {}", id);
}
@@ -1,48 +0,0 @@
#pragma once
#include <atomic>
#include <cstdint>
#include <optional>
#include <string>
#include <thread>
#include <unordered_map>
class NiriService {
public:
NiriService();
~NiriService();
NiriService(const NiriService&) = delete;
NiriService& operator=(const NiriService&) = delete;
[[nodiscard]] bool isRunning() const noexcept;
private:
bool connectSocket();
void startReader();
void stopReader();
void readerLoop();
bool readLine(int fd, std::string& out);
void handleEventLine(const std::string& line);
void handleWorkspacesChanged(const std::string& line);
void handleWorkspaceActivated(const std::string& line);
static std::optional<std::string> parseJsonStringField(const std::string& input,
std::size_t from,
const char* key);
static std::optional<std::uint64_t> parseJsonUintField(const std::string& input,
std::size_t from,
const char* key);
static std::size_t findMatchingBracket(const std::string& input, std::size_t open_pos);
static std::size_t findMatchingBrace(const std::string& input, std::size_t open_pos);
void logFocusedWorkspace(std::uint64_t id);
int m_socket_fd{-1};
std::atomic<bool> m_running{false};
std::thread m_reader_thread;
std::string m_read_buffer;
std::unordered_map<std::uint64_t, std::string> m_workspace_labels;
std::optional<std::uint64_t> m_last_focused_workspace;
};
+175 -1
View File
@@ -10,6 +10,7 @@
#if NOCTALIA_HAVE_WLR_LAYER_SHELL
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
#endif
#include "ext-workspace-v1-client-protocol.h"
#include "xdg-output-unstable-v1-client-protocol.h"
namespace {
@@ -21,6 +22,7 @@ constexpr std::uint32_t kShmVersion = 1;
constexpr std::uint32_t kLayerShellVersion = 4;
#endif
constexpr std::uint32_t kXdgOutputManagerVersion = 3;
constexpr std::uint32_t kExtWorkspaceManagerVersion = 1;
constexpr std::uint32_t kOutputVersion = 4;
const wl_registry_listener kRegistryListener = {
@@ -79,6 +81,83 @@ const wl_output_listener kOutputListener = {
.description = outputDescription,
};
void workspaceGroupCapabilities(void* /*data*/, ext_workspace_group_handle_v1* /*group*/, uint32_t /*caps*/) {}
void workspaceGroupOutputEnter(void* /*data*/, ext_workspace_group_handle_v1* /*group*/, wl_output* /*output*/) {}
void workspaceGroupOutputLeave(void* /*data*/, ext_workspace_group_handle_v1* /*group*/, wl_output* /*output*/) {}
void workspaceGroupWorkspaceEnter(void* /*data*/, ext_workspace_group_handle_v1* /*group*/, ext_workspace_handle_v1* /*workspace*/) {}
void workspaceGroupWorkspaceLeave(void* /*data*/, ext_workspace_group_handle_v1* /*group*/, ext_workspace_handle_v1* /*workspace*/) {}
void workspaceGroupRemoved(void* data, ext_workspace_group_handle_v1* group) {
auto* self = static_cast<WaylandConnection*>(data);
self->onWorkspaceGroupRemoved(group);
}
const ext_workspace_group_handle_v1_listener kWorkspaceGroupListener = {
.capabilities = workspaceGroupCapabilities,
.output_enter = workspaceGroupOutputEnter,
.output_leave = workspaceGroupOutputLeave,
.workspace_enter = workspaceGroupWorkspaceEnter,
.workspace_leave = workspaceGroupWorkspaceLeave,
.removed = workspaceGroupRemoved,
};
void workspaceId(void* /*data*/, ext_workspace_handle_v1* /*workspace*/, const char* /*id*/) {}
void workspaceName(void* data, ext_workspace_handle_v1* workspace, const char* name) {
auto* self = static_cast<WaylandConnection*>(data);
self->onWorkspaceNameChanged(workspace, name);
}
void workspaceCoordinates(void* /*data*/, ext_workspace_handle_v1* /*workspace*/, wl_array* /*coords*/) {}
void workspaceState(void* data, ext_workspace_handle_v1* workspace, uint32_t state) {
auto* self = static_cast<WaylandConnection*>(data);
self->onWorkspaceStateChanged(workspace, state);
}
void workspaceCapabilities(void* /*data*/, ext_workspace_handle_v1* /*workspace*/, uint32_t /*caps*/) {}
void workspaceRemoved(void* data, ext_workspace_handle_v1* workspace) {
auto* self = static_cast<WaylandConnection*>(data);
self->onWorkspaceRemoved(workspace);
}
const ext_workspace_handle_v1_listener kWorkspaceListener = {
.id = workspaceId,
.name = workspaceName,
.coordinates = workspaceCoordinates,
.state = workspaceState,
.capabilities = workspaceCapabilities,
.removed = workspaceRemoved,
};
void workspaceManagerWorkspaceGroup(void* data,
ext_workspace_manager_v1* /*manager*/,
ext_workspace_group_handle_v1* group) {
auto* self = static_cast<WaylandConnection*>(data);
self->onWorkspaceGroupCreated(group);
}
void workspaceManagerWorkspace(void* data,
ext_workspace_manager_v1* /*manager*/,
ext_workspace_handle_v1* workspace) {
auto* self = static_cast<WaylandConnection*>(data);
self->onWorkspaceCreated(workspace);
}
void workspaceManagerDone(void* /*data*/, ext_workspace_manager_v1* /*manager*/) {}
void workspaceManagerFinished(void* data, ext_workspace_manager_v1* /*manager*/) {
auto* self = static_cast<WaylandConnection*>(data);
self->onWorkspaceManagerFinished();
}
const ext_workspace_manager_v1_listener kWorkspaceManagerListener = {
.workspace_group = workspaceManagerWorkspaceGroup,
.workspace = workspaceManagerWorkspace,
.done = workspaceManagerDone,
.finished = workspaceManagerFinished,
};
} // namespace
WaylandConnection::WaylandConnection() = default;
@@ -142,6 +221,10 @@ bool WaylandConnection::hasXdgOutputManager() const noexcept {
return m_xdgOutputManager != nullptr;
}
bool WaylandConnection::hasExtWorkspaceManager() const noexcept {
return m_workspaceManager != nullptr;
}
wl_display* WaylandConnection::display() const noexcept {
return m_display;
}
@@ -228,6 +311,15 @@ void WaylandConnection::bindGlobal(wl_registry* registry,
return;
}
if (interfaceName == ext_workspace_manager_v1_interface.name) {
m_hasExtWorkspaceGlobal = true;
const auto bindVersion = std::min(version, kExtWorkspaceManagerVersion);
m_workspaceManager = static_cast<ext_workspace_manager_v1*>(
wl_registry_bind(registry, name, &ext_workspace_manager_v1_interface, bindVersion));
ext_workspace_manager_v1_add_listener(m_workspaceManager, &kWorkspaceManagerListener, this);
return;
}
if (interfaceName == wl_output_interface.name) {
const auto bindVersion = std::min(version, kOutputVersion);
auto* output = static_cast<wl_output*>(
@@ -255,7 +347,87 @@ WaylandOutput* WaylandConnection::findOutputByWl(wl_output* wlOutput) {
return nullptr;
}
void WaylandConnection::onWorkspaceGroupCreated(ext_workspace_group_handle_v1* group) {
if (group == nullptr) {
return;
}
m_workspaceGroups.push_back(group);
ext_workspace_group_handle_v1_add_listener(group, &kWorkspaceGroupListener, this);
}
void WaylandConnection::onWorkspaceGroupRemoved(ext_workspace_group_handle_v1* group) {
std::erase(m_workspaceGroups, group);
if (group != nullptr) {
ext_workspace_group_handle_v1_destroy(group);
}
}
void WaylandConnection::onWorkspaceCreated(ext_workspace_handle_v1* workspace) {
if (workspace == nullptr) {
return;
}
m_workspaces.emplace(workspace, TrackedWorkspace{});
ext_workspace_handle_v1_add_listener(workspace, &kWorkspaceListener, this);
}
void WaylandConnection::onWorkspaceNameChanged(ext_workspace_handle_v1* workspace, const char* name) {
const auto it = m_workspaces.find(workspace);
if (it == m_workspaces.end()) {
return;
}
it->second.name = name != nullptr ? name : "";
}
void WaylandConnection::onWorkspaceStateChanged(ext_workspace_handle_v1* workspace, std::uint32_t state) {
const auto it = m_workspaces.find(workspace);
if (it == m_workspaces.end()) {
return;
}
const bool is_active = (state & EXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE) != 0;
if (it->second.active != is_active) {
it->second.active = is_active;
if (is_active) {
const std::string label = it->second.name.empty() ? "(unnamed)" : it->second.name;
logInfo("workspace active: {}", label);
}
}
}
void WaylandConnection::onWorkspaceRemoved(ext_workspace_handle_v1* workspace) {
m_workspaces.erase(workspace);
if (workspace != nullptr) {
ext_workspace_handle_v1_destroy(workspace);
}
}
void WaylandConnection::onWorkspaceManagerFinished() {
m_workspaceManager = nullptr;
m_workspaces.clear();
m_workspaceGroups.clear();
}
void WaylandConnection::cleanup() {
for (auto& [workspace, _] : m_workspaces) {
if (workspace != nullptr) {
ext_workspace_handle_v1_destroy(workspace);
}
}
m_workspaces.clear();
for (auto* group : m_workspaceGroups) {
if (group != nullptr) {
ext_workspace_group_handle_v1_destroy(group);
}
}
m_workspaceGroups.clear();
if (m_workspaceManager != nullptr) {
ext_workspace_manager_v1_stop(m_workspaceManager);
ext_workspace_manager_v1_destroy(m_workspaceManager);
m_workspaceManager = nullptr;
}
if (m_xdgOutputManager != nullptr) {
zxdg_output_manager_v1_destroy(m_xdgOutputManager);
m_xdgOutputManager = nullptr;
@@ -301,14 +473,16 @@ void WaylandConnection::cleanup() {
}
m_outputs.clear();
m_hasExtWorkspaceGlobal = false;
}
void WaylandConnection::logStartupSummary() const {
logInfo("wayland connected compositor={} shm={} layer-shell={} xdg-output={} outputs={}",
logInfo("wayland connected compositor={} shm={} layer-shell={} xdg-output={} ext-workspace={} outputs={}",
m_compositor != nullptr ? "yes" : "no",
m_shm != nullptr ? "yes" : "no",
hasLayerShell() ? "yes" : "no",
hasXdgOutputManager() ? "yes" : "no",
hasExtWorkspaceManager() ? "yes" : "no",
m_outputs.size());
for (const auto& output : m_outputs) {
+26
View File
@@ -3,6 +3,7 @@
#include <cstdint>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>
struct wl_compositor;
@@ -13,6 +14,9 @@ struct wl_seat;
struct wl_shm;
struct zwlr_layer_shell_v1;
struct zxdg_output_manager_v1;
struct ext_workspace_manager_v1;
struct ext_workspace_group_handle_v1;
struct ext_workspace_handle_v1;
struct WaylandOutput {
std::uint32_t name = 0;
@@ -43,6 +47,7 @@ public:
bool hasRequiredGlobals() const noexcept;
bool hasLayerShell() const noexcept;
bool hasXdgOutputManager() const noexcept;
bool hasExtWorkspaceManager() const noexcept;
wl_display* display() const noexcept;
wl_compositor* compositor() const noexcept;
wl_shm* shm() const noexcept;
@@ -59,6 +64,23 @@ public:
std::uint32_t name);
private:
struct TrackedWorkspace {
std::string name;
bool active = false;
};
public:
// Internal callback entrypoints used by C listeners for ext-workspace.
void onWorkspaceGroupCreated(ext_workspace_group_handle_v1* group);
void onWorkspaceGroupRemoved(ext_workspace_group_handle_v1* group);
void onWorkspaceCreated(ext_workspace_handle_v1* workspace);
void onWorkspaceNameChanged(ext_workspace_handle_v1* workspace, const char* name);
void onWorkspaceStateChanged(ext_workspace_handle_v1* workspace, std::uint32_t state);
void onWorkspaceRemoved(ext_workspace_handle_v1* workspace);
void onWorkspaceManagerFinished();
private:
void bindGlobal(wl_registry* registry,
std::uint32_t name,
const char* interface,
@@ -73,7 +95,11 @@ private:
wl_shm* m_shm = nullptr;
zwlr_layer_shell_v1* m_layerShell = nullptr;
zxdg_output_manager_v1* m_xdgOutputManager = nullptr;
ext_workspace_manager_v1* m_workspaceManager = nullptr;
bool m_hasLayerShellGlobal = false;
bool m_hasExtWorkspaceGlobal = false;
std::vector<WaylandOutput> m_outputs;
std::vector<ext_workspace_group_handle_v1*> m_workspaceGroups;
std::unordered_map<ext_workspace_handle_v1*, TrackedWorkspace> m_workspaces;
OutputChangeCallback m_outputChangeCallback;
};