diff --git a/README.md b/README.md index 2ed730d..9d88065 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# gBar +# gBar My personal blazingly fast and efficient status bar + widgets, in case anyone finds a use for it. *gBar: **G**TK **Bar*** @@ -71,7 +71,7 @@ gBar bluetooth [monitor] ## Features / Widgets Bar: -- Workspaces (Hyprland only) +- Workspaces (Hyprland only. Technically works on all compositors implementing ext_workspace, though workspace control relies on Hyprland) - Time - Bluetooth (BlueZ only) - Audio control diff --git a/flake.nix b/flake.nix index dffe100..eaae0b2 100644 --- a/flake.nix +++ b/flake.nix @@ -24,6 +24,8 @@ ]; buildInputs = [ wayland + wayland-protocols + wayland-scanner bluez gtk3 gtk-layer-shell diff --git a/meson.build b/meson.build index e6f36df..37adeec 100644 --- a/meson.build +++ b/meson.build @@ -1,14 +1,28 @@ project('gBar', - ['cpp'], + ['c', 'cpp'], version: '0.0.1', license: 'MIT', meson_version: '>=0.49.0', - default_options: ['c_std=c++17', + default_options: ['cpp_std=c++17', 'warning_level=3', 'default_library=static', 'buildtype=release'], ) +# Wayland protocols +wayland_client = dependency('wayland-client') +wayland_scanner = find_program('wayland-scanner') + +ext_workspace_src = custom_target('generate-ext-workspace-src', + input: ['protocols/ext-workspace-unstable-v1.xml'], + output: ['ext-workspace-unstable-v1.c'], + command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@']) + +ext_workspace_header = custom_target('generate-ext-workspace-header', + input: ['protocols/ext-workspace-unstable-v1.xml'], + output: ['ext-workspace-unstable-v1.h'], + command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@']) + gtk = dependency('gtk+-3.0') gtk_layer_shell = dependency('gtk-layer-shell-0') @@ -24,8 +38,12 @@ headers = [ ] if get_option('WithHyprland') - add_global_arguments('-DWITH_HYPRLAND', language: 'cpp') - headers += 'src/Hyprland.h' + add_global_arguments('-DWITH_WORKSPACES', language: 'cpp') + headers += 'src/Workspaces.h' +endif +if get_option('WithWorkspaces') + add_global_arguments('-DWITH_WORKSPACES', language: 'cpp') + headers += 'src/Workspaces.h' endif if get_option('WithNvidia') add_global_arguments('-DWITH_NVIDIA', language: 'cpp') @@ -41,24 +59,30 @@ endif if get_option('WithSys') add_global_arguments('-DWITH_SYS', language: 'cpp') endif +if get_option('ForceHyprlandIPC') + add_global_arguments('-DFORCE_HYPRLAND_IPC', language: 'cpp') +endif add_global_arguments('-DUSE_LOGFILE', language: 'cpp') pulse = dependency('libpulse') -libgBar = library('gBar', - ['src/Window.cpp', - 'src/Widget.cpp', - 'src/System.cpp', - 'src/Bar.cpp', +libgBar = library('gBar', + [ ext_workspace_src, + ext_workspace_header, + 'src/Window.cpp', + 'src/Widget.cpp', + 'src/System.cpp', + 'src/Bar.cpp', + 'src/Workspaces.cpp', 'src/AudioFlyin.cpp', 'src/BluetoothDevices.cpp', 'src/Plugin.cpp', 'src/Config.cpp', 'src/CSS.cpp', - 'src/Log.cpp' + 'src/Log.cpp', ], - dependencies: [gtk, gtk_layer_shell, pulse], + dependencies: [gtk, gtk_layer_shell, pulse, wayland_client], install: true) pkg = import('pkgconfig') @@ -66,7 +90,7 @@ pkg.generate(libgBar) executable( 'gBar', - ['src/gBar.cpp'], + ['src/gBar.cpp'], dependencies: [gtk], link_with: libgBar, install_rpath: '$ORIGIN/', diff --git a/meson_options.txt b/meson_options.txt index 43924da..4fc3d44 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,7 +1,10 @@ -option('WithHyprland', type: 'boolean', value : true) +# Deprecated, use WithWorkspaces instead +option('WithHyprland', type: 'boolean', value : false) +option('WithWorkspaces', type: 'boolean', value : true) option('WithNvidia', type: 'boolean', value : true) option('WithAMD', type: 'boolean', value : true) option('WithBlueZ', type: 'boolean', value : true) +option('ForceHyprlandIPC', type: 'boolean', value : false) # You shouldn't enable this, unless you know what you are doing! option('WithSys', type: 'boolean', value : false) diff --git a/protocols/ext-workspace-unstable-v1.xml b/protocols/ext-workspace-unstable-v1.xml new file mode 100644 index 0000000..24410b6 --- /dev/null +++ b/protocols/ext-workspace-unstable-v1.xml @@ -0,0 +1,306 @@ + + + + Copyright © 2019 Christopher Billington + Copyright © 2020 Ilia Bozhinov + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + Workspaces, also called virtual desktops, are groups of surfaces. A + compositor with a concept of workspaces may only show some such groups of + surfaces (those of 'active' workspaces) at a time. 'Activating' a + workspace is a request for the compositor to display that workspace's + surfaces as normal, whereas the compositor may hide or otherwise + de-emphasise surfaces that are associated only with 'inactive' workspaces. + Workspaces are grouped by which sets of outputs they correspond to, and + may contain surfaces only from those outputs. In this way, it is possible + for each output to have its own set of workspaces, or for all outputs (or + any other arbitrary grouping) to share workspaces. Compositors may + optionally conceptually arrange each group of workspaces in an + N-dimensional grid. + + The purpose of this protocol is to enable the creation of taskbars and + docks by providing them with a list of workspaces and their properties, + and allowing them to activate and deactivate workspaces. + + After a client binds the zext_workspace_manager_v1, each workspace will be + sent via the workspace event. + + + + + This event is emitted whenever a new workspace group has been created. + + All initial details of the workspace group (workspaces, outputs) will be + sent immediately after this event via the corresponding events in + zext_workspace_group_handle_v1. + + + + + + + The client must send this request after it has finished sending other + requests. The compositor must process a series of requests preceding a + commit request atomically. + + This allows changes to the workspace properties to be seen as atomic, + even if they happen via multiple events, and even if they involve + multiple zext_workspace_handle_v1 objects, for example, deactivating one + workspace and activating another. + + + + + + This event is sent after all changes in all workspace groups have been + sent. + + This allows changes to one or more zext_workspace_group_handle_v1 + properties to be seen as atomic, even if they happen via multiple + events. In particular, an output moving from one workspace group to + another sends an output_enter event and an output_leave event to the two + zext_workspace_group_handle_v1 objects in question. The compositor sends + the done event only after updating the output information in both + workspace groups. + + + + + + This event indicates that the compositor is done sending events to the + zext_workspace_manager_v1. The server will destroy the object + immediately after sending this request, so it will become invalid and + the client should free any resources associated with it. + + + + + + Indicates the client no longer wishes to receive events for new + workspace groups. However the compositor may emit further workspace + events, until the finished event is emitted. + + The client must not send any more requests after this one. + + + + + + + A zext_workspace_group_handle_v1 object represents a a workspace group + that is assigned a set of outputs and contains a number of workspaces. + + The set of outputs assigned to the workspace group is conveyed to the client via + output_enter and output_leave events, and its workspaces are conveyed with + workspace events. + + For example, a compositor which has a set of workspaces for each output may + advertise a workspace group (and its workspaces) per output, whereas a compositor + where a workspace spans all outputs may advertise a single workspace group for all + outputs. + + + + + This event is emitted whenever an output is assigned to the workspace + group. + + + + + + + This event is emitted whenever an output is removed from the workspace + group. + + + + + + + This event is emitted whenever a new workspace has been created. + + All initial details of the workspace (name, coordinates, state) will + be sent immediately after this event via the corresponding events in + zext_workspace_handle_v1. + + + + + + + This event means the zext_workspace_group_handle_v1 has been destroyed. + It is guaranteed there won't be any more events for this + zext_workspace_group_handle_v1. The zext_workspace_group_handle_v1 becomes + inert so any requests will be ignored except the destroy request. + + The compositor must remove all workspaces belonging to a workspace group + before removing the workspace group. + + + + + + Request that the compositor create a new workspace with the given name. + + There is no guarantee that the compositor will create a new workspace, + or that the created workspace will have the provided name. + + + + + + + Destroys the zext_workspace_handle_v1 object. + + This request should be called either when the client does not want to + use the workspace object any more or after the remove event to finalize + the destruction of the object. + + + + + + + A zext_workspace_handle_v1 object represents a a workspace that handles a + group of surfaces. + + Each workspace has a name, conveyed to the client with the name event; a + list of states, conveyed to the client with the state event; and + optionally a set of coordinates, conveyed to the client with the + coordinates event. The client may request that the compositor activate or + deactivate the workspace. + + Each workspace can belong to only a single workspace group. + Depepending on the compositor policy, there might be workspaces with + the same name in different workspace groups, but these workspaces are still + separate (e.g. one of them might be active while the other is not). + + + + + This event is emitted immediately after the zext_workspace_handle_v1 is + created and whenever the name of the workspace changes. + + + + + + + This event is used to organize workspaces into an N-dimensional grid + within a workspace group, and if supported, is emitted immediately after + the zext_workspace_handle_v1 is created and whenever the coordinates of + the workspace change. Compositors may not send this event if they do not + conceptually arrange workspaces in this way. If compositors simply + number workspaces, without any geometric interpretation, they may send + 1D coordinates, which clients should not interpret as implying any + geometry. Sending an empty array means that the compositor no longer + orders the workspace geometrically. + + Coordinates have an arbitrary number of dimensions N with an uint32 + position along each dimension. By convention if N > 1, the first + dimension is X, the second Y, the third Z, and so on. The compositor may + chose to utilize these events for a more novel workspace layout + convention, however. No guarantee is made about the grid being filled or + bounded; there may be a workspace at coordinate 1 and another at + coordinate 1000 and none in between. Within a workspace group, however, + workspaces must have unique coordinates of equal dimensionality. + + + + + + + This event is emitted immediately after the zext_workspace_handle_v1 is + created and each time the workspace state changes, either because of a + compositor action or because of a request in this protocol. + + + + + + + The different states that a workspace can have. + + + + + + + The workspace is not visible in its workspace group, and clients + attempting to visualize the compositor workspace state should not + display such workspaces. + + + + + + + This event means the zext_workspace_handle_v1 has been destroyed. It is + guaranteed there won't be any more events for this + zext_workspace_handle_v1. The zext_workspace_handle_v1 becomes inert so + any requests will be ignored except the destroy request. + + + + + + Destroys the zext_workspace_handle_v1 object. + + This request should be called either when the client does not want to + use the workspace object any more or after the remove event to finalize + the destruction of the object. + + + + + + Request that this workspace be activated. + + There is no guarantee the workspace will be actually activated, and + behaviour may be compositor-dependent. For example, activating a + workspace may or may not deactivate all other workspaces in the same + group. + + + + + + Request that this workspace be deactivated. + + There is no guarantee the workspace will be actually deactivated. + + + + + + Request that this workspace be removed. + + There is no guarantee the workspace will be actually removed. + + + + diff --git a/src/Bar.cpp b/src/Bar.cpp index 9ff871b..01fb2c9 100644 --- a/src/Bar.cpp +++ b/src/Bar.cpp @@ -198,7 +198,7 @@ namespace Bar return TimerResult::Ok; } -#ifdef WITH_HYPRLAND +#ifdef WITH_WORKSPACES static std::array workspaces; TimerResult UpdateWorkspaces(Box&) { @@ -547,7 +547,7 @@ namespace Bar parent.AddChild(std::move(eventBox)); } -#ifdef WITH_HYPRLAND +#ifdef WITH_WORKSPACES void WidgetWorkspaces(Widget& parent) { auto margin = Widget::Create(); @@ -591,8 +591,8 @@ namespace Bar auto left = Widget::Create(); left->SetSpacing({0, false}); left->SetHorizontalTransform({-1, true, Alignment::Left}); -#ifdef WITH_HYPRLAND - if (RuntimeConfig::Get().hasHyprland) +#ifdef WITH_WORKSPACES + if (RuntimeConfig::Get().hasWorkspaces) { WidgetWorkspaces(*left); } diff --git a/src/Config.h b/src/Config.h index c54161a..60ecb87 100644 --- a/src/Config.h +++ b/src/Config.h @@ -33,7 +33,7 @@ public: static const Config& Get(); }; -// Configs, that rely on specific files to be available(e.g. Hyprland running) +// Configs, that rely on specific files to be available(e.g. BlueZ running) class RuntimeConfig { public: @@ -48,10 +48,10 @@ public: bool hasAMD = false; #endif -#ifdef WITH_HYPRLAND - bool hasHyprland = true; +#ifdef WITH_WORKSPACES + bool hasWorkspaces = true; #else - bool hasHyprland = false; + bool hasWorkspaces = false; #endif #ifdef WITH_BLUEZ diff --git a/src/System.cpp b/src/System.cpp index be0e14f..6bb9864 100644 --- a/src/System.cpp +++ b/src/System.cpp @@ -3,7 +3,7 @@ #include "NvidiaGPU.h" #include "AMDGPU.h" #include "PulseAudio.h" -#include "Hyprland.h" +#include "Workspaces.h" #include "Config.h" #include @@ -446,22 +446,22 @@ namespace System PulseAudio::SetVolumeSource(volume); } -#ifdef WITH_HYPRLAND +#ifdef WITH_WORKSPACES void PollWorkspaces(uint32_t monitor, uint32_t numWorkspaces) { - Hyprland::PollStatus(monitor, numWorkspaces); + Workspaces::PollStatus(monitor, numWorkspaces); } WorkspaceStatus GetWorkspaceStatus(uint32_t workspace) { - return Hyprland::GetStatus(workspace); + return Workspaces::GetStatus(workspace); } void GotoWorkspace(uint32_t workspace) { - return Hyprland::Goto(workspace); + return Workspaces::Goto(workspace); } void GotoNextWorkspace(char direction) { - return Hyprland::GotoNext(direction); + return Workspaces::GotoNext(direction); } std::string GetWorkspaceSymbol(int index) { @@ -582,8 +582,8 @@ namespace System AMDGPU::Init(); #endif -#ifdef WITH_HYPRLAND - Hyprland::Init(); +#ifdef WITH_WORKSPACES + Workspaces::Init(); #endif #ifdef WITH_BLUEZ @@ -601,6 +601,10 @@ namespace System #endif PulseAudio::Shutdown(); +#ifdef WITH_WORKSPACES + Workspaces::Shutdown(); +#endif + #ifdef WITH_BLUEZ StopBTScan(); #endif diff --git a/src/System.h b/src/System.h index babd3fe..7b79ebf 100644 --- a/src/System.h +++ b/src/System.h @@ -83,7 +83,7 @@ namespace System void SetVolumeSink(double volume); void SetVolumeSource(double volume); -#ifdef WITH_HYPRLAND +#ifdef WITH_WORKSPACES enum class WorkspaceStatus { Dead, diff --git a/src/Workspaces.cpp b/src/Workspaces.cpp new file mode 100644 index 0000000..2b594a7 --- /dev/null +++ b/src/Workspaces.cpp @@ -0,0 +1,283 @@ +#include "Workspaces.h" + +#include + +#include +#include + +#ifdef WITH_WORKSPACES +#ifndef FORCE_HYPRLAND_IPC +namespace Workspaces +{ + struct WaylandMonitor + { + std::string name; + wl_output* output; + zext_workspace_group_handle_v1* workspaceGroup; + }; + + struct WaylandWorkspace + { + zext_workspace_group_handle_v1* parent; + uint32_t id; + bool active; + }; + struct WaylandWorkspaceGroup + { + std::vector workspaces; + zext_workspace_handle_v1* lastActiveWorkspace; + }; + + // There's probably a better way to avoid the LUTs + static std::unordered_map monitors; + static std::unordered_map workspaceGroups; + static std::unordered_map workspaces; + + static uint32_t curID = 0; + + static wl_display* display; + static wl_registry* registry; + static zext_workspace_manager_v1* workspaceManager; + + static bool registeredMonitors = false; + static bool registeredGroup = false; + static bool registeredWorkspace = false; + static bool registeredWorkspaceInfo = false; + + // Wayland callbacks + + // Workspace Callbacks + static void OnWorkspaceName(void*, zext_workspace_handle_v1* workspace, const char* name) + { + workspaces[workspace].id = std::stoul(name); + LOG("Workspace ID: " << workspaces[workspace].id); + registeredWorkspaceInfo = true; + } + static void OnWorkspaceGeometry(void*, zext_workspace_handle_v1*, wl_array*) {} + static void OnWorkspaceState(void*, zext_workspace_handle_v1* ws, wl_array* arrState) + { + WaylandWorkspace& workspace = workspaces[ws]; + ASSERT(workspace.parent, "Wayland: Workspace not registered!"); + WaylandWorkspaceGroup& group = workspaceGroups[workspace.parent]; + + workspace.active = false; + // Manual wl_array_for_each, since that's broken for C++ + for (zext_workspace_handle_v1_state* state = (zext_workspace_handle_v1_state*)arrState->data; + (uint8_t*)state < (uint8_t*)arrState->data + arrState->size; state += 1) + { + if (*state == ZEXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE) + { + group.lastActiveWorkspace = ws; + workspace.active = true; + } + } + } + static void OnWorkspaceRemove(void*, zext_workspace_handle_v1* ws) + { + WaylandWorkspace& workspace = workspaces[ws]; + ASSERT(workspace.parent, "Wayland: Workspace not registered!"); + WaylandWorkspaceGroup& group = workspaceGroups[workspace.parent]; + auto it = std::find(group.workspaces.begin(), group.workspaces.end(), ws); + group.workspaces.erase(it); + + workspaces.erase(ws); + + LOG("Wayland: Removed workspace!"); + } + zext_workspace_handle_v1_listener workspaceListener = {OnWorkspaceName, OnWorkspaceGeometry, OnWorkspaceState, OnWorkspaceRemove}; + + // Workspace Group callbacks + static void OnWSGroupOutputEnter(void*, zext_workspace_group_handle_v1* group, wl_output* output) + { + auto monitor = std::find_if(monitors.begin(), monitors.end(), + [&](const std::pair& mon) + { + return mon.second.output == output; + }); + ASSERT(monitor != monitors.end(), "Wayland: Registered WS group before monitor!"); + LOG("Wayland: Added group to monitor"); + monitor->second.workspaceGroup = group; + } + static void OnWSGroupOutputLeave(void*, zext_workspace_group_handle_v1*, wl_output* output) + { + auto monitor = std::find_if(monitors.begin(), monitors.end(), + [&](const std::pair& mon) + { + return mon.second.output == output; + }); + ASSERT(monitor != monitors.end(), "Wayland: Registered WS group before monitor!"); + LOG("Wayland: Added group to monitor"); + monitor->second.workspaceGroup = nullptr; + } + static void OnWSGroupWorkspaceAdded(void*, zext_workspace_group_handle_v1* workspace, zext_workspace_handle_v1* ws) + { + LOG("Wayland: Added workspace!"); + workspaceGroups[workspace].workspaces.push_back(ws); + workspaces[ws] = {workspace, (uint32_t)-1}; + zext_workspace_handle_v1_add_listener(ws, &workspaceListener, nullptr); + registeredWorkspace = true; + } + static void OnWSGroupRemove(void*, zext_workspace_group_handle_v1* workspaceGroup) + { + workspaceGroups.erase(workspaceGroup); + } + zext_workspace_group_handle_v1_listener workspaceGroupListener = {OnWSGroupOutputEnter, OnWSGroupOutputLeave, OnWSGroupWorkspaceAdded, + OnWSGroupRemove}; + + // Workspace Manager Callbacks + static void OnWSManagerNewGroup(void*, zext_workspace_manager_v1*, zext_workspace_group_handle_v1* group) + { + // Register callbacks for the group. + registeredGroup = true; + zext_workspace_group_handle_v1_add_listener(group, &workspaceGroupListener, nullptr); + } + static void OnWSManagerDone(void*, zext_workspace_manager_v1*) {} + static void OnWSManagerFinished(void*, zext_workspace_manager_v1*) + { + LOG("Wayland: Workspace manager finished. Disabling workspaces!"); + RuntimeConfig::Get().hasWorkspaces = false; + } + zext_workspace_manager_v1_listener workspaceManagerListener = {OnWSManagerNewGroup, OnWSManagerDone, OnWSManagerFinished}; + + // Output Callbacks + // Very bloated, indeed + static void OnOutputGeometry(void*, wl_output*, int32_t, int32_t, int32_t, int32_t, int32_t, const char*, const char*, int32_t) {} + static void OnOutputMode(void*, wl_output*, uint32_t, int32_t, int32_t, int32_t) {} + static void OnOutputDone(void*, wl_output*) {} + static void OnOutputScale(void*, wl_output*, int32_t) {} + static void OnOutputName(void*, wl_output* output, const char* name) + { + LOG("Wayland: Registering monitor " << name << " at ID " << curID); + registeredMonitors = true; + monitors.try_emplace(curID++, WaylandMonitor{name, output, nullptr}); + } + static void OnOutputDescription(void*, wl_output*, const char*) {} + wl_output_listener outputListener = {OnOutputGeometry, OnOutputMode, OnOutputDone, OnOutputScale, OnOutputName, OnOutputDescription}; + + // Registry Callbacks + static void OnRegistryAdd(void*, wl_registry* registry, uint32_t name, const char* interface, uint32_t version) + { + if (strcmp(interface, "wl_output") == 0) + { + wl_output* output = (wl_output*)wl_registry_bind(registry, name, &wl_output_interface, 4); + wl_output_add_listener(output, &outputListener, nullptr); + } + if (strcmp(interface, "zext_workspace_manager_v1") == 0) + { + workspaceManager = (zext_workspace_manager_v1*)wl_registry_bind(registry, name, &zext_workspace_manager_v1_interface, version); + zext_workspace_manager_v1_add_listener(workspaceManager, &workspaceManagerListener, nullptr); + } + } + static void OnRegistryRemove(void*, wl_registry*, uint32_t) {} + wl_registry_listener registryListener = {OnRegistryAdd, OnRegistryRemove}; + + // Dispatch events. + static void Dispatch() + { + wl_display_roundtrip(display); + } + static void WaitFor(bool& condition) + { + while (!condition && wl_display_dispatch(display) != -1) + { + } + } + + void Init() + { + display = wl_display_connect(nullptr); + ASSERT(display, "Cannot connect to wayland compositor!"); + registry = wl_display_get_registry(display); + ASSERT(registry, "Cannot get wayland registry!"); + + wl_registry_add_listener(registry, ®istryListener, nullptr); + wl_display_roundtrip(display); + + WaitFor(registeredMonitors); + registeredMonitors = false; + + if (!workspaceManager) + { + LOG("Compositor doesn't implement zext_workspace_manager_v1, disabling workspaces!"); + RuntimeConfig::Get().hasWorkspaces = false; + return; + } + } + + // Hack for unified interface + static uint32_t lastPolledMonitor = 0; + void PollStatus(uint32_t monitor, uint32_t) + { + lastPolledMonitor = monitor; + // Dispatch events + Dispatch(); + if (registeredGroup) + { + // New Group, wait for workspaces to be registered. + WaitFor(registeredWorkspace); + } + if (registeredWorkspace) + { + // New workspace added, need info + WaitFor(registeredWorkspaceInfo); + } + registeredGroup = false; + registeredWorkspace = false; + registeredWorkspaceInfo = false; + return; + } + + System::WorkspaceStatus GetStatus(uint32_t workspaceId) + { + WaylandMonitor& monitor = monitors[lastPolledMonitor]; + if (!monitor.output) + { + LOG("Polled monitor doesn't exist!"); + return System::WorkspaceStatus::Dead; + } + + auto workspaceIt = std::find_if(workspaces.begin(), workspaces.end(), + [&](const std::pair& ws) + { + return ws.second.id == workspaceId; + }); + if (workspaceIt == workspaces.end()) + { + return System::WorkspaceStatus::Dead; + } + + WaylandWorkspaceGroup& group = workspaceGroups[monitor.workspaceGroup]; + if (group.lastActiveWorkspace) + { + WaylandWorkspace& activeWorkspace = workspaces[group.lastActiveWorkspace]; + if (activeWorkspace.id == workspaceId && activeWorkspace.active) + { + return System::WorkspaceStatus::Active; + } + else if (activeWorkspace.id == workspaceId) + { + // Last active workspace (Means we can still see it, since no other ws is active and thus is only visible) + return System::WorkspaceStatus::Current; + } + } + + WaylandWorkspaceGroup& currentWorkspaceGroup = workspaceGroups[workspaceIt->second.parent]; + if (currentWorkspaceGroup.lastActiveWorkspace == workspaceIt->first) + { + return System::WorkspaceStatus::Visible; + } + else + { + return System::WorkspaceStatus::Inactive; + } + + return System::WorkspaceStatus::Dead; + } + + void Shutdown() + { + wl_display_disconnect(display); + } +} +#endif +#endif diff --git a/src/Hyprland.h b/src/Workspaces.h similarity index 86% rename from src/Hyprland.h rename to src/Workspaces.h index 303d9a0..a59ec08 100644 --- a/src/Hyprland.h +++ b/src/Workspaces.h @@ -11,16 +11,17 @@ #include #include -#ifdef WITH_HYPRLAND -namespace Hyprland +#ifdef WITH_WORKSPACES +namespace Workspaces { +#ifdef FORCE_HYPRLAND_IPC inline void Init() { if (!getenv("HYPRLAND_INSTANCE_SIGNATURE")) { - LOG("Hyprland not running, disabling workspaces"); + LOG("Workspaces not running, disabling workspaces"); // Not available - RuntimeConfig::Get().hasHyprland = false; + RuntimeConfig::Get().hasWorkspaces = false; } } @@ -59,9 +60,9 @@ namespace Hyprland inline void PollStatus(uint32_t monitorID, uint32_t numWorkspaces) { - if (RuntimeConfig::Get().hasHyprland == false) + if (RuntimeConfig::Get().hasWorkspaces == false) { - LOG("Error: Polled workspace status, but Hyprland isn't open!"); + LOG("Error: Polled workspace status, but Workspaces isn't open!"); return; } workspaceStati.clear(); @@ -135,20 +136,32 @@ namespace Hyprland inline System::WorkspaceStatus GetStatus(uint32_t workspaceId) { - if (RuntimeConfig::Get().hasHyprland == false) + if (RuntimeConfig::Get().hasWorkspaces == false) { - LOG("Error: Queried for workspace status, but Hyprland isn't open!"); + LOG("Error: Queried for workspace status, but Workspaces isn't open!"); return System::WorkspaceStatus::Dead; } ASSERT(workspaceId > 0 && workspaceId <= workspaceStati.size(), "Invalid workspaceId, you need to poll the workspace first!"); return workspaceStati[workspaceId - 1]; } + inline void Shutdown() {} +#else + void Init(); + + void PollStatus(uint32_t monitorID, uint32_t numWorkspaces); + + System::WorkspaceStatus GetStatus(uint32_t workspaceId); + + void Shutdown(); +#endif + + // TODO: Use ext_workspaces for this inline void Goto(uint32_t workspace) { - if (RuntimeConfig::Get().hasHyprland == false) + if (RuntimeConfig::Get().hasWorkspaces == false) { - LOG("Error: Called Go to workspace, but Hyprland isn't open!"); + LOG("Error: Called Go to workspace, but Workspaces isn't open!"); return; }