From 1210b5b7be4b18d77d8514881289518ac06000b1 Mon Sep 17 00:00:00 2001 From: ilusha Date: Fri, 6 Jun 2025 21:29:40 +0000 Subject: [PATCH] initial --- .gitignore | 7 + CMakeLists.txt | 29 +++ LICENSE | 21 ++ README.md | 352 +++++++++++++++++++++++++++++++++ default.nix | 20 ++ flake.lock | 424 ++++++++++++++++++++++++++++++++++++++++ flake.nix | 55 ++++++ hyprpm.toml | 26 +++ install.sh | 5 + src/DwindleSwitcher.cpp | 171 ++++++++++++++++ src/DwindleSwitcher.hpp | 23 +++ src/OverviewLayout.cpp | 245 +++++++++++++++++++++++ src/OverviewLayout.hpp | 82 ++++++++ src/OverviewManager.cpp | 77 ++++++++ src/OverviewManager.hpp | 40 ++++ src/Plugin.cpp | 30 +++ src/Plugin.hpp | 17 ++ src/main.cpp | 37 ++++ 18 files changed, 1661 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100644 default.nix create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 hyprpm.toml create mode 100644 install.sh create mode 100644 src/DwindleSwitcher.cpp create mode 100644 src/DwindleSwitcher.hpp create mode 100644 src/OverviewLayout.cpp create mode 100644 src/OverviewLayout.hpp create mode 100644 src/OverviewManager.cpp create mode 100644 src/OverviewManager.hpp create mode 100644 src/Plugin.cpp create mode 100644 src/Plugin.hpp create mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..591dd9d --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/result +/build +/.vscode +/.cache +*build* +*rel* +.idea \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..5678183 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.19) +project(Hycov VERSION "0.2") +set(CMAKE_CXX_STANDARD 23) +add_compile_definitions(WLR_USE_UNSTABLE) +add_compile_options(-g -Wall -Wextra -Wno-unused-parameter -Wno-unused-value -Wno-missing-field-initializers -Wno-narrowing -Wno-pointer-arith) + +# nix workaround +if(CMAKE_EXPORT_COMPILE_COMMANDS) + set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES + ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +endif() + +find_package(PkgConfig REQUIRED) +pkg_check_modules(DEPS REQUIRED hyprland pixman-1 libdrm pango pangocairo) + +add_library(hycov SHARED + src/main.cpp + src/Plugin.cpp + src/OverviewManager.cpp + src/OverviewLayout.cpp + src/DwindleSwitcher.cpp +) + +target_include_directories(hycov PRIVATE ${DEPS_INCLUDE_DIRS}) + +install(TARGETS hycov LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +# set(CMAKE_VERBOSE_MAKEFILE ON) +# set(CMAKE_EXPORT_COMPILE_COMMANDS ON) \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0c8a6ac --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 GreamMaoMao + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1141489 --- /dev/null +++ b/README.md @@ -0,0 +1,352 @@ +# update info + +**for hyprland v 0.4.0** +checkout to tag `v0.4.0` + +**for hyprland v0.45.0** +checkout to tag `v0.45.0` + +**for hyprland v0.45.2^** +in the `main` branch + +no more than hyprland v0.46.1 + +# hycov + +A Hyprland overview mode plugin, a new tiling WM workflow. + +> [!NOTE] +> This repository only maintains the hyprland version corresponding to each hycov release, please do not bring up the issue of non-release versions, because I do not have enough time to track every commit. + +### What can it do? + +- Hycov can tile all of your windows in a single workspace via grid layout. + +- After quitting the overview mode, hycov can perfectly recover a window's previous state (fullscreen, floating, size, positon, etc.) + +- Hycov supports a variety of trigger methods, such as touch pad gestures, hot corners, and keyboard shortcuts. + +- Supports multiple monitors. + +- You can change the way that Hycov focuses a window, whether directional or cyclical. (single-shortcut) + +https://github.com/DreamMaoMao/hycov/assets/30348075/59121362-21a8-4143-be95-72ce79ee8e95 + +Anyone is welcome to fork. If you end up improving the plugin, please let me know, and I'll be happy to use your fork. + +### Manual Installation + +> [!NOTE] +> +> 1. After Hycov is installed, you will need to logout, then log back in. This may happen automatically, but do not worry. This behaviour is normal. +> 2. Only supports hyprland source code after 2023-10-22, because the plugin requires this [commit](https://github.com/hyprwm/Hyprland/commit/a61eb7694df25a75f45502ed64b1536fda370c1d) in [hyprland](https://github.com/hyprwm/Hyprland). +> 3. Each release of hycov corresponds to each release of hyprland. If you are using a release version of hycov, but you are using the latest hyprland-git, this may not be available. + +##### Using meson and ninja: + +```shell +git clone https://github.com/DreamMaoMao/hycov.git +cd hycov +sudo meson setup build --prefix=/usr +sudo ninja -C build +sudo ninja -C build install # `libhycov.so` path: /usr/lib/libhycov.so +``` + +##### Using CMake: + +```shell +git clone https://github.com/DreamMaoMao/hycov.git +cd hycov +bash install.sh # `libhycov.so` path: /usr/lib/libhycov.so +``` + +##### Using hyprpm: + +```shell +hyprpm update +hyprpm add https://github.com/DreamMaoMao/hycov +hyprpm enable hycov +``` + +### Usage (hyprland.conf) + +```conf +# When entering overview mode, you can use left-button to jump, right-button to kill or use keybind + +# If you are installing hycov with hyprpm, you should comment out this +plugin = /usr/lib/libhycov.so + +# If you are installing hycov by manually compile , you should comment out this +exec-once = hyprpm reload + +# bind key to toggle overview (normal) +bind = ALT,tab,hycov:toggleoverview + +# bind key to toggle overview (force mode, not affected by `only_active_workspace` or `only_active_monitor`) +bind = ALT,grave,hycov:toggleoverview,forceall #grave key is the '~' key + +# bind key to toggle overview (force mode, not affected by `only_active_workspace` or `only_active_monitor`) +bind = ALT,c,hycov:toggleoverview,onlycurrentworkspace + +# bind key to toggle overview (shows all windows in one monitor, not affected by `only_active_workspace` or `only_active_monitor`) +bind = ALT,g,hycov:toggleoverview,forceallinone + +# The key binding for directional switch mode. +# Calculate the window closest to the direction to switch focus. +# This keybind is applicable not only to the overview, but also to the general layout. +bind=ALT,left,hycov:movefocus,l +bind=ALT,right,hycov:movefocus,r +bind=ALT,up,hycov:movefocus,u +bind=ALT,down,hycov:movefocus,d + +# if you want that focusmove can cross monitor, use this +bind=ALT,left,hycov:movefocus,leftcross +bind=ALT,right,hycov:movefocus,rightcross +bind=ALT,up,hycov:movefocus,upcross +bind=ALT,down,hycov:movefocus,downcross + +plugin { + hycov { + overview_gappo = 60 # gaps width from screen edge + overview_gappi = 24 # gaps width from clients + enable_hotarea = 1 # enable mouse cursor hotarea, when cursor enter hotarea, it will toggle overview + enable_click_action = 1 # enable mouse left button jump and right button kill in overview mode + hotarea_monitor = all # monitor name which hotarea is in, default is all + hotarea_pos = 1 # position of hotarea (1: bottom left, 2: bottom right, 3: top left, 4: top right) + hotarea_size = 10 # hotarea size, 10x10 + swipe_fingers = 4 # finger number of gesture,move any directory + move_focus_distance = 100 # distance for movefocus,only can use 3 finger to move + enable_gesture = 0 # enable gesture + auto_exit = 1 # enable auto exit when no client in overview + auto_fullscreen = 0 # auto make active window maximize after exit overview + only_active_workspace = 0 # only overview the active workspace + only_active_monitor = 0 # only overview the active monitor + enable_alt_release_exit = 0 # alt swith mode arg,see readme for detail + alt_replace_key = Alt_L # alt swith mode arg,see readme for detail + alt_toggle_auto_next = 0 # auto focus next window when toggle overview in alt swith mode + click_in_cursor = 1 # when click to jump,the target windwo is find by cursor, not the current foucus window. + hight_of_titlebar = 0 # height deviation of title bar height + show_special = 0 # show windwos in special workspace in overview. + raise_float_to_top = 1 # raise the window that is floating before to top after leave overview mode + } +} + +``` + +# suggested additional configuration + +- when `auto_fullscreen=1` is set, you can also set the border color to mark the maximize state and bind key to control fullscreen maximize state. + +``` +windowrulev2 = bordercolor rgb(158833),fullscreen:1 # set bordercolor to green if window is fullscreen maximize +# toggle fullscreen maximize +bind = ALT,a,fullscreen,1 +``` + +
+detail video + +https://github.com/DreamMaoMao/hycov/assets/30348075/15ba36c2-1782-4ae0-8ac1-d0ca98e01e0f + +
+ +- if you use the `hyprland/workspaces` module in waybar,you should change field {id} to {name}. It will let you know you are in overview mode. + +``` +"hyprland/workspaces": { + "format": "{name}", + "on-click":"activate", +}, +``` + +
+detail picture + +![image](https://github.com/DreamMaoMao/hycov/assets/30348075/332f4025-20c1-4a44-853b-1b5264df986e) +![image](https://github.com/DreamMaoMao/hycov/assets/30348075/500d9fd7-299b-48bc-ab72-146f263044a5) + +
+ +# Alt switch mode + +```conf +enable_alt_release_exit = 1 +alt_toggle_auto_next = 0 # auto focus next window when enter overview in alt mode +# alt_replace_key = Alt_L # If your MainKey of toggleoverview is ALt, you can ignore it +``` + +## operation + +such as `alt + tab`: + +- 1.`alt + tab` will enter overview when you not in overview(please hold alt,don't make it release) + +- 2.`alt + tab` will switch window focus circularly when you in overview. (please hold alt,don't make it release) + +- 3.when you release `alt` , it will auto exit overview. + +
+ If you don't want to use `alt` as MainKey in alt mode + +such as use `super` to repalce `alt` + +- 1.bind toggleoverview + +``` +bind = SUPER,tab,hycov:toggleoverview +``` + +- 2.use `alt_replace_key` to specify what is the detection key on release. + +``` +# use keyname +alt_replace_key = Super_L # Alt_L,Alt_R,Super_L,Super_R,Control_L,Control_R,Shift_L,Shift_R + +# use keycode +alt_replace_key = code:133 # use `xev` command to get keycode +``` + +
+ +### NixOS with home—manager + +```nix +# flake.nix + +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + + home-manager = { + url = "github:nix-community/home-manager"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + + hyprland.url = "github:hyprwm/Hyprland"; + + hycov={ + url = "github:DreamMaoMao/hycov"; + inputs.hyprland.follows = "hyprland"; + }; + }; + + outputs = { nixpkgs, home-manager, hyprland, hycov, ... }: + let + system = "x86_64-linux"; + pkgs = nixpkgs.legacyPackages.${system}; + in + { + homeConfigurations."user@hostname" = home-manager.lib.homeManagerConfiguration { + pkgs = nixpkgs.legacyPackages.x86_64-linux; + + modules = [ + hyprland.homeManagerModules.default + { + wayland.windowManager.hyprland = { + enable = true; + package = hyprland.packages."${pkgs.system}".hyprland; + plugins = [ + hycov.packages.${pkgs.system}.hycov + ]; + extraConfig = '' + bind = ALT,tab,hycov:toggleoverview + bind=ALT,left,hycov:movefocus,l + bind=ALT,right,hycov:movefocus,r + bind=ALT,up,hycov:movefocus,u + bind=ALT,down,hycov:movefocus,d + + plugin { + hycov { + overview_gappo = 60 #gaps width from screen + overview_gappi = 24 #gaps width from clients + hotarea_size = 10 #hotarea size in bottom left,10x10 + enable_hotarea = 1 # enable mouse cursor hotarea + } + } + '' + '' + # your othor config + ''; + }; + } + # ... + ]; + }; + }; +} +``` + +## Frequently Asked Questions + +- some config not work, or the plugin not work. + +``` +if you use install hycov at first time,please try logout and relogin again. +``` + +- The numbers on the waybar are confused + +``` +1.Please pull the latest waybar source code compilation, +this issue has been fixed in the waybar project, fix date (2023-10-27) + +2.Change the {id} field in hyprland/workspace field to {name} +``` + +- Compilation failure + +``` +Please pull the latest hyprland source code to compile and install. +The plugin relies on a hyprland pr,pr submission date (2023-10-21) +``` + +- Unable to load + +``` +Check whether hyprland has been updated, +and if so, please recompile hyprcov +``` + +- build fail with message `No such file or directory #include ` + +``` +#step1 +yay -R hyprland-git wlroots-git + +#step2 +sudo rm -rf /usr/include/hyprland +sudo rm -rf /usr/include/wlr +sudo rm -rf/usr/local/include/hyprland +sudo rm -rf /usr/local/include/wlr + +#step3 +yay -S wlroots-git hyprland-git +``` + +## Some cool use cases + +
+ hycov + [hyprland-easymotion](https://github.com/zakk4223/hyprland-easymotion) + +https://github.com/DreamMaoMao/hycov/assets/30348075/486b08f1-be0d-4647-90a3-2029961402cd + +```conf +bind = ALT,tab, exec, ~/.config/hypr/scripts/hycov-easymotion.sh +submap=__easymotionsubmap__ +bind = ALT, Tab, exec, ~/.config/hypr/scripts/hycov-easymotion.sh +submap=reset +``` + +```bash +#!/bin/bash + +workspace_name=$(hyprctl -j activeworkspace | jq -r '.name') + +if [ "$workspace_name" = "OVERVIEW" ]; then + hyprctl dispatch hycov:leaveoverview +else + hyprctl dispatch hycov:enteroverview + hyprctl dispatch 'easymotion action:hyprctl --batch "dispatch focuswindow address:{} ; dispatch hycov:leaveoverview"' +fi +``` + +
diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..3447ac0 --- /dev/null +++ b/default.nix @@ -0,0 +1,20 @@ +{ lib +, stdenv +, hyprland +}: +stdenv.mkDerivation { + pname = "hycov"; + version = "0.2"; + src = ./.; + + inherit (hyprland) nativeBuildInputs; + + buildInputs = [ hyprland ] ++ hyprland.buildInputs; + + meta = with lib; { + homepage = "https://github.com/DreamMaoMao/hycov"; + description = "clients overview for hyprland plugin"; + license = licenses.mit; + platforms = platforms.linux; + }; +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..00215b6 --- /dev/null +++ b/flake.lock @@ -0,0 +1,424 @@ +{ + "nodes": { + "aquamarine": { + "inputs": { + "hyprutils": [ + "hyprland", + "hyprutils" + ], + "hyprwayland-scanner": [ + "hyprland", + "hyprwayland-scanner" + ], + "nixpkgs": [ + "hyprland", + "nixpkgs" + ], + "systems": [ + "hyprland", + "systems" + ] + }, + "locked": { + "lastModified": 1734906446, + "narHash": "sha256-6OWluVE2A8xi+8V3jN9KA72RCgJjYdyyuLBUjxZ2q2U=", + "owner": "hyprwm", + "repo": "aquamarine", + "rev": "eecb74dc79bb6752a2a507e6edee3042390a6091", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "aquamarine", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "hyprland", + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "hyprcursor": { + "inputs": { + "hyprlang": [ + "hyprland", + "hyprlang" + ], + "nixpkgs": [ + "hyprland", + "nixpkgs" + ], + "systems": [ + "hyprland", + "systems" + ] + }, + "locked": { + "lastModified": 1734906540, + "narHash": "sha256-vQ/L9hZFezC0LquLo4TWXkyniWtYBlFHAKIsDc7PYJE=", + "owner": "hyprwm", + "repo": "hyprcursor", + "rev": "69270ba8f057d55b0e6c2dca0e165d652856e613", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "hyprcursor", + "type": "github" + } + }, + "hyprgraphics": { + "inputs": { + "hyprutils": [ + "hyprland", + "hyprutils" + ], + "nixpkgs": [ + "hyprland", + "nixpkgs" + ], + "systems": [ + "hyprland", + "systems" + ] + }, + "locked": { + "lastModified": 1734906236, + "narHash": "sha256-vH/ysV2ONGQgYZPtcJKwc8jJivzyVxru2aaOxC20ZOE=", + "owner": "hyprwm", + "repo": "hyprgraphics", + "rev": "6dea3fba08fd704dd624b6d4b261638fb4003c9c", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "hyprgraphics", + "type": "github" + } + }, + "hyprland": { + "inputs": { + "aquamarine": "aquamarine", + "hyprcursor": "hyprcursor", + "hyprgraphics": "hyprgraphics", + "hyprland-protocols": "hyprland-protocols", + "hyprland-qtutils": "hyprland-qtutils", + "hyprlang": "hyprlang", + "hyprutils": "hyprutils", + "hyprwayland-scanner": "hyprwayland-scanner", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": "pre-commit-hooks", + "systems": "systems", + "xdph": "xdph" + }, + "locked": { + "lastModified": 1735585949, + "narHash": "sha256-vCGG4tGMvzCzz+ZIsiNtpoFW9+f+itYLTAVW41qk/Hk=", + "ref": "refs/heads/main", + "rev": "1989b0049f7fb714a2417dfb14d6b4f3d2a079d3", + "revCount": 5607, + "submodules": true, + "type": "git", + "url": "https://github.com/hyprwm/Hyprland" + }, + "original": { + "submodules": true, + "type": "git", + "url": "https://github.com/hyprwm/Hyprland" + } + }, + "hyprland-protocols": { + "inputs": { + "nixpkgs": [ + "hyprland", + "nixpkgs" + ], + "systems": [ + "hyprland", + "systems" + ] + }, + "locked": { + "lastModified": 1728345020, + "narHash": "sha256-xGbkc7U/Roe0/Cv3iKlzijIaFBNguasI31ynL2IlEoM=", + "owner": "hyprwm", + "repo": "hyprland-protocols", + "rev": "a7c183800e74f337753de186522b9017a07a8cee", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "hyprland-protocols", + "type": "github" + } + }, + "hyprland-qtutils": { + "inputs": { + "hyprutils": [ + "hyprland", + "hyprutils" + ], + "nixpkgs": [ + "hyprland", + "nixpkgs" + ], + "systems": [ + "hyprland", + "systems" + ] + }, + "locked": { + "lastModified": 1734906472, + "narHash": "sha256-pWPRv/GA/X/iAwoE6gMNUqn/ZeJX1IeLPRpZI0tTPK0=", + "owner": "hyprwm", + "repo": "hyprland-qtutils", + "rev": "c77109d7e1ddbcdb87cafd32ce411f76328ae152", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "hyprland-qtutils", + "type": "github" + } + }, + "hyprlang": { + "inputs": { + "hyprutils": [ + "hyprland", + "hyprutils" + ], + "nixpkgs": [ + "hyprland", + "nixpkgs" + ], + "systems": [ + "hyprland", + "systems" + ] + }, + "locked": { + "lastModified": 1734906259, + "narHash": "sha256-P79t/7HbACO4/PuJBroGpTptvCWJtXTv+gWsF+sM6MI=", + "owner": "hyprwm", + "repo": "hyprlang", + "rev": "0404833ea18d543df44df935ebf1b497310eb046", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "hyprlang", + "type": "github" + } + }, + "hyprutils": { + "inputs": { + "nixpkgs": [ + "hyprland", + "nixpkgs" + ], + "systems": [ + "hyprland", + "systems" + ] + }, + "locked": { + "lastModified": 1735316583, + "narHash": "sha256-AiiUwHWHfEdpFzXy7l1x3zInCUa1xcRMrbZ1XRSkzwU=", + "owner": "hyprwm", + "repo": "hyprutils", + "rev": "8f15d45b120b33712f6db477fe5ffb18034d0ea8", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "hyprutils", + "type": "github" + } + }, + "hyprwayland-scanner": { + "inputs": { + "nixpkgs": [ + "hyprland", + "nixpkgs" + ], + "systems": [ + "hyprland", + "systems" + ] + }, + "locked": { + "lastModified": 1734793513, + "narHash": "sha256-rrrHcXapXJvGFqX+L/Bb0182L25jofAZ0fm1FInvrTQ=", + "owner": "hyprwm", + "repo": "hyprwayland-scanner", + "rev": "4d7367b6eee87397e2dbca2e78078dd0a4ef4c61", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "hyprwayland-scanner", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1735291276, + "narHash": "sha256-NYVcA06+blsLG6wpAbSPTCyLvxD/92Hy4vlY9WxFI1M=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "634fd46801442d760e09493a794c4f15db2d0cbb", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1730741070, + "narHash": "sha256-edm8WG19kWozJ/GqyYx2VjW99EdhjKwbY3ZwdlPAAlo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d063c1dd113c91ab27959ba540c0d9753409edf3", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "pre-commit-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [ + "hyprland", + "nixpkgs" + ], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1734797603, + "narHash": "sha256-ulZN7ps8nBV31SE+dwkDvKIzvN6hroRY8sYOT0w+E28=", + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "f0f0dc4920a903c3e08f5bdb9246bb572fcae498", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "root": { + "inputs": { + "hyprland": "hyprland", + "nixpkgs": [ + "hyprland", + "nixpkgs" + ], + "systems": [ + "hyprland", + "systems" + ] + } + }, + "systems": { + "locked": { + "lastModified": 1689347949, + "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", + "owner": "nix-systems", + "repo": "default-linux", + "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default-linux", + "type": "github" + } + }, + "xdph": { + "inputs": { + "hyprland-protocols": [ + "hyprland", + "hyprland-protocols" + ], + "hyprlang": [ + "hyprland", + "hyprlang" + ], + "hyprutils": [ + "hyprland", + "hyprutils" + ], + "hyprwayland-scanner": [ + "hyprland", + "hyprwayland-scanner" + ], + "nixpkgs": [ + "hyprland", + "nixpkgs" + ], + "systems": [ + "hyprland", + "systems" + ] + }, + "locked": { + "lastModified": 1734907020, + "narHash": "sha256-p6HxwpRKVl1KIiY5xrJdjcEeK3pbmc///UOyV6QER+w=", + "owner": "hyprwm", + "repo": "xdg-desktop-portal-hyprland", + "rev": "d7f18dda5e511749fa1511185db3536208fb1a63", + "type": "github" + }, + "original": { + "owner": "hyprwm", + "repo": "xdg-desktop-portal-hyprland", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..bf0a773 --- /dev/null +++ b/flake.nix @@ -0,0 +1,55 @@ +{ + description = "Hyprland Plugins (Hycov)"; + + inputs = { + hyprland.url = "git+https://github.com/hyprwm/Hyprland?submodules=1"; + nixpkgs.follows = "hyprland/nixpkgs"; + systems.follows = "hyprland/systems"; + }; + + outputs = + { self + , hyprland + , nixpkgs + , systems + }: + let + inherit (nixpkgs) lib; + withPkgsFor = fn: lib.genAttrs (import systems) (system: + let + pkgs = import nixpkgs { + localSystem.system = system; + overlays = [ + hyprland.overlays.hyprland-packages + self.overlays.default + ]; + }; + in + fn system pkgs); + in + { + overlays = { + default = self.overlays.hycov; + hycov = final: prev: { + hyprlandPlugins = prev.hyprlandPlugins or {} // { + hycov = final.callPackage ./default.nix { + stdenv = final.gcc14Stdenv; + }; + }; + }; + }; + + packages = withPkgsFor (system: pkgs: { + default = self.packages.${system}.hycov; + inherit (pkgs.hyprlandPlugins) hycov; + }); + + devShells = withPkgsFor (system: pkgs: { + default = pkgs.mkShell.override { stdenv = pkgs.gcc14Stdenv; } { + name = "hyprland-plugins"; + # buildInputs = [ pkgs.hyprland ]; + inputsFrom = [ pkgs.hycov ]; + }; + }); + }; +} diff --git a/hyprpm.toml b/hyprpm.toml new file mode 100644 index 0000000..2dcf630 --- /dev/null +++ b/hyprpm.toml @@ -0,0 +1,26 @@ +[repository] +name = "hycov" +authors = ["DreamMaoMao"] +commit_pins = [ + ["03ebbe18ed8517ee22591eac82cd54322f42cb7d", "b14e35d8c08ca2e946b7249f9024c2b1787398e6"], + ["cbadf3e3f31ab5ad5d192daac5f2ca930d08b8fb", "c5c314d2e4ca44011cc9f64f13ff9071eda751dd"], + ["84ab8d11e8951a6551d1e1bf87796a8589da6d47", "06c3ec69171e1937a889bc20b84bbc33a70b4400"], + ["13f6f0b923ff3ec94a3bec886c28b90402ceef91", "2c88820384c78b13ce92ef61fa0822369c9c6ecf"], + ["1c460e98f870676b15871fe4e5bfeb1a32a3d6d8", "7d48cff8364a7b6ae52a3472ad1dfe01b9f728ae"], + ["e93fbd7c4f991cb8ef03e433ccc4d43587923e15", "05fb15703d07a372b14a3260a337de13d1c16b91"], + ["fe7b748eb668136dd0558b7c8279bfcd7ab4d759", "05fb15703d07a372b14a3260a337de13d1c16b91"], + ["cba1ade848feac44b2eda677503900639581c3f4", "89fc3dc62d4aca3745d2b1aa6ab81dab541c232c"], + ["ea2501d4556f84d3de86a4ae2f4b22a474555b9f", "7b1f51d005c5f477c763f1031b807787f2f6b449"], + ["9e781040d9067c2711ec2e9f5b47b76ef70762b3","a1c4287c2ebb5f5596dc53571993725be9d63cb0"], + ["918d8340afd652b011b937d29d5eea0be08467f5","a1c4287c2ebb5f5596dc53571993725be9d63cb0"] + +] + +[hycov] +description = "hyprland overview mode" +authors = ["DreamMaoMao"] +output = "build/libhycov.so" +build = [ + "cmake -DCMAKE_BUILD_TYPE=Release -B build", + "cmake --build build -j $(nproc)" +] diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..ffe3aee --- /dev/null +++ b/install.sh @@ -0,0 +1,5 @@ +sudo rm build/* -rf +cmake -DCMAKE_BUILD_TYPE=Debug -B build +cmake --build build -j $(nproc) +sudo cp build/libhycov.so /usr/lib + diff --git a/src/DwindleSwitcher.cpp b/src/DwindleSwitcher.cpp new file mode 100644 index 0000000..ad25791 --- /dev/null +++ b/src/DwindleSwitcher.cpp @@ -0,0 +1,171 @@ + +#include "DwindleSwitcher.hpp" + +#include + +#define private public +#include "src/managers/LayoutManager.hpp" +#undef private + +#include "src/Compositor.hpp" +#include "src/helpers/Monitor.hpp" + +#include + +struct DwindleSwitcher::PrivateContext { + struct AdditionalState { + bool fullscreen = false; + }; + + CHyprDwindleLayout* layout = nullptr; + std::map additional; + std::list dwindleNodes; +}; + +bool canRestore(std::list& target, std::list& backup); +auto findNodePointerByData(std::list& nodes, SDwindleNodeData *node) -> SDwindleNodeData *; +void remapPointers(std::list& source, std::list& target); + + +std::string DwindleSwitcher::getName() const { return "dwindle"; } + +DwindleSwitcher::DwindleSwitcher() { + context = new PrivateContext(); +} + +DwindleSwitcher::~DwindleSwitcher() { + delete context; +} + +void DwindleSwitcher::onEnterOverviewBefore() { + context->layout = dynamic_cast(g_pLayoutManager->getCurrentLayout()); + if (!context->layout) return; + remapPointers(context->layout->m_dwindleNodesData, context->dwindleNodes); + + for (const auto& window : g_pCompositor->m_windows) { + context->additional[window] = {}; + if (window->isFullscreen()) { + context->additional[window].fullscreen = true; + } + } + + LayoutSwitcher::onEnterOverviewBefore(); +} + +void DwindleSwitcher::onEnterOverviewAfter() {} + +void DwindleSwitcher::onLeaveOverviewBefore() {} + +void DwindleSwitcher::onLeaveOverviewAfter() { + if (!context->layout) return; + + if (canRestore(context->layout->m_dwindleNodesData, context->dwindleNodes)) { + remapPointers(context->dwindleNodes, context->layout->m_dwindleNodesData); + + for (auto& node : context->layout->m_dwindleNodesData) { + context->layout->applyNodeDataToWindow(&node); + } + } + + for (auto& [window, data] : context->additional) { + if (!window) + continue; + + if (data.fullscreen) { + // if selected window is in the same workspace as fullscreen window then don't restore fullscreen + if (g_pCompositor->m_lastWindow != window) { + if (g_pCompositor->m_lastWindow->m_workspace == window->m_workspace) { + continue; + } + } + g_pCompositor->setWindowFullscreenState(window, {FSMODE_FULLSCREEN, FSMODE_FULLSCREEN}); + } + } + + context->layout = nullptr; +} + +bool canRestore(std::list& target, std::list& backup) { + + std::set rootsTarget; + std::set rootsBackup; + + std::set windowsTarget; + std::set windowsBackup; + + for (auto& node : backup) { + if (!node.isNode) { + if (node.pWindow.expired()) + return false; + windowsBackup.insert(node.pWindow.lock()); + } + + if (!node.pParent) + rootsBackup.insert(node.workspaceID); + } + + for (auto& node : target) { + if (!node.isNode) { + windowsTarget.insert(node.pWindow.lock()); + } + + if (!node.pParent) + rootsTarget.insert(node.workspaceID); + } + + if (rootsBackup != rootsTarget) { + return false; + } + + return windowsTarget == windowsBackup; +} + +auto findNodePointerByData(std::list& nodes, SDwindleNodeData *node) -> SDwindleNodeData * { + for (auto &iter : nodes) { + if (iter.pWindow) { + if (node->pWindow == iter.pWindow) { + return &iter; + } + } else { + if (node->children == iter.children) { + return &iter; + } + } + } + return nullptr; +} + +void remapPointers(std::list& source, std::list& target) { + target.clear(); + for (auto node : source) { + target.push_back(node); + } + + std::map targetToSourceMap; + + for (auto& node : target) { + if (node.pParent) { + targetToSourceMap[node.pParent] = findNodePointerByData(target, node.pParent); + } + if (node.children[0]) { + targetToSourceMap[node.children[0]] = findNodePointerByData(target, node.children[0]); + } + if (node.children[1]) { + targetToSourceMap[node.children[1]] = findNodePointerByData(target, node.children[1]); + } + } + + for (auto& node : target) { + if (node.pParent) { + node.pParent = targetToSourceMap[node.pParent]; + } + if (node.children[0]) { + node.children[0] = targetToSourceMap[node.children[0]]; + } + if (node.children[1]) { + node.children[1] = targetToSourceMap[node.children[1]]; + } + } + + assert(!std::any_of(targetToSourceMap.begin(), targetToSourceMap.end(), [](auto& pair){ return !pair.second;})); +} \ No newline at end of file diff --git a/src/DwindleSwitcher.hpp b/src/DwindleSwitcher.hpp new file mode 100644 index 0000000..aaab892 --- /dev/null +++ b/src/DwindleSwitcher.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "OverviewManager.hpp" + +struct DwindleSwitcher : public LayoutSwitcher { +public: + struct PrivateContext; + +public: + DwindleSwitcher(); + ~DwindleSwitcher(); + + [[nodiscard]] std::string getName() const; + + void onEnterOverviewBefore() override; + void onEnterOverviewAfter() override; + + void onLeaveOverviewBefore() override; + void onLeaveOverviewAfter() override; + +private: + PrivateContext* context = nullptr; +}; diff --git a/src/OverviewLayout.cpp b/src/OverviewLayout.cpp new file mode 100644 index 0000000..a814a49 --- /dev/null +++ b/src/OverviewLayout.cpp @@ -0,0 +1,245 @@ + +#include "OverviewLayout.hpp" + +#include "src/desktop/Workspace.hpp" +#include "src/helpers/Monitor.hpp" +#include "src/managers/LayoutManager.hpp" +#include "src/render/Renderer.hpp" +#include "src/Compositor.hpp" + +std::string OverviewLayout::getLayoutName() { return "grid"; } + +OverviewLayout::OverviewWindowNode::OverviewWindowNode(PHLWINDOW window_) { + window = std::move(window_); + savedState.monitorId = window->monitorID(); + savedState.workspaceId = window->workspaceID(); + savedState.workspaceName = g_pCompositor->getWorkspaceByID(savedState.workspaceId)->m_name; + + savedState.pos = window->m_position; + savedState.size = window->m_size; + savedState.ratio = (float) (savedState.size.y / savedState.size.x); + + savedState.posReal = window->m_realPosition->goal(); + savedState.sizeReal = window->m_realSize->goal(); + + savedState.floating = window->m_isFloating; + + if (!savedState.size.x || !savedState.size.y) { + savedState.size = savedState.sizeReal; + savedState.size.clamp({10, 10}); + } +} + +OverviewLayout::OverviewLayout(OverviewManager* overviewManager) { + mOverviewManager = overviewManager; +} + +void OverviewLayout::onEnable() { + for (auto &window: g_pCompositor->m_windows) { + if (!window->m_isMapped || window->isHidden()) + continue; + + if (window->m_workspace->m_isSpecialWorkspace) { + continue; + } + + onWindowCreatedTiling(window, DIRECTION_DEFAULT); + } + + updateWorkspaces(); +} + +void OverviewLayout::updateWorkspaces() { + std::map> activeMonitorMap; + + for (auto& node : m_windowNodes) { + if (!node.second.window->m_workspace->m_isSpecialWorkspace) { + activeMonitorMap[node.second.window->m_monitor.lock()].insert(node.second.window->m_workspace); + } + } + + auto underCursorWorkspace = g_pCompositor->getMonitorFromCursor()->m_activeWorkspace; + for (auto& monitor : activeMonitorMap) { + if (monitor.second.contains(underCursorWorkspace)) { + monitor.first->changeWorkspace(underCursorWorkspace); + } else { + monitor.first->changeWorkspace(*monitor.second.begin()); + } + monitor.first->setSpecialWorkspace(nullptr); + } + + for (auto& node : m_windowNodes) { + auto monitor = node.second.window->m_monitor; + auto workspace = monitor->m_activeWorkspace; + + node.second.window->m_workspace = workspace; + node.second.window->m_monitor = monitor; + node.second.window->m_isFloating = false; // this it needed to disable minSize clamping + } +} + +void OverviewLayout::onDisable() { + + for (auto& [window, node] : m_windowNodes) { + auto monitor = g_pCompositor->getMonitorFromID(node.savedState.monitorId); + auto workspace = g_pCompositor->getWorkspaceByID(node.savedState.workspaceId); + + if (!workspace) { + workspace = g_pCompositor->createNewWorkspace(node.savedState.workspaceId, node.savedState.monitorId, node.savedState.workspaceName); + } + + window->m_workspace = workspace; + window->m_monitor = monitor; + + window->m_position = node.savedState.pos; + window->m_size = node.savedState.size; + + *window->m_realPosition = node.savedState.posReal; + *window->m_realSize = node.savedState.sizeReal; + + window->m_isFloating = node.savedState.floating; + } + + m_windowNodes.clear(); +} + +void OverviewLayout::onWindowRemoved(PHLWINDOW pWindow) { + onWindowRemovedTiling(pWindow); +} + +void OverviewLayout::onWindowRemovedTiling(PHLWINDOW pWindow) { + m_windowNodes.erase(pWindow.get()); + updateLayout(); +} + +void OverviewLayout::updateLayout() { + for (auto &monitor: g_pCompositor->m_monitors) { + recalculateMonitor(monitor->m_id); + } +} + +void OverviewLayout::onWindowCreatedTiling(PHLWINDOW window, eDirection direction) { + if (m_windowNodes.contains(window.get())) return; + + auto newNode = OverviewWindowNode(window); + m_windowNodes.insert({ window.get(), newNode }); + + updateLayout(); +} + +void OverviewLayout::recalculateMonitor(const MONITORID &monitorId) { + auto monitor = g_pCompositor->getMonitorFromID(monitorId); + + if (!monitor || !monitor->m_activeWorkspace) { + return; + } + + std::vector nodes; + for (auto& node : m_windowNodes) { + if (node.second.window->monitorID() == monitorId) { + nodes.push_back(&node.second); + } + } + + calculateOverviewGrid(nodes, g_pCompositor->getWorkspaceByID(monitor->activeWorkspaceID())); +} + +void OverviewLayout::calculateOverviewGrid(const std::vector& windows, const PHLWORKSPACE& workspace) const { + if (windows.empty()) { + return; + } + + const auto monitor = g_pCompositor->getMonitorFromID(workspace->m_monitor->m_id); + + auto monitorX = (int) (monitor->m_position.x + monitor->m_reservedTopLeft.x); + auto monitorY = (int) (monitor->m_position.y + monitor->m_reservedTopLeft.y); + auto monitorW = (int) (monitor->m_size.x - monitor->m_reservedTopLeft.x); + auto monitorH = (int) (monitor->m_size.y - monitor->m_reservedTopLeft.y); + + auto numNodes = (int) windows.size(); + + // Calculate the integer part of the square root of the number of nodes + int columns = 0; + while (columns <= numNodes / 2) { + if (columns * columns >= numNodes) { + break; + } + columns++; + } + + // The number of rows and columns multiplied by the number of nodes must be greater than the number of nodes to fit all the Windows + int rows = (columns && (columns - 1) * columns >= numNodes) ? columns - 1 : columns; + + // If the nodes do not exactly fill all rows, the number of Windows in the unfilled rows is + int numLeftOvers = numNodes % columns; + + // Calculate the width and height of the layout area based on the number of rows and columns + int height = (int) ((monitorH - 2 * (gapOut) - (rows - 1) * (gapIn)) / rows); + int width = (int) ((monitorW - 2 * (gapOut) - (columns - 1) * (gapIn)) / columns); + + int leftOversOffsetX = 0; + if (numLeftOvers) { + leftOversOffsetX = (monitorW - numLeftOvers * width - (numLeftOvers - 1) * gapIn) / 2 - gapOut; + } + + for (int i = 0; i < numNodes; i++) { + auto node = windows[i]; + + int posX = monitorX + (i % columns) * (width + gapIn) + gapOut; + int posY = monitorY + (int) (i / columns) * (height + gapIn) + gapOut; + + if (i >= numNodes - numLeftOvers) { + posX += leftOversOffsetX; + } + + auto cellPos = Vector2D(posX + border, posY + border); + auto cellSize = Vector2D(width - border * 2, height - border * 2); + + auto windowPos = cellPos; + auto windowSize = cellSize; + + if (1) { + if (cellSize.x > node->savedState.size.x && cellSize.y > node->savedState.size.y) { + windowPos = cellPos + (cellSize / 2) - (node->savedState.size / 2); + windowSize = node->savedState.size; + } else { + // apply ratio scaling + auto cellRatio = (float) (cellSize.y / cellSize.x); + if (cellRatio < node->savedState.ratio) { + windowSize.x = cellSize.y / node->savedState.ratio; + windowPos.x += (cellSize.x - windowSize.x) / 2; + } else if (cellRatio > node->savedState.ratio) { + windowSize.y = cellSize.x * node->savedState.ratio; + windowPos.y += (cellSize.y - windowSize.y) / 2; + } + } + } + + node->window->m_position = windowPos; + *node->window->m_realPosition = windowPos; + node->window->m_size = windowSize; + *node->window->m_realSize = windowSize; + } +} + +PHLWINDOW OverviewLayout::getNextWindowCandidate(PHLWINDOW) { + if (!m_windowNodes.empty()) + return m_windowNodes.begin()->second.window; + return nullptr; +} + +void OverviewLayout::onBeginDragWindow() { + mOverviewManager->leaveOverview(); +} + +Vector2D OverviewLayout::predictSizeForNewWindowTiled() { return {}; } +bool OverviewLayout::isWindowTiled(PHLWINDOW pWindow) { return true; } +void OverviewLayout::recalculateWindow(PHLWINDOW pWindow) {} +void OverviewLayout::resizeActiveWindow(const Vector2D &pixResize, eRectCorner corner, PHLWINDOW pWindow) {} +void OverviewLayout::fullscreenRequestForWindow( PHLWINDOW pWindow, const eFullscreenMode CURRENT_EFFECTIVE_MODE, const eFullscreenMode EFFECTIVE_MODE) {} +std::any OverviewLayout::layoutMessage(SLayoutMessageHeader header, std::string content) { return ""; } +SWindowRenderLayoutHints OverviewLayout::requestRenderHints(PHLWINDOW pWindow) { return {}; } +void OverviewLayout::switchWindows(PHLWINDOW pWindowA, PHLWINDOW pWindowB) {} +void OverviewLayout::alterSplitRatio(PHLWINDOW pWindow, float delta, bool exact) {} +void OverviewLayout::replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to) {} +void OverviewLayout::moveWindowTo(PHLWINDOW, const std::string &dir, bool silent) {} \ No newline at end of file diff --git a/src/OverviewLayout.hpp b/src/OverviewLayout.hpp new file mode 100644 index 0000000..d2dd518 --- /dev/null +++ b/src/OverviewLayout.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include "OverviewManager.hpp" + +#include +#include + +#include +#include + + +class OverviewLayout : public IHyprLayout { + + struct WindowState { + Vector2D pos; + Vector2D size; + + Vector2D posReal; + Vector2D sizeReal; + + long monitorId = 0; + long workspaceId = 0; + std::string workspaceName; + + float ratio = 1.f; + bool floating = false; + }; + + struct OverviewWindowNode { + explicit OverviewWindowNode(PHLWINDOW window_); + + bool operator==(const OverviewWindowNode &in) const { + return window == in.window; + } + + PHLWINDOW window = nullptr; + WindowState savedState; + }; + +public: + explicit OverviewLayout(OverviewManager* overviewManager); + + void onEnable() override; + void onDisable() override; + +public: + void onWindowCreatedTiling(PHLWINDOW, eDirection direction) override; + void onWindowRemovedTiling(PHLWINDOW) override; + void onWindowRemoved(PHLWINDOW) override; + + void onBeginDragWindow() override; + + bool isWindowTiled(PHLWINDOW) override; + PHLWINDOW getNextWindowCandidate(PHLWINDOW) override; + void recalculateMonitor(const MONITORID &) override; + void recalculateWindow(PHLWINDOW) override; + void resizeActiveWindow(const Vector2D &, eRectCorner corner, PHLWINDOW pWindow) override; + void fullscreenRequestForWindow(PHLWINDOW pWindow, eFullscreenMode CURRENT_EFFECTIVE_MODE, eFullscreenMode EFFECTIVE_MODE) override; + std::any layoutMessage(SLayoutMessageHeader, std::string) override; + SWindowRenderLayoutHints requestRenderHints(PHLWINDOW) override; + void switchWindows(PHLWINDOW, PHLWINDOW) override; + void alterSplitRatio(PHLWINDOW, float, bool) override; + std::string getLayoutName() override; + Vector2D predictSizeForNewWindowTiled() override; + void replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to) override; + void moveWindowTo(PHLWINDOW, const std::string &direction, bool silent) override; + +private: + void updateLayout(); + void calculateOverviewGrid(const std::vector& windows, const PHLWORKSPACE& workspace) const; + void updateWorkspaces(); + +public: + std::map m_windowNodes; + + const int border = 5; + const int gapIn = 20; + const int gapOut = 150; + +private: + OverviewManager* mOverviewManager = nullptr; +}; diff --git a/src/OverviewManager.cpp b/src/OverviewManager.cpp new file mode 100644 index 0000000..c275842 --- /dev/null +++ b/src/OverviewManager.cpp @@ -0,0 +1,77 @@ +#include "OverviewManager.hpp" + +#include + +#include "src/Compositor.hpp" +#include "src/managers/LayoutManager.hpp" + +void LayoutSwitcher::onEnterOverviewBefore() { + for (auto& window : g_pCompositor->m_windows) { + g_pCompositor->setWindowFullscreenState(window, SFullscreenState()); + } +} + +OverviewManager::OverviewManager() { + registerSwitcher(std::make_shared()); +} + +void OverviewManager::registerSwitcher(const std::shared_ptr &switcher) { + mSwitchers[switcher->getName()] = switcher; +} + +void OverviewManager::setCurrentSwitcher(const std::string& name) { + if (mSwitchers.contains(name)) { + mCurrentSwitcher = mSwitchers[name]; + } else { + mCurrentSwitcher = mSwitchers["all"]; + } +} + +void OverviewManager::toggle() { + auto currentName = g_pLayoutManager->getCurrentLayout()->getLayoutName(); + auto overviewName = mOverviewLayout->getLayoutName(); + + if (currentName == overviewName) { + leaveOverview(); + } else { + enterOverview(); + } +} + +void OverviewManager::leaveOverview() { + auto currentName = g_pLayoutManager->getCurrentLayout()->getLayoutName(); + auto overviewName = mOverviewLayout->getLayoutName(); + + if (currentName != overviewName) return; + + mCurrentSwitcher->onLeaveOverviewBefore(); + g_pLayoutManager->switchToLayout(mFallbackLayout->getLayoutName()); + mCurrentSwitcher->onLeaveOverviewAfter(); + + onLastActiveWindow(g_pCompositor->m_lastWindow.lock()); +} + +void OverviewManager::enterOverview() { + auto currentName = g_pLayoutManager->getCurrentLayout()->getLayoutName(); + auto overviewName = mOverviewLayout->getLayoutName(); + + if (currentName == overviewName) return; + + mFallbackLayout = g_pLayoutManager->getCurrentLayout(); + + setCurrentSwitcher(mFallbackLayout->getLayoutName()); + + mCurrentSwitcher->onEnterOverviewBefore(); + g_pLayoutManager->switchToLayout(mOverviewLayout->getLayoutName()); + mCurrentSwitcher->onEnterOverviewAfter(); +} + +void OverviewManager::onLastActiveWindow(PHLWINDOW windowToFocus) { + g_pCompositor->changeWindowZOrder(windowToFocus, true); + g_pCompositor->focusWindow(std::move(windowToFocus)); +} + +void OverviewManager::setOverviewLayout(IHyprLayout *layout) { + mOverviewLayout = layout; +} + diff --git a/src/OverviewManager.hpp b/src/OverviewManager.hpp new file mode 100644 index 0000000..e8969ee --- /dev/null +++ b/src/OverviewManager.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "src/layout/IHyprLayout.hpp" + +#include +#include + +struct LayoutSwitcher { + virtual std::string getName() const { return "all"; } + virtual void onEnterOverviewBefore(); + virtual void onEnterOverviewAfter() {} + virtual void onLeaveOverviewBefore() {} + virtual void onLeaveOverviewAfter() {} + + virtual ~LayoutSwitcher() = default; +}; + +class OverviewManager { +public: + OverviewManager(); + void setOverviewLayout(IHyprLayout* layout); + void registerSwitcher(const std::shared_ptr& switcher); + void toggle(); + +public: + void leaveOverview(); + void enterOverview(); + + static void onLastActiveWindow(PHLWINDOW windowToFocus); + +private: + void setCurrentSwitcher(const std::string &name); + +private: + std::shared_ptr mCurrentSwitcher = nullptr; + std::map> mSwitchers; + + IHyprLayout* mFallbackLayout = nullptr; + IHyprLayout* mOverviewLayout = nullptr; +}; \ No newline at end of file diff --git a/src/Plugin.cpp b/src/Plugin.cpp new file mode 100644 index 0000000..304b27d --- /dev/null +++ b/src/Plugin.cpp @@ -0,0 +1,30 @@ + +#include "Plugin.hpp" + +#include "DwindleSwitcher.hpp" + +#include + + +PluginState::PluginState(HANDLE _handle) { + handle = _handle; + manager = new OverviewManager(); + layout = new OverviewLayout(manager); +} + +PluginState::~PluginState() { + delete layout; + delete manager; +} + +void PluginState::bind() { + manager->setOverviewLayout(layout); + manager->registerSwitcher(std::make_shared()); + + HyprlandAPI::addLayout(handle, layout->getLayoutName(), layout); + + HyprlandAPI::addDispatcherV2(handle, "overview:toggle", [this](const std::string&){ + manager->toggle(); + return SDispatchResult(); + }); +} \ No newline at end of file diff --git a/src/Plugin.hpp b/src/Plugin.hpp new file mode 100644 index 0000000..3593caf --- /dev/null +++ b/src/Plugin.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "OverviewLayout.hpp" +#include "OverviewManager.hpp" + +#include + +struct PluginState { + explicit PluginState(HANDLE handle); + ~PluginState(); + + HANDLE handle = nullptr; + OverviewLayout* layout = nullptr; + OverviewManager* manager = nullptr; + + void bind(); +}; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..9a47f44 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,37 @@ +#include "Plugin.hpp" + +#include +#include + +typedef void (*CInputManager_onMouseButton)(void* , IPointer::SButtonEvent e); + +static PluginState* gPluginState = nullptr; +static CFunctionHook* mouseHook = nullptr; + +APICALL EXPORT std::string PLUGIN_API_VERSION() { + return HYPRLAND_API_VERSION; +} + +static void mouseButtonHook(void* self, IPointer::SButtonEvent buttonEvent) { + if (buttonEvent.button == BTN_LEFT ) { + gPluginState->manager->leaveOverview(); + } + + (*(CInputManager_onMouseButton)mouseHook->m_original)(self, buttonEvent); +} + +APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { + gPluginState = new PluginState(handle); + + gPluginState->bind(); + + + mouseHook = HyprlandAPI::createFunctionHook(gPluginState->handle, (void*)&CInputManager::onMouseButton, (void*)&mouseButtonHook); + mouseHook->hook(); + + return {"overview", "overview mode", "ilusha", "0.0"}; +} + +APICALL EXPORT void PLUGIN_EXIT() { + delete gPluginState; +} \ No newline at end of file