Workspaces: Add config option for Hyprland IPC

ext_workspace wasn't as perfect of a replacement as I thought it was...
This commit is contained in:
scorpion-26 2023-03-11 00:24:50 +01:00
parent 78107f16fa
commit c2c38dcae7
7 changed files with 446 additions and 397 deletions

View file

@ -34,6 +34,11 @@ WorkspaceScrollOnMonitor: true
# When true: Scroll up -> Next workspace instead of previous workspace. Analogous with scroll down # When true: Scroll up -> Next workspace instead of previous workspace. Analogous with scroll down
WorkspaceScrollInvert: false WorkspaceScrollInvert: false
# Use Hyprland IPC instead of the ext_workspace protocol for workspace polling.
# Hyprland IPC is *slightly* less performant (+0.1% one core), but way less bug prone,
# since the protocol is not as feature complete as Hyprland IPC.
UseHyprlandIPC: false
# Forces the time to be centered. # Forces the time to be centered.
# This can cause issues, if there is not enough space on screen (e.g. when opening the text) # This can cause issues, if there is not enough space on screen (e.g. when opening the text)
CenterTime: true CenterTime: true

View file

@ -38,7 +38,7 @@ headers = [
] ]
if get_option('WithHyprland') if get_option('WithHyprland')
add_global_arguments('-DWITH_WORKSPACES', language: 'cpp') add_global_arguments('-DWITH_HYPRLAND', language: 'cpp')
headers += 'src/Workspaces.h' headers += 'src/Workspaces.h'
endif endif
if get_option('WithWorkspaces') if get_option('WithWorkspaces')
@ -59,9 +59,6 @@ endif
if get_option('WithSys') if get_option('WithSys')
add_global_arguments('-DWITH_SYS', language: 'cpp') add_global_arguments('-DWITH_SYS', language: 'cpp')
endif endif
if get_option('ForceHyprlandIPC')
add_global_arguments('-DFORCE_HYPRLAND_IPC', language: 'cpp')
endif
add_global_arguments('-DUSE_LOGFILE', language: 'cpp') add_global_arguments('-DUSE_LOGFILE', language: 'cpp')

View file

@ -1,10 +1,12 @@
# Deprecated, use WithWorkspaces instead # Hyprland IPC
option('WithHyprland', type: 'boolean', value : false) option('WithHyprland', type: 'boolean', value : true)
# Workspaces general, enables Wayland protocol
option('WithWorkspaces', type: 'boolean', value : true) option('WithWorkspaces', type: 'boolean', value : true)
option('WithNvidia', type: 'boolean', value : true) option('WithNvidia', type: 'boolean', value : true)
option('WithAMD', type: 'boolean', value : true) option('WithAMD', type: 'boolean', value : true)
option('WithBlueZ', 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! # You shouldn't enable this, unless you know what you are doing!
option('WithSys', type: 'boolean', value : false) option('WithSys', type: 'boolean', value : false)

View file

@ -137,6 +137,7 @@ void Config::Load()
AddConfigVar("NetworkWidget", config.networkWidget, lineView, foundProperty); AddConfigVar("NetworkWidget", config.networkWidget, lineView, foundProperty);
AddConfigVar("WorkspaceScrollOnMonitor", config.workspaceScrollOnMonitor, lineView, foundProperty); AddConfigVar("WorkspaceScrollOnMonitor", config.workspaceScrollOnMonitor, lineView, foundProperty);
AddConfigVar("WorkspaceScrollInvert", config.workspaceScrollInvert, lineView, foundProperty); AddConfigVar("WorkspaceScrollInvert", config.workspaceScrollInvert, lineView, foundProperty);
AddConfigVar("UseHyprlandIPC", config.useHyprlandIPC, lineView, foundProperty);
AddConfigVar("MinUploadBytes", config.minUploadBytes, lineView, foundProperty); AddConfigVar("MinUploadBytes", config.minUploadBytes, lineView, foundProperty);
AddConfigVar("MaxUploadBytes", config.maxUploadBytes, lineView, foundProperty); AddConfigVar("MaxUploadBytes", config.maxUploadBytes, lineView, foundProperty);

View file

@ -20,6 +20,7 @@ public:
bool networkWidget = true; bool networkWidget = true;
bool workspaceScrollOnMonitor = true; // Scroll through workspaces on monitor instead of all bool workspaceScrollOnMonitor = true; // Scroll through workspaces on monitor instead of all
bool workspaceScrollInvert = false; // Up = +1, instead of Up = -1 bool workspaceScrollInvert = false; // Up = +1, instead of Up = -1
bool useHyprlandIPC = false; // Use Hyprland IPC instead of ext_workspaces protocol (Less buggy, but also less performant)
// Controls for color progression of the network widget // Controls for color progression of the network widget
uint32_t minUploadBytes = 0; // Bottom limit of the network widgets upload. Everything below it is considered "under" uint32_t minUploadBytes = 0; // Bottom limit of the network widgets upload. Everything below it is considered "under"

View file

@ -6,308 +6,485 @@
#include <ext-workspace-unstable-v1.h> #include <ext-workspace-unstable-v1.h>
#ifdef WITH_WORKSPACES #ifdef WITH_WORKSPACES
#ifndef FORCE_HYPRLAND_IPC
namespace Workspaces namespace Workspaces
{ {
struct WaylandMonitor namespace Wayland
{ {
std::string name; struct WaylandMonitor
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<zext_workspace_handle_v1*> workspaces;
zext_workspace_handle_v1* lastActiveWorkspace;
};
// There's probably a better way to avoid the LUTs
static std::unordered_map<uint32_t, WaylandMonitor> monitors;
static std::unordered_map<zext_workspace_group_handle_v1*, WaylandWorkspaceGroup> workspaceGroups;
static std::unordered_map<zext_workspace_handle_v1*, WaylandWorkspace> 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) 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<zext_workspace_handle_v1*> workspaces;
zext_workspace_handle_v1* lastActiveWorkspace;
};
// There's probably a better way to avoid the LUTs
static std::unordered_map<uint32_t, WaylandMonitor> monitors;
static std::unordered_map<zext_workspace_group_handle_v1*, WaylandWorkspaceGroup> workspaceGroups;
static std::unordered_map<zext_workspace_handle_v1*, WaylandWorkspace> 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)
{ {
LOG("Wayland: Activate Workspace " << workspace.id); if (*state == ZEXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE)
group.lastActiveWorkspace = ws; {
workspace.active = true; LOG("Wayland: Activate Workspace " << workspace.id);
group.lastActiveWorkspace = ws;
workspace.active = true;
}
}
if (!workspace.active)
{
LOG("Wayland: Deactivate Workspace " << workspace.id);
} }
} }
if (!workspace.active) static void OnWorkspaceRemove(void*, zext_workspace_handle_v1* ws)
{ {
LOG("Wayland: Deactivate Workspace " << workspace.id); 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};
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); // Workspace Group callbacks
static void OnWSGroupOutputEnter(void*, zext_workspace_group_handle_v1* group, wl_output* output)
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<uint32_t, WaylandMonitor>& 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<uint32_t, WaylandMonitor>& 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); auto monitor = std::find_if(monitors.begin(), monitors.end(),
wl_output_add_listener(output, &outputListener, nullptr); [&](const std::pair<uint32_t, WaylandMonitor>& 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;
} }
if (strcmp(interface, "zext_workspace_manager_v1") == 0) static void OnWSGroupOutputLeave(void*, zext_workspace_group_handle_v1*, wl_output* output)
{ {
workspaceManager = (zext_workspace_manager_v1*)wl_registry_bind(registry, name, &zext_workspace_manager_v1_interface, version); auto monitor = std::find_if(monitors.begin(), monitors.end(),
zext_workspace_manager_v1_add_listener(workspaceManager, &workspaceManagerListener, nullptr); [&](const std::pair<uint32_t, WaylandMonitor>& 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)
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)
{ {
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)
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, &registryListener, nullptr);
wl_display_roundtrip(display);
WaitFor(registeredMonitors);
registeredMonitors = false;
if (!workspaceManager)
{ {
LOG("Compositor doesn't implement zext_workspace_manager_v1, disabling workspaces!"); 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; 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, &registryListener, 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: manually activate workspace for each monitor
for (auto& monitor : monitors)
{
// Find group
auto& group = workspaceGroups[monitor.second.workspaceGroup];
// Find ws with monitor index + 1
auto workspaceIt = std::find_if(workspaces.begin(), workspaces.end(),
[&](const std::pair<zext_workspace_handle_v1*, WaylandWorkspace>& ws)
{
return ws.second.id == monitor.first + 1;
});
if (workspaceIt != workspaces.end())
{
LOG("Forcefully activate workspace " << workspaceIt->second.id)
if (workspaceIt->second.id == 1)
{
// Activate first workspace
workspaceIt->second.active = true;
}
// Make it visible
group.lastActiveWorkspace = workspaceIt->first;
}
}
}
// 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; return;
} }
// Hack: manually activate workspace for each monitor System::WorkspaceStatus GetStatus(uint32_t workspaceId)
for (auto& monitor : monitors)
{ {
// Find group WaylandMonitor& monitor = monitors[lastPolledMonitor];
auto& group = workspaceGroups[monitor.second.workspaceGroup]; if (!monitor.output)
{
LOG("Polled monitor doesn't exist!");
return System::WorkspaceStatus::Dead;
}
// Find ws with monitor index + 1
auto workspaceIt = std::find_if(workspaces.begin(), workspaces.end(), auto workspaceIt = std::find_if(workspaces.begin(), workspaces.end(),
[&](const std::pair<zext_workspace_handle_v1*, WaylandWorkspace>& ws) [&](const std::pair<zext_workspace_handle_v1*, WaylandWorkspace>& ws)
{ {
return ws.second.id == monitor.first + 1; return ws.second.id == workspaceId;
}); });
if (workspaceIt != workspaces.end()) if (workspaceIt == workspaces.end())
{ {
LOG("Forcefully activate workspace " << workspaceIt->second.id) return System::WorkspaceStatus::Dead;
if (workspaceIt->second.id == 1)
{
// Activate first workspace
workspaceIt->second.active = true;
}
// Make it visible
group.lastActiveWorkspace = workspaceIt->first;
} }
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()
{
if (display)
wl_display_disconnect(display);
} }
} }
// Hack for unified interface #ifdef WITH_HYPRLAND
static uint32_t lastPolledMonitor = 0; namespace Hyprland
void PollStatus(uint32_t monitor, uint32_t)
{ {
lastPolledMonitor = monitor; void Init()
// Dispatch events
Dispatch();
if (registeredGroup)
{ {
// New Group, wait for workspaces to be registered. if (!getenv("HYPRLAND_INSTANCE_SIGNATURE"))
WaitFor(registeredWorkspace); {
LOG("Workspaces not running, disabling workspaces");
// Not available
RuntimeConfig::Get().hasWorkspaces = false;
}
} }
if (registeredWorkspace)
std::string DispatchIPC(const std::string& arg)
{ {
// New workspace added, need info int hyprSocket = socket(AF_UNIX, SOCK_STREAM, 0);
WaitFor(registeredWorkspaceInfo); const char* instanceSignature = getenv("HYPRLAND_INSTANCE_SIGNATURE");
std::string socketPath = "/tmp/hypr/" + std::string(instanceSignature) + "/.socket.sock";
sockaddr_un addr = {};
addr.sun_family = AF_UNIX;
memcpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path));
int ret = connect(hyprSocket, (sockaddr*)&addr, SUN_LEN(&addr));
ASSERT(ret >= 0, "Couldn't connect to hyprland socket");
ssize_t written = write(hyprSocket, arg.c_str(), arg.size());
ASSERT(written >= 0, "Couldn't write to socket");
char buf[2056];
std::string res;
while (true)
{
ssize_t bytesRead = read(hyprSocket, buf, sizeof(buf));
if (bytesRead == 0)
{
break;
}
ASSERT(bytesRead >= 0, "Couldn't read");
res += std::string(buf, bytesRead);
}
close(hyprSocket);
return res;
} }
registeredGroup = false;
registeredWorkspace = false; static std::vector<System::WorkspaceStatus> workspaceStati;
registeredWorkspaceInfo = false;
return; void PollStatus(uint32_t monitorID, uint32_t numWorkspaces)
{
if (RuntimeConfig::Get().hasWorkspaces == false)
{
LOG("Error: Polled workspace status, but Workspaces isn't open!");
return;
}
workspaceStati.clear();
workspaceStati.resize(numWorkspaces, System::WorkspaceStatus::Dead);
size_t parseIdx = 0;
// First parse workspaces
std::string workspaces = DispatchIPC("/workspaces");
while ((parseIdx = workspaces.find("workspace ID ", parseIdx)) != std::string::npos)
{
// Goto (
size_t begWSNum = workspaces.find('(', parseIdx) + 1;
size_t endWSNum = workspaces.find(')', begWSNum);
std::string ws = workspaces.substr(begWSNum, endWSNum - begWSNum);
int32_t wsId = std::atoi(ws.c_str());
if (wsId >= 1 && wsId < (int32_t)numWorkspaces)
{
// WS is at least inactive
workspaceStati[wsId - 1] = System::WorkspaceStatus::Inactive;
}
parseIdx = endWSNum;
}
// Parse active workspaces for monitor
std::string monitors = DispatchIPC("/monitors");
parseIdx = 0;
while ((parseIdx = monitors.find("Monitor ", parseIdx)) != std::string::npos)
{
// Goto ( and remove ID (=Advance 4 spaces, 1 for (, two for ID, one for space)
size_t begMonNum = monitors.find('(', parseIdx) + 4;
size_t endMonNum = monitors.find(')', begMonNum);
std::string mon = monitors.substr(begMonNum, endMonNum - begMonNum);
int32_t monIdx = std::atoi(mon.c_str());
// Parse active workspace
parseIdx = monitors.find("active workspace: ", parseIdx);
ASSERT(parseIdx != std::string::npos, "Invalid IPC response!");
size_t begWSNum = monitors.find('(', parseIdx) + 1;
size_t endWSNum = monitors.find(')', begWSNum);
std::string ws = monitors.substr(begWSNum, endWSNum - begWSNum);
int32_t wsId = std::atoi(ws.c_str());
// Check if focused
parseIdx = monitors.find("focused: ", parseIdx);
ASSERT(parseIdx != std::string::npos, "Invalid IPC response!");
size_t begFocused = monitors.find(' ', parseIdx) + 1;
size_t endFocused = monitors.find('\n', begFocused);
bool focused = std::string_view(monitors).substr(begFocused, endFocused - begFocused) == "yes";
if (wsId >= 1 && wsId < (int32_t)numWorkspaces)
{
if ((uint32_t)monIdx == monitorID)
{
if (focused)
{
workspaceStati[wsId - 1] = System::WorkspaceStatus::Active;
}
else
{
workspaceStati[wsId - 1] = System::WorkspaceStatus::Current;
}
}
else
{
workspaceStati[wsId - 1] = System::WorkspaceStatus::Visible;
}
}
}
}
System::WorkspaceStatus GetStatus(uint32_t workspaceId)
{
if (RuntimeConfig::Get().hasWorkspaces == false)
{
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];
}
}
#endif
void Init()
{
#ifdef WITH_HYPRLAND
if (Config::Get().useHyprlandIPC)
{
Hyprland::Init();
return;
}
#endif
Wayland::Init();
}
void PollStatus(uint32_t monitorID, uint32_t numWorkspaces)
{
#ifdef WITH_HYPRLAND
if (Config::Get().useHyprlandIPC)
{
Hyprland::PollStatus(monitorID, numWorkspaces);
return;
}
#endif
Wayland::PollStatus(monitorID, numWorkspaces);
} }
System::WorkspaceStatus GetStatus(uint32_t workspaceId) System::WorkspaceStatus GetStatus(uint32_t workspaceId)
{ {
WaylandMonitor& monitor = monitors[lastPolledMonitor]; #ifdef WITH_HYPRLAND
if (!monitor.output) if (Config::Get().useHyprlandIPC)
{ {
LOG("Polled monitor doesn't exist!"); return Hyprland::GetStatus(workspaceId);
return System::WorkspaceStatus::Dead;
} }
#endif
auto workspaceIt = std::find_if(workspaces.begin(), workspaces.end(), return Wayland::GetStatus(workspaceId);
[&](const std::pair<zext_workspace_handle_v1*, WaylandWorkspace>& 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() void Shutdown()
{ {
wl_display_disconnect(display); Wayland::Shutdown();
} }
} }
#endif #endif
#endif

View file

@ -14,139 +14,6 @@
#ifdef WITH_WORKSPACES #ifdef WITH_WORKSPACES
namespace Workspaces namespace Workspaces
{ {
#ifdef FORCE_HYPRLAND_IPC
inline void Init()
{
if (!getenv("HYPRLAND_INSTANCE_SIGNATURE"))
{
LOG("Workspaces not running, disabling workspaces");
// Not available
RuntimeConfig::Get().hasWorkspaces = false;
}
}
inline std::string DispatchIPC(const std::string& arg)
{
int hyprSocket = socket(AF_UNIX, SOCK_STREAM, 0);
const char* instanceSignature = getenv("HYPRLAND_INSTANCE_SIGNATURE");
std::string socketPath = "/tmp/hypr/" + std::string(instanceSignature) + "/.socket.sock";
sockaddr_un addr = {};
addr.sun_family = AF_UNIX;
memcpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path));
int ret = connect(hyprSocket, (sockaddr*)&addr, SUN_LEN(&addr));
ASSERT(ret >= 0, "Couldn't connect to hyprland socket");
ssize_t written = write(hyprSocket, arg.c_str(), arg.size());
ASSERT(written >= 0, "Couldn't write to socket");
char buf[2056];
std::string res;
while (true)
{
ssize_t bytesRead = read(hyprSocket, buf, sizeof(buf));
if (bytesRead == 0)
{
break;
}
ASSERT(bytesRead >= 0, "Couldn't read");
res += std::string(buf, bytesRead);
}
close(hyprSocket);
return res;
}
static std::vector<System::WorkspaceStatus> workspaceStati;
inline void PollStatus(uint32_t monitorID, uint32_t numWorkspaces)
{
if (RuntimeConfig::Get().hasWorkspaces == false)
{
LOG("Error: Polled workspace status, but Workspaces isn't open!");
return;
}
workspaceStati.clear();
workspaceStati.resize(numWorkspaces, System::WorkspaceStatus::Dead);
size_t parseIdx = 0;
// First parse workspaces
std::string workspaces = DispatchIPC("/workspaces");
while ((parseIdx = workspaces.find("workspace ID ", parseIdx)) != std::string::npos)
{
// Goto (
size_t begWSNum = workspaces.find('(', parseIdx) + 1;
size_t endWSNum = workspaces.find(')', begWSNum);
std::string ws = workspaces.substr(begWSNum, endWSNum - begWSNum);
int32_t wsId = std::atoi(ws.c_str());
if (wsId >= 1 && wsId < (int32_t)numWorkspaces)
{
// WS is at least inactive
workspaceStati[wsId - 1] = System::WorkspaceStatus::Inactive;
}
parseIdx = endWSNum;
}
// Parse active workspaces for monitor
std::string monitors = DispatchIPC("/monitors");
parseIdx = 0;
while ((parseIdx = monitors.find("Monitor ", parseIdx)) != std::string::npos)
{
// Goto ( and remove ID (=Advance 4 spaces, 1 for (, two for ID, one for space)
size_t begMonNum = monitors.find('(', parseIdx) + 4;
size_t endMonNum = monitors.find(')', begMonNum);
std::string mon = monitors.substr(begMonNum, endMonNum - begMonNum);
int32_t monIdx = std::atoi(mon.c_str());
// Parse active workspace
parseIdx = monitors.find("active workspace: ", parseIdx);
ASSERT(parseIdx != std::string::npos, "Invalid IPC response!");
size_t begWSNum = monitors.find('(', parseIdx) + 1;
size_t endWSNum = monitors.find(')', begWSNum);
std::string ws = monitors.substr(begWSNum, endWSNum - begWSNum);
int32_t wsId = std::atoi(ws.c_str());
// Check if focused
parseIdx = monitors.find("focused: ", parseIdx);
ASSERT(parseIdx != std::string::npos, "Invalid IPC response!");
size_t begFocused = monitors.find(' ', parseIdx) + 1;
size_t endFocused = monitors.find('\n', begFocused);
bool focused = std::string_view(monitors).substr(begFocused, endFocused - begFocused) == "yes";
if (wsId >= 1 && wsId < (int32_t)numWorkspaces)
{
if ((uint32_t)monIdx == monitorID)
{
if (focused)
{
workspaceStati[wsId - 1] = System::WorkspaceStatus::Active;
}
else
{
workspaceStati[wsId - 1] = System::WorkspaceStatus::Current;
}
}
else
{
workspaceStati[wsId - 1] = System::WorkspaceStatus::Visible;
}
}
}
}
inline System::WorkspaceStatus GetStatus(uint32_t workspaceId)
{
if (RuntimeConfig::Get().hasWorkspaces == false)
{
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 Init();
void PollStatus(uint32_t monitorID, uint32_t numWorkspaces); void PollStatus(uint32_t monitorID, uint32_t numWorkspaces);
@ -154,9 +21,8 @@ namespace Workspaces
System::WorkspaceStatus GetStatus(uint32_t workspaceId); System::WorkspaceStatus GetStatus(uint32_t workspaceId);
void Shutdown(); void Shutdown();
#endif
// TODO: Use ext_workspaces for this // TODO: Use ext_workspaces for this, if applicable
inline void Goto(uint32_t workspace) inline void Goto(uint32_t workspace)
{ {
if (RuntimeConfig::Get().hasWorkspaces == false) if (RuntimeConfig::Get().hasWorkspaces == false)