diff --git a/.gitignore b/.gitignore index 41be4b0..a53c6bd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ compile_flags.txt compile_commands.json .cache result/ +.idea \ No newline at end of file diff --git a/README.md b/README.md index 9e7bcd7..25ed745 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ # Virtual desktops for Hyprland ![hyprico](.github/hyprland.ico) `virtual-desktops` is a plugin for the [Hyprland](https://github.com/hyprwm/Hyprland) compositor. `virtual-desktops` manages multiple screens workspaces as if they were a single virtual desktop. -This plugin **only supports official releases of Hyprland** (e.g., v0.39.x, v0.40.x). -If you are on `hyprland-git`, please try compiling this plugin from the [dev branch](https://github.com/levnikmyskin/hyprland-virtual-desktops/tree/dev). -There is **NO GUARANTEE** that the plugin will compile succesfully on the latest Hyprland commit, but we try our best to keep it updated. Also, always check the [PR section](https://github.com/levnikmyskin/hyprland-virtual-desktops/pulls?q=is%3Apr+is%3Aopen+sort%3Aupdated-desc), -as there might be a draft PR for the next Hyprland release, where you can check the status of development. +## WARNING +You are on the `dev` branch. This branch unconsistently gather updates and fixes to follow `hyprland-git`. By unconsistently, I mean that there is NO GUARANTEE that this branch will work with `hyprland-git` at any given time. +This plugin **DOES NOT** support `hyprland-git`, please refrain from opening issues if virtual-desktop does not compile with the latest commit on hyprland. Feel free to contribute to this branch via PRs though. Feel free to join our [matrix room](https://matrix.to/#/#hypr-virtual-desktops:matrix.org)! +>>>>>>> main ## Table of contents - [Virtual desktops for Hyprland ](#virtual-desktops-for-hyprland-) @@ -121,6 +121,7 @@ Since version 2.2, this plugin exposes a couple of `hyprctl` commands. That is, | Command | description | args | example| |------------|-------------|------|--------| | printdesk (vdesk)| Prints to Hyprland log the specified vdesk or the currently active vdesk* (if no argument is given) | optional vdesk, see [above](#hyprctl-dispatchers) | `hyprctl printdesk` or `hyprctl printdesk 2` or `hyprctl printdesk coding`| +| printstate | Prints state of all vdesks | `none` | `hyprctl printstate` | | printlayout | print to Hyprland logs the current layout | `none` | `hyprctl printlayout` | diff --git a/include/VirtualDesk.hpp b/include/VirtualDesk.hpp index 0f0ec38..4bd1263 100644 --- a/include/VirtualDesk.hpp +++ b/include/VirtualDesk.hpp @@ -11,8 +11,10 @@ #include "globals.hpp" #include "utils.hpp" #include +#include typedef std::unordered_map WorkspaceMap; +// map with CMonitor* -> hyprland workspace id typedef std::unordered_map Layout; typedef std::string MonitorName; @@ -25,21 +27,21 @@ typedef std::string MonitorName; class VirtualDesk { public: VirtualDesk(int id = 1, std::string name = "1"); - int id; - std::string name; - std::vector layouts; - - const Layout& activeLayout(const RememberLayoutConf&, const CMonitor* exclude = nullptr); - Layout& searchActiveLayout(const RememberLayoutConf&, const CMonitor* exclude = nullptr); - std::unordered_set setFromMonitors(const std::vector>&); - void changeWorkspaceOnMonitor(int, CMonitor*); - void invalidateActiveLayout(); - void resetLayout(); - CMonitor* deleteInvalidMonitor(const CMonitor*); - void deleteInvalidMonitorsOnActiveLayout(); - void deleteInvalidMonitorOnAllLayouts(const CMonitor*); - static std::shared_ptr firstAvailableMonitor(const std::vector>&); - bool isWorkspaceOnActiveLayout(int workspaceId); + int id; + std::string name; + std::vector layouts; + + const Layout& activeLayout(const RememberLayoutConf&, const CMonitor* exclude = nullptr); + Layout& searchActiveLayout(const RememberLayoutConf&, const CMonitor* exclude = nullptr); + std::unordered_set setFromMonitors(const std::vector>&); + void changeWorkspaceOnMonitor(int, CMonitor*); + void invalidateActiveLayout(); + void resetLayout(); + CMonitor* deleteInvalidMonitor(const CMonitor*); + void deleteInvalidMonitorsOnActiveLayout(); + void deleteInvalidMonitorOnAllLayouts(const CMonitor*); + static CSharedPointer firstAvailableMonitor(const std::vector>&); + bool isWorkspaceOnActiveLayout(int workspaceId); private: int m_activeLayout_idx; diff --git a/include/utils.hpp b/include/utils.hpp index 29f9b3d..bf2c720 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -5,6 +5,7 @@ #include #include "globals.hpp" #include +#include #include #include @@ -36,6 +37,7 @@ const std::string BACKCYCLE_DISPATCH_STR = "backcyclevdesks"; const std::string RESET_VDESK_DISPATCH_STR = "vdeskreset"; const std::string PRINTDESK_DISPATCH_STR = "printdesk"; +const std::string PRINTSTATE_DISPATCH_STR = "printstate"; const std::string PRINTLAYOUT_DISPATCH_STR = "printlayout"; const std::string REMEMBER_NONE = "none"; @@ -48,17 +50,17 @@ enum RememberLayoutConf { monitors = 2 }; -RememberLayoutConf layoutConfFromInt(const int64_t); -RememberLayoutConf layoutConfFromString(const std::string& conf); -void printLog(std::string s, LogLevel level = INFO); +RememberLayoutConf layoutConfFromInt(const int64_t); +RememberLayoutConf layoutConfFromString(const std::string& conf); +void printLog(std::string s, LogLevel level = INFO); -std::string parseMoveDispatch(std::string& arg); -bool extractBool(std::string& arg); -std::vector> currentlyEnabledMonitors(const CMonitor* exclude = nullptr); +std::string parseMoveDispatch(std::string& arg); +bool extractBool(std::string& arg); +std::vector> currentlyEnabledMonitors(const CMonitor* exclude = nullptr); -std::string ltrim(const std::string& s); -std::string rtrim(const std::string& s); -std::string trim(const std::string& s); +std::string ltrim(const std::string& s); +std::string rtrim(const std::string& s); +std::string trim(const std::string& s); -bool isVerbose(); +bool isVerbose(); #endif diff --git a/src/VirtualDesk.cpp b/src/VirtualDesk.cpp index 09d6014..80e2f4a 100644 --- a/src/VirtualDesk.cpp +++ b/src/VirtualDesk.cpp @@ -117,9 +117,9 @@ void VirtualDesk::deleteInvalidMonitorsOnActiveLayout() { } } -std::shared_ptr VirtualDesk::firstAvailableMonitor(const std::vector>& enabledMonitors) { - int n = INT_MAX; - std::shared_ptr newMonitor; +CSharedPointer VirtualDesk::firstAvailableMonitor(const std::vector>& enabledMonitors) { + int n = INT_MAX; + CSharedPointer newMonitor; for (auto mon : currentlyEnabledMonitors()) { auto n_on_mon = g_pCompositor->getWindowsOnWorkspace(mon->activeWorkspaceID()); if (n_on_mon < n) { @@ -160,7 +160,7 @@ void VirtualDesk::checkAndAdaptLayout(Layout* layout, const CMonitor* exclude) { } } -std::unordered_set VirtualDesk::setFromMonitors(const std::vector>& monitors) { +std::unordered_set VirtualDesk::setFromMonitors(const std::vector>& monitors) { std::unordered_set set; std::transform(monitors.begin(), monitors.end(), std::inserter(set, set.begin()), [](auto mon) { return monitorDesc(mon.get()); }); return set; diff --git a/src/VirtualDeskManager.cpp b/src/VirtualDeskManager.cpp index 9f5b63b..e48c4ce 100644 --- a/src/VirtualDeskManager.cpp +++ b/src/VirtualDeskManager.cpp @@ -112,7 +112,7 @@ int VirtualDeskManager::moveToDesk(std::string& arg, int vdeskId) { if (isVerbose()) printLog("creating new vdesk with id " + std::to_string(vdeskId)); - auto vdesk = getOrCreateVdesk(vdeskId); + auto vdesk = getOrCreateVdesk(vdeskId); PHLWINDOW window = g_pCompositor->getWindowByRegex(arg); if (!window) { @@ -178,7 +178,7 @@ void VirtualDeskManager::cycleWorkspaces() { return; auto n_monitors = g_pCompositor->m_vMonitors.size(); - CMonitor* currentMonitor = g_pCompositor->m_pLastMonitor; + CMonitor* currentMonitor = g_pCompositor->m_pLastMonitor.get(); // TODO: implement for more than two monitors as well. // This probably requires to compute monitors position @@ -281,15 +281,15 @@ void VirtualDeskManager::invalidateAllLayouts() { } CMonitor* VirtualDeskManager::getCurrentMonitor() { - CMonitor* currentMonitor = g_pCompositor->m_pLastMonitor; + CMonitor* currentMonitor = g_pCompositor->m_pLastMonitor.get(); // This can happen when we receive the "on disconnect" signal // let's just take first monitor we can find if (currentMonitor && (!currentMonitor->m_bEnabled || !currentMonitor->output)) { - for (std::shared_ptr mon : g_pCompositor->m_vMonitors) { + for (auto mon : g_pCompositor->m_vMonitors) { if (mon->m_bEnabled && mon->output) return mon.get(); } return nullptr; } return currentMonitor; -} +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index bd7433c..2d0be9d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "globals.hpp" #include "VirtualDeskManager.hpp" @@ -14,21 +15,21 @@ #include #include -static std::shared_ptr onWorkspaceChangeHook = nullptr; -static std::shared_ptr onWindowOpenHook = nullptr; -static std::shared_ptr onConfigReloadedHook = nullptr; +static CSharedPointer onWorkspaceChangeHook = nullptr; +static CSharedPointer onWindowOpenHook = nullptr; +static CSharedPointer onConfigReloadedHook = nullptr; -inline CFunctionHook* g_pMonitorConnectHook = nullptr; -inline CFunctionHook* g_pMonitorDisconnectHook = nullptr; -typedef void (*origMonitorConnect)(void*, bool); -typedef void (*origMonitorDisconnect)(void*, bool); +inline CFunctionHook* g_pMonitorConnectHook = nullptr; +inline CFunctionHook* g_pMonitorDisconnectHook = nullptr; +typedef void (*origMonitorConnect)(void*, bool); +typedef void (*origMonitorDisconnect)(void*, bool); -std::unique_ptr manager = std::make_unique(); -std::vector stickyRules; -bool notifiedInit = false; -bool monitorLayoutChanging = false; +std::unique_ptr manager = std::make_unique(); +std::vector stickyRules; +bool notifiedInit = false; +bool monitorLayoutChanging = false; -void parseNamesConf(std::string& conf) { +void parseNamesConf(std::string& conf) { size_t pos; size_t delim; std::string rule; @@ -173,6 +174,60 @@ std::string printVDeskDispatch(eHyprCtlOutputFormat format, std::string arg) { return ""; } +std::string printStateDispatch(eHyprCtlOutputFormat format, std::string arg) { + std::string out; + if (format == eHyprCtlOutputFormat::FORMAT_NORMAL) { + out += "Virtual desks\n"; + int index = 0; + for(auto const& [vdeskId, desk] : manager->vdesksMap) { + unsigned int windows = 0; + std::string workspaces; + bool first = true; + for(auto const& [monitor, workspaceId] : desk->activeLayout(manager->conf)) { + windows += g_pCompositor->getWindowsOnWorkspace(workspaceId); + if(!first) workspaces += ", "; + else first = false; + workspaces += std::format("{}", workspaceId); + } + out += std::format( + "- {}: {}\n Focused: {}\n Populated: {}\n Workspaces: {}\n Windows: {}\n", + desk->name, + desk->id, + manager->activeVdesk().get() == desk.get(), + windows > 0, + workspaces, + windows + ); + if(index++ < manager->vdesksMap.size() - 1) out += "\n"; + } + } else if(format == eHyprCtlOutputFormat::FORMAT_JSON) { + std::string vdesks; + int index = 0; + for(auto const& [vdeskId, desk] : manager->vdesksMap) { + unsigned int windows = 0; + std::string workspaces; + bool first = true; + for(auto const& [monitor, workspaceId] : desk->activeLayout(manager->conf)) { + windows += g_pCompositor->getWindowsOnWorkspace(workspaceId); + if(!first) workspaces += ", "; + else first = false; + workspaces += std::format("{}", workspaceId); + } + vdesks += std::format(R"#({{ + "id": {}, + "name": "{}", + "focused": {}, + "populated": {}, + "workspaces": [{}], + "windows": {} + }})#", vdeskId, desk->name, manager->activeVdesk().get() == desk.get(), windows > 0, workspaces, windows); + if(index++ < manager->vdesksMap.size() - 1) vdesks += ","; + } + out += std::format(R"#([{}])#", vdesks); + } + return out; +} + std::string printLayoutDispatch(eHyprCtlOutputFormat format, std::string arg) { auto activeDesk = manager->activeVdesk(); auto layout = activeDesk->activeLayout(manager->conf); @@ -233,7 +288,7 @@ void onWorkspaceChange(void*, SCallbackInfo&, std::any val) { void onWindowOpen(void*, SCallbackInfo&, std::any val) { PHLWINDOW window = std::any_cast(val); - int vdesk = StickyApps::matchRuleOnWindow(stickyRules, manager, window); + int vdesk = StickyApps::matchRuleOnWindow(stickyRules, manager, window); if (vdesk > 0) manager->changeActiveDesk(vdesk, true); } @@ -292,13 +347,21 @@ void registerHyprctlCommands() { if (!ptr) printLog(std::format("Failed to register hyprctl command: {}", PRINTLAYOUT_DISPATCH_STR)); + // Register printstate + cmd.name = PRINTSTATE_DISPATCH_STR; + cmd.fn = printStateDispatch; + cmd.exact = true; + ptr = HyprlandAPI::registerHyprCtlCommand(PHANDLE, cmd); + if (!ptr) + printLog(std::format("Failed to register hyprctl command: {}", PRINTSTATE_DISPATCH_STR)); + // Register printdesk cmd.name = PRINTDESK_DISPATCH_STR; cmd.fn = printVDeskDispatch; cmd.exact = false; ptr = HyprlandAPI::registerHyprCtlCommand(PHANDLE, cmd); if (!ptr) - printLog(std::format("Failed to register hyprctl command: {}", VDESK_DISPATCH_STR)); + printLog(std::format("Failed to register hyprctl command: {}", PRINTDESK_DISPATCH_STR)); } // Do NOT change this function. diff --git a/src/sticky_apps.cpp b/src/sticky_apps.cpp index 339ac62..8b5a492 100644 --- a/src/sticky_apps.cpp +++ b/src/sticky_apps.cpp @@ -65,11 +65,11 @@ int StickyApps::matchRuleOnWindow(const std::vector& rules, std::un const std::string StickyApps::extractProperty(const SStickyRule& rule, PHLWINDOW window) { if (rule.property == TITLE) { - return g_pXWaylandManager->getTitle(window); + return window->m_szTitle; } else if (rule.property == INITIAL_TITLE) { return window->m_szInitialTitle; } else if (rule.property == CLASS) { - return g_pXWaylandManager->getAppIDClass(window); + return window->m_szClass; } else if (rule.property == INITIAL_CLASS) { return window->m_szInitialClass; } diff --git a/src/utils.cpp b/src/utils.cpp index 48f89a0..4abfed5 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -58,8 +58,8 @@ bool isVerbose() { return **PVERBOSELOGS; } -std::vector> currentlyEnabledMonitors(const CMonitor* exclude) { - std::vector> monitors; +std::vector> currentlyEnabledMonitors(const CMonitor* exclude) { + std::vector> monitors; std::copy_if(g_pCompositor->m_vMonitors.begin(), g_pCompositor->m_vMonitors.end(), std::back_inserter(monitors), [&](auto mon) { if (g_pCompositor->m_pUnsafeOutput && g_pCompositor->m_pUnsafeOutput->szName == mon->szName) return false;