commit 1210b5b7be4b18d77d8514881289518ac06000b1 Author: ilusha Date: Fri Jun 6 21:29:40 2025 +0000 initial 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