Skip to content

Commit

Permalink
Merge pull request #2 from levnikmyskin/feature/remember_workspace_mo…
Browse files Browse the repository at this point in the history
…nitor

New virtual desk 2.0b.
  • Loading branch information
levnikmyskin authored Oct 9, 2023
2 parents af767d8 + da774a7 commit 7d62853
Show file tree
Hide file tree
Showing 10 changed files with 755 additions and 185 deletions.
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ check_env:
$(PLUGIN_NAME).so: $(SOURCE_FILES) $(INCLUDE_FILES)
g++ -shared $(COMPILE_FLAGS) $(COMPILE_DEFINES) $(SOURCE_FILES) -o $(PLUGIN_NAME).so

debug: $(SOURCE_FILES) $(INCLUDE_FILES)
g++ -DDEBUG -shared $(COMPILE_FLAGS) $(COMPILE_DEFINES) $(SOURCE_FILES) -o $(PLUGIN_NAME).so

clean:
rm -f ./$(PLUGIN_NAME).so

Expand Down
85 changes: 67 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,20 @@ multiple screens, this plugin basically replicates that
functionality.
Taking the previous example:
- You will be on virtual desktop 1. Let's say you open your web browser on your first screen and an IDE on your second screen;
- When you switch to virtual desktop 2, both screens will switch to empty workspaces. Let's say here you open your email client and your favourite chat applicaiton;
- When you switch to virtual desktop 2, both screens will switch to empty workspaces. Let's say here you open your email client and your favourite chat application;
- If you switch back to virtual desktop 1, you will get back your web browser and the IDE on screen 1 and 2; and viceversa when you go back to virtual desktop 2.

**WARNING**: the main branch is currently on `virtual-desktops` v. 2.0b, which is beta software. If you're experiencing any bug, please open an issue here and then consider switching to v. 1.0

## How does this work?

### It's just workspaces, really
Internally, this simply ties _n_ workspaces to your _n_ screens, for each virtual desktop. That is, on virtual desktop 1 you will always have workspace 1 on screen 1 and workspace 2 on screen 2;
on virtual desktop 2, you will always have workspace 3 on screen 1 and workspace 4 on screen 2, and so on.
Internally, this simply ties _n_ workspaces to your _n_ screens, for each virtual desktop. That is, on virtual desktop 1 you will have workspace 1 on screen 1 and workspace 2 on screen 2;
on virtual desktop 2, you will have workspace 3 on screen 1 and workspace 4 on screen 2, and so on.

**Notice**: screen 1 and screen 2 are not necessarily what you expect your first and second screen to be, i.e., screen 1 is not necessarily your left screen, and screen 2 is not necessarily your right screen.
However, if you focus another workspace on a given virtual desktop, the plugin will remember this and you will keep this layout (see [Layouts](#Layouts)).

**Notice**: screen 1 and screen 2 are not necessarily what you expect your first and second screen to be, e.g., screen 1 is not necessarily your left screen, and screen 2 is not necessarily your right screen.

### Hyprctl dispatchers

Expand All @@ -36,6 +40,10 @@ This plugin exposes a few hyprctl dispatchers:
| printdesk (vdesk)| Prints to Hyprland log the specified vdesk or the currently active vdesk* (if no argument is given) | optional vdesk, see below | `printdesk` or `printdesk 2` or `printdesk coding`|
| movetodesk vdesk(, window) | Moves the active/selected window to the specified `vdesk` | `vdesk`, optional window, see below | `movetodesk 2` or `movetodesk 2,title:kitty` |
| movetodesksilent vdesk(, window) | same as `movetodesk`, but doesn't switch to desk | same as above | same as above |
| vdeskreset (vdesk) | reset layouts on `vdesk` or on all vdesks if no argument is given (see [Layouts](#Layouts)) | optional vdesk, see below | `vdeskreset` or `vdeskreset 2` or `vdeskreset coding` |
| nextdesk | go to next vdesk. Creates it if it doesn't exist | `none` | `nextdesk` |
| cyclevdesks | cycle between currently existing vdesks. Goes back to vdesk 1 if next vdesk does not exist | `none` | `cyclevdesks` |
| printlayout | print to Hyprland logs the current layout | `none` | `printlayout` |

\*`printdesk` currently prints to the active Hyprland session log, thus probably not really useful.

Expand All @@ -50,15 +58,11 @@ plugin will remember this association even if Hyprland kills the related workspa
The `movetodesk` and `movetodesksilent` dispatchers work similarly to
Hyprland's `movetoworkspace` and `movetoworkspacesilent` dispatchers. See [Hyprland's wiki](https://wiki.hyprland.org/Configuring/Dispatchers/#list-of-dispatchers). Of course, make sure to use the `vdesk` syntax above instead of Hyprland's.

#### Mix with Hyprland native workspaces, but know what you're doing
You can mix this with Hyprland native workspaces functionality, but beware
of how this plugin manages workspaces. Every vdesk will _always_ operate on the _same_ set of workspaces. If you're on vdesk 1, with 2 monitors, and
switch monitor 2 to workspace 4, switching to vdesk 2 will always show workspaces 3 and 4 (and switching back to vdesk 1 will show workspace 1 and
2).
Also notice that you can use `hyprctl dispatch vdesk n`, even if you have
#### Mix with Hyprland native workspaces
You can use `hyprctl dispatch vdesk n`, even if you have
no secondary screen connected at the moment (the behaviour would be identical to native workspaces). Also, I would REMOVE
any workspace related configuration, such as `wsbind`. If you want to leverage [workspace-specific rules](https://wiki.hyprland.org/Configuring/Workspace-Rules/), you can: workspaces are always assigned
to the same vdesk given the same number of monitors. For instance:
to the same vdesk given the same number of monitors, unless you focus (e.g. with hyprctl) another workspace (see [Layouts](#Layouts)). For instance:
- Given two monitors:
- vdesk 1 has workspaces 1 and 2;
- vdesk 2 has workspaces 3 and 4, and so on;
Expand All @@ -74,14 +78,16 @@ The vdesk a workspace will end up to is easily computed by doing `ceil(workspace

This plugin exposes a few configuration options, under the `plugin:virtual-desktops:` category, namely:

| Name | type | example|
|------|------|--------|
| names | map[int:string], see below| `1:coding, 2:internet, 3:mail and chats`|
| cycleworkspaces | `0` or `1`| `1`|
| Name | description | type | example|
|------|-------------|------|--------|
| names | map a vdesk id with a name | map[int:string], see below| `names = 1:coding, 2:internet, 3:mail and chats`|
| cycleworkspaces | if set to 1 and switching to the currently active vdesk, workspaces will be swapped between your monitors (see [swapactiveworkspaces](https://wiki.hyprland.org/Configuring/Dispatchers/#list-of-dispatchers))| `0` or `1`| `cycleworkspaces = 1`|
| rememberlayout | chooses how layouts should be remembered (see [Layouts](#Layouts)), defaults to `size` | `none`, `size` or `monitors` | `remember = size` |
| notifyinit | chooses whether to display the startup notification, defaults to 1 | `0` or `1` | `notifyinit = 0` |
| verbose_logging | whether to log more stuff, defaults to 0 | `0` or `1` | `verbose_logging = 0` |

* The `names` config option maps virtual desktop IDs to a name (you can then use this with the hyprctl [dispatcher](#hyprctl-dispatchers));
* If `cycleworkspaces` is set to `1`, and you switch to the currently active virtual desktop, this swaps the workspaces of your two monitors (see hyprctl [swapactiveworkspaces](https://wiki.hyprland.org/Configuring/Dispatchers/#list-of-dispatchers)).
THIS CURRENTLY DOES NOT WORK WITH MORE THAN 2 MONITORS. If you need this feature, please feel welcome to submit a PR ^^ (see also the `dev` branch).
* The `names` config option maps virtual desktop IDs to a name (you can then use this with the hyprctl [dispatchers](#hyprctl-dispatchers));
* `cycleworkspaces`: THIS CURRENTLY DOES NOT WORK WITH MORE THAN 2 MONITORS. If you need this feature, please feel welcome to submit a PR ^^.


#### Example config
Expand All @@ -90,10 +96,53 @@ plugin {
virtual-desktops {
names = 1:coding, 2:internet, 3:mail and chats
cycleworkspaces = 1
rememberlayout = size
notifyinit = 0
verbose_logging = 0
}
}
```

## Layouts
Version 2.0 of this plugin introduced the concept of a *layout*, with the meaning of "a specific combination of workspaces on a (more or less) specific combination of monitors".
In other words, `virtual-desktops` remembers if you focused another workspace on your vdesk, even if you switch to another vdesk and then come back to this one

#### Example
Say you have 2 monitors A and B, and you're on vdesk 1:
- On monitor A you have workspace 1, and on monitor B you have workspace 2;
- Now, say you focus workspace 4 with `hyprctl dispatch workspace 4` on monitor B.
- If you switch to vdesk 2 and back to vdesk 1, you will see workspace 4 on monitor B instead of workspace 2.

**Notice** that, in this case, workspace 4 would also be shown on vdesk 2.

### Layouts are cached and restored if you disconnect/reconnect monitors
Internally, every vdesk will cache all the previous layouts. Once a monitor is connected/disconnected, the plugin will try to look for a previously existing layout for this configuration
and it will apply it.

#### Example
Continuing from the previous example, say you now disconnect monitor B and then you reconnect it, while being on vdesk 1:
- the previous layout will be matched and you will get back workspace 1 on monitor A and workspace 4 on monitor B.

This would work also if instead of disconnecting and reconnecting monitor B, you connected a third monitor C and then disconnect it.


### Choosing how to remember, or choosing to forget
Version 2.0 also introduced the `rememberlayout` config option: with this option, we can choose if we want to match a layout by number of monitors (`size`) or by unique monitor descriptions (`monitors`). There is also the third and final option to not remember layouts at all (`none`).


#### Example
Continuing from the previous example. Say we now disconnect monitor B and connect monitor C: our connected monitors are A and C.

- if `rememberlayout = size` (the default), the existing layout will still be matched, we will have workspace 1 on monitor A, workspace 4 on monitor C;
- if `rememberlayout = monitors`, a new layout will be created with defaults: workspace 1 on monitor A, workspace 2 on monitor C;
- if `rememberlayout = none`, same as above.

If we now disconnect monitor C and reconnect monitor B: our connected monitors are A and B.

- if `rememberlayout = size` (the default), the existing layout will be matched, we will have workspace 1 on monitor A, workspace 4 on monitor B;
- if `rememberlayout = monitors`, the existing layout will be matched, we will have workspace 1 on monitor A, workspace 4 on monitor B;
- if `rememberlayout = none`, a new layout will be created with defaults: workspace 1 on monitor A, workspace 2 on monitor C.


## Install
In order to use plugins, you should compile Hyprland yourself. See [Hyprland Wiki#Using Plugins](https://wiki.hyprland.org/Plugins/Using-Plugins/).
Expand Down
2 changes: 1 addition & 1 deletion hyprload.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[virtual-desktops]
description = "Virtual desktops"
version = "1.0.0"
version = "2.0b"
author = "LevMyskin"

[virtual-desktops.build]
Expand Down
46 changes: 46 additions & 0 deletions include/VirtualDesk.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#pragma once

#define VDESK_H

#include <string>
#include <unordered_map>
#include <unordered_set>

#include <src/helpers/Monitor.hpp>
#include "globals.hpp"
#include "utils.hpp"

typedef std::unordered_map<int, int> WorkspaceMap;
typedef std::unordered_map<std::string, int> Layout;
typedef std::string MonitorName;

/*
* Each virtual desk holds a list of layouts. Layouts remember which workspace was on which monitor
* when those exact monitors (or that exact number of monitors) is/was connected.
* VirtualDeskManager holds instead a map of vdesk_id -> virtual desk.
*/

class VirtualDesk {
public:
VirtualDesk(int id = 1, std::string name = "1");
int id;
std::string name;
std::vector<Layout> layouts;

const Layout& activeLayout(const RememberLayoutConf&);
Layout& searchActiveLayout(const RememberLayoutConf&);
std::unordered_set<std::string> setFromMonitors(const std::vector<std::shared_ptr<CMonitor>>&);
void changeWorkspaceOnMonitor(int, CMonitor*);
void invalidateActiveLayout();
void resetLayout();
void deleteInvalidMonitor(CMonitor*);
void deleteInvalidMonitorOnAllLayouts(CMonitor*);

private:
int m_activeLayout_idx;
bool activeIsValid = false;
Layout generateCurrentMonitorLayout();
std::vector<std::shared_ptr<CMonitor>> currentlyEnabledMonitors();
static std::string monitorDesc(const CMonitor&);
void checkAndAdaptLayout(Layout*);
};
35 changes: 35 additions & 0 deletions include/VirtualDeskManager.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@

#pragma once

#define VDESK_MANAGER_H

#include "VirtualDesk.hpp"

class VirtualDeskManager {

public:
VirtualDeskManager();
std::unordered_map<int, std::shared_ptr<VirtualDesk>> vdesksMap;
int prevDesk = -1;
std::unordered_map<int, std::string> vdeskNamesMap = {{1, "1"}};
RememberLayoutConf conf;
const std::shared_ptr<VirtualDesk>& activeVdesk();
void changeActiveDesk(std::string&, bool);
void changeActiveDesk(int, bool);
void previousDesk();
void nextDesk(bool cycle);
void applyCurrentVDesk();
int moveToDesk(std::string&);
void loadLayoutConf();
void invalidateAllLayouts();
void resetAllVdesks();
void resetVdesk(const std::string& arg);
void deleteInvalidMonitorsOnAllVdesks(CMonitor*);

private:
int m_activeDeskKey = 1;
bool confLoaded = false;
void cycleWorkspaces();
int getDeskIdFromName(const std::string& name, bool createIfNotFound = true);
CMonitor* getCurrentMonitor();
};
41 changes: 41 additions & 0 deletions include/utils.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#pragma once

#define UTILS_H

#include "src/debug/Log.hpp"
#include "globals.hpp"
#include "src/config/ConfigManager.hpp"
#include <string>

const std::string VIRTUALDESK_NAMES_CONF = "plugin:virtual-desktops:names";
const std::string CYCLEWORKSPACES_CONF = "plugin:virtual-desktops:cycleworkspaces";
const std::string REMEMBER_LAYOUT_CONF = "plugin:virtual-desktops:rememberlayout";
const std::string NOTIFY_INIT = "plugin:virtual-desktops:notifyinit";
const std::string VERBOSE_LOGS = "plugin:virtual-desktops:verbose_logging";
const std::string VDESK_DISPATCH_STR = "vdesk";
const std::string MOVETODESK_DISPATCH_STR = "movetodesk";
const std::string MOVETODESKSILENT_DISPATCH_STR = "movetodesksilent";
const std::string RESET_VDESK_DISPATCH_STR = "vdeskreset";
const std::string PREVDESK_DISPATCH_STR = "prevdesk";
const std::string NEXTDESK_DISPATCH_STR = "nextdesk";
const std::string PRINTDESK_DISPATCH_STR = "printdesk";
const std::string PRINTLAYOUT_DISPATCH_STR = "printlayout";
const std::string CYCLEVDESK_DISPATCH_STR = "cyclevdesks";

const std::string REMEMBER_NONE = "none";
const std::string REMEMBER_SIZE = "size";
const std::string REMEMBER_MONITORS = "monitors";

enum RememberLayoutConf {
none = 0,
size = 1,
monitors = 2
};

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 isVerbose();
Loading

0 comments on commit 7d62853

Please sign in to comment.