mirror of
https://github.com/scorpion-26/gBar.git
synced 2024-11-22 03:02:49 +00:00
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:
parent
78107f16fa
commit
c2c38dcae7
7 changed files with 446 additions and 397 deletions
|
@ -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
|
||||||
|
|
|
@ -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')
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -6,8 +6,9 @@
|
||||||
#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
|
||||||
{
|
{
|
||||||
|
@ -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
|
#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
|
||||||
|
|
136
src/Workspaces.h
136
src/Workspaces.h
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue