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,9 +6,10 @@
#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
{ {
namespace Wayland
{
struct WaylandMonitor struct WaylandMonitor
{ {
std::string name; std::string name;
@ -306,8 +307,184 @@ namespace Workspaces
void Shutdown() void Shutdown()
{ {
if (display)
wl_display_disconnect(display); wl_display_disconnect(display);
} }
}
#ifdef WITH_HYPRLAND
namespace Hyprland
{
void Init()
{
if (!getenv("HYPRLAND_INSTANCE_SIGNATURE"))
{
LOG("Workspaces not running, disabling workspaces");
// Not available
RuntimeConfig::Get().hasWorkspaces = false;
}
}
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;
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)
{
#ifdef WITH_HYPRLAND
if (Config::Get().useHyprlandIPC)
{
return Hyprland::GetStatus(workspaceId);
}
#endif
return Wayland::GetStatus(workspaceId);
}
void Shutdown()
{
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)