From e3f4e050ceef3e96b1c5190fc46b33c918c19699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanis=C5=82aw=20Zag=C3=B3rowski?= Date: Tue, 28 Feb 2023 00:58:50 +0100 Subject: [PATCH] Initial commit --- .clang-format | 14 +++++ .gitignore | 2 + Makefile | 28 +++++++++ README.md | 43 ++++++++++++++ include/globals.hpp | 5 ++ src/main.cpp | 136 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 228 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 include/globals.hpp create mode 100644 src/main.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..26b3ece --- /dev/null +++ b/.clang-format @@ -0,0 +1,14 @@ +BasedOnStyle: LLVM +BreakBeforeBraces: Stroustrup +TabWidth: 4 +IndentWidth: 4 +UseTab: Never +AllowShortIfStatementsOnASingleLine: false +IndentCaseLabels: true +ColumnLimit: 200 +PointerAlignment: Left +AllowShortBlocksOnASingleLine: Empty +AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..67a882e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.so +compile_flags.txt diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..101544c --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +# compile with HYPRLAND_HEADERS= make all +# make sure that the path above is to the root hl repo directory, NOT src/ +# and that you have ran `make protocols` in the hl dir. + +PLUGIN_NAME=split-monitor-workspaces + +SOURCE_FILES=$(wildcard src/*.cpp) + +.PHONY: clean clangd + +all: check_env $(PLUGIN_NAME).so + +install: all + cp $(PLUGIN_NAME).so ${HOME}/.local/share/hyprload/plugins/bin + +check_env: +ifndef HYPRLAND_HEADERS + $(error HYPRLAND_HEADERS is undefined! Please set it to the path to the root of the configured Hyprland repo) +endif + +$(PLUGIN_NAME).so: $(SOURCE_FILES) $(INCLUDE_FILES) + g++ -shared -fPIC --no-gnu-unique $(SOURCE_FILES) -o $(PLUGIN_NAME).so -g -I "/usr/include/pixman-1" -I "/usr/include/libdrm" -I "${HYPRLAND_HEADERS}" -Iinclude -std=c++23 + +clean: + rm ./examplePlugin.so + +clangd: + printf "%b" "-I/usr/include/pixman-1\n-I/usr/include/libdrm\n-I${HYPRLAND_HEADERS}\n-Iinclude\n-std=c++2b" > compile_flags.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..bb0b455 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# split-monitor-workspaces +A small plugin to provide `awesome`/`dwm`-like behavior with workspaces: split them between monitors and provide independent numbering + +# Installing +Since Hyprland plugins don't have ABI guarantees, you *should* download the Hyprland source and compile it if you plan to use plugins. +This ensures the compiler version is the same between the Hyprland build you're running, and the plugins you are using. + +The guide on compiling and installing Hyprland manually is on the [wiki](http://wiki.hyprland.org/Getting-Started/Installation/#manual-manual-build) + +## Using [hyprload](https://github.com/Duckonaut/hyprload) +1. Export the `HYPRLAND_HEADERS` variable to point to the root directory of the Hyprland repo + - `export HYPRLAND_HEADERS="$HOME/repos/Hyprland"` +2. Install + - `make install` + +## Manual installation +1. Export the `HYPRLAND_HEADERS` variable to point to the root directory of the Hyprland repo + - `export HYPRLAND_HEADERS="$HOME/repos/Hyprland"` +2. Compile + - `make all` +3. Add this line to the bottom of your hyprland config + - `exec-once=hyprctl plugin load ` + + +# Usage +The plugin provides drop-in replacements for workspace-related commands +| Normal | Replacement | +|-----------------------|-------------------------------| +| workspace | split-workspace | +| movetoworkspace | split-movetoworkspace | +| movetoworkspacesilent | split-movetoworkspacesilent | + +It also provides the following config values +| Name | Type | Default | Description | +|-------------------------------------------|-----------|-----------|-----------------------------------------------| +| `plugin:split-monitor-workspaces:count` | int | 10 | How many workspaces to bind to the monitor | + +Keep in mind that if you're using, for example, the `wlr/workspaces` widgets in [waybar](https://github.com/Alexays/Waybar), this will require a change to your config. You should set `all-outputs` to `false`, and adjust the icon mapping. + +If your workspace-per-monitor count is 10, the first monitor will have workspaces 1-10, the second 11-20 and so on. They will be accessed via numbers 1-10 while your mouse is on a given monitor. + +# Special thanks +- [hyprsome](https://github.com/sopa0/hyprsome): An earlier project of similar nature diff --git a/include/globals.hpp b/include/globals.hpp new file mode 100644 index 0000000..3b61032 --- /dev/null +++ b/include/globals.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +inline HANDLE PHANDLE = nullptr; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..f70c836 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,136 @@ +#define WLR_USE_UNSTABLE +#include "src/helpers/Color.hpp" +#include "src/managers/KeybindManager.hpp" + +#include "globals.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include + +const std::string s_workspaceCount = "plugin:split-monitor-workspaces:count"; +const CColor s_pluginColor = {0x61 / 255.0f, 0xAF / 255.0f, 0xEF / 255.0f, 1.0f}; + +std::map> g_vMonitorWorkspaceMap; + +static HOOK_CALLBACK_FN* e_monitorAddedHandle = nullptr; +static HOOK_CALLBACK_FN* e_monitorRemovedHandle = nullptr; + +const std::string& getWorkspaceFromMonitor(CMonitor* monitor, const std::string& workspace) +{ + int workspaceIndex = std::stoi(workspace); + if (workspaceIndex - 1 < 0) { + return workspace; + } + + if (workspaceIndex - 1 >= g_vMonitorWorkspaceMap[monitor->ID].size()) { + return workspace; + } + + return g_vMonitorWorkspaceMap[monitor->ID][workspaceIndex - 1]; +} + +void monitorWorkspace(std::string workspace) +{ + CMonitor* monitor = g_pCompositor->getMonitorFromCursor(); + int workspaceCount = HyprlandAPI::getConfigValue(PHANDLE, s_workspaceCount)->intValue; + + HyprlandAPI::invokeHyprctlCommand("dispatch", "workspace " + getWorkspaceFromMonitor(monitor, workspace)); +} + +void monitorMoveToWorkspace(std::string workspace) +{ + CMonitor* monitor = g_pCompositor->getMonitorFromCursor(); + int workspaceCount = HyprlandAPI::getConfigValue(PHANDLE, s_workspaceCount)->intValue; + + HyprlandAPI::invokeHyprctlCommand("dispatch", "movetoworkspace " + getWorkspaceFromMonitor(monitor, workspace)); +} + +void monitorMoveToWorkspaceSilent(std::string workspace) +{ + CMonitor* monitor = g_pCompositor->getMonitorFromCursor(); + int workspaceCount = HyprlandAPI::getConfigValue(PHANDLE, s_workspaceCount)->intValue; + + HyprlandAPI::invokeHyprctlCommand("dispatch", "movetoworkspacesilent " + getWorkspaceFromMonitor(monitor, workspace)); +} + +void mapWorkspacesToMonitors() +{ + g_vMonitorWorkspaceMap.clear(); + + int workspaceIndex = 1; + + for (auto& monitor : g_pCompositor->m_vMonitors) { + int workspaceCount = g_pConfigManager->getConfigValuePtrSafe(s_workspaceCount)->intValue; + std::string logMessage = + "[split-monitor-workspaces] Mapping workspaces " + std::to_string(workspaceIndex) + "-" + std::to_string(workspaceIndex + workspaceCount - 1) + " to monitor " + monitor->szName; + + HyprlandAPI::addNotification(PHANDLE, logMessage, s_pluginColor, 5000); + + for (int i = workspaceIndex; i < workspaceIndex + workspaceCount; i++) { + std::string workspaceName = std::to_string(i); + g_vMonitorWorkspaceMap[monitor->ID].push_back(workspaceName); + HyprlandAPI::invokeHyprctlCommand("keyword", "wsbind " + workspaceName + "," + monitor->szName); + CWorkspace* workspace = g_pCompositor->getWorkspaceByName(workspaceName); + + if (workspace != nullptr) { + g_pCompositor->moveWorkspaceToMonitor(workspace, monitor.get()); + } + } + + workspaceIndex += workspaceCount; + } +} + +void refreshMapping(void*, std::any value) +{ + mapWorkspacesToMonitors(); +} + +// Do NOT change this function. +APICALL EXPORT std::string PLUGIN_API_VERSION() +{ + return HYPRLAND_API_VERSION; +} + +APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) +{ + PHANDLE = handle; + + HyprlandAPI::addConfigValue(PHANDLE, s_workspaceCount, SConfigValue{.intValue = 10}); + + HyprlandAPI::addDispatcher(PHANDLE, "split-workspace", monitorWorkspace); + HyprlandAPI::addDispatcher(PHANDLE, "split-movetoworkspace", monitorMoveToWorkspace); + HyprlandAPI::addDispatcher(PHANDLE, "split-movetoworkspacesilent", monitorMoveToWorkspaceSilent); + + HyprlandAPI::reloadConfig(); + + mapWorkspacesToMonitors(); + + HyprlandAPI::addNotification(PHANDLE, "[split-monitor-workspaces] Initialized successfully!", s_pluginColor, 5000); + + e_monitorAddedHandle = HyprlandAPI::registerCallbackDynamic(PHANDLE, "monitorAdded", refreshMapping); + e_monitorRemovedHandle = HyprlandAPI::registerCallbackDynamic(PHANDLE, "monitorRemoved", refreshMapping); + + return {"split-monitor-workspaces", "Split monitor workspace namespaces", "Duckonaut", "1.0"}; +} + +APICALL EXPORT void PLUGIN_EXIT() +{ + HyprlandAPI::removeDispatcher(PHANDLE, "split-workspace"); + HyprlandAPI::removeDispatcher(PHANDLE, "split-movetoworkspace"); + HyprlandAPI::removeDispatcher(PHANDLE, "split-movetoworkspacesilent"); + + HyprlandAPI::unregisterCallback(PHANDLE, e_monitorAddedHandle); + HyprlandAPI::unregisterCallback(PHANDLE, e_monitorRemovedHandle); + + HyprlandAPI::addNotification(PHANDLE, "[split-monitor-workspaces] Unloaded successfully!", s_pluginColor, 5000); + + g_vMonitorWorkspaceMap.clear(); +}