mirror of
https://github.com/hyprwm/hyprpaper.git
synced 2025-12-19 20:20:02 +01:00
core: migrate to hyprtoolkit (#288)
--------- Co-authored-by: Mihai Fufezan <mihai@fufexan.net>
This commit is contained in:
parent
1733e0025b
commit
1d8df14fce
36 changed files with 1172 additions and 1808 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -22,6 +22,9 @@ result
|
|||
protocols/*.hpp
|
||||
protocols/*.cpp
|
||||
|
||||
hw-protocols/*.hpp
|
||||
hw-protocols/*.cpp
|
||||
|
||||
.cache/
|
||||
|
||||
hyprctl/hyprctl
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ project(
|
|||
DESCRIPTION "A blazing fast wayland wallpaper utility"
|
||||
VERSION ${VERSION})
|
||||
|
||||
add_compile_definitions(HYPRPAPER_VERSION="${VERSION}")
|
||||
|
||||
set(CMAKE_MESSAGE_LOG_LEVEL "STATUS")
|
||||
|
||||
message(STATUS "Configuring hyprpaper!")
|
||||
|
|
@ -39,7 +41,7 @@ execute_process(
|
|||
OUTPUT_VARIABLE GIT_DIRTY
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
include_directories(.)
|
||||
include_directories(. hw-protocols protocols)
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
add_compile_options(-DWLR_USE_UNSTABLE)
|
||||
add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value
|
||||
|
|
@ -53,19 +55,20 @@ pkg_check_modules(
|
|||
deps
|
||||
REQUIRED
|
||||
IMPORTED_TARGET
|
||||
wayland-client
|
||||
wayland-protocols>=1.35
|
||||
cairo
|
||||
pango
|
||||
pangocairo
|
||||
hyprlang>=0.6.0
|
||||
hyprutils>=0.2.4
|
||||
hyprgraphics)
|
||||
wayland-client
|
||||
hyprtoolkit>=0.4.1
|
||||
hyprwire
|
||||
pixman-1
|
||||
libdrm)
|
||||
|
||||
file(GLOB_RECURSE SRCFILES "src/*.cpp")
|
||||
|
||||
add_executable(hyprpaper ${SRCFILES})
|
||||
|
||||
# Wayland
|
||||
|
||||
pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)
|
||||
message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}")
|
||||
pkg_get_variable(WAYLAND_SCANNER_PKGDATA_DIR wayland-scanner pkgdatadir)
|
||||
|
|
@ -107,6 +110,25 @@ protocolnew("stable/xdg-shell" "xdg-shell" false)
|
|||
protocolnew("staging/cursor-shape" "cursor-shape-v1" false)
|
||||
protocolnew("stable/tablet" "tablet-v2" false)
|
||||
|
||||
# Hyprwire
|
||||
|
||||
function(hyprprotocolServer protoPath protoName)
|
||||
set(path ${CMAKE_SOURCE_DIR}/${protoPath})
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_SOURCE_DIR}/hw-protocols/${protoName}-server.cpp
|
||||
${CMAKE_SOURCE_DIR}/hw-protocols/${protoName}-server.hpp
|
||||
COMMAND hyprwire-scanner ${path}/${protoName}.xml
|
||||
${CMAKE_SOURCE_DIR}/hw-protocols/
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
|
||||
target_sources(hyprpaper PRIVATE hw-protocols/${protoName}-server.cpp
|
||||
hw-protocols/${protoName}-server.hpp
|
||||
)
|
||||
endfunction()
|
||||
|
||||
hyprprotocolServer(hw-protocols hyprpaper_core)
|
||||
|
||||
#
|
||||
|
||||
string(REPLACE "\"" " " GIT_COMMIT_MESSAGE_ESCAPED "${GIT_COMMIT_MESSAGE}")
|
||||
target_compile_definitions(hyprpaper
|
||||
PRIVATE "-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"")
|
||||
|
|
|
|||
163
README.md
163
README.md
|
|
@ -1,13 +1,12 @@
|
|||
# hyprpaper
|
||||
|
||||
Hyprpaper is a blazing fast wallpaper utility for Hyprland with the ability to dynamically change wallpapers through sockets. It will work on all wlroots-based compositors, though.
|
||||
Hyprpaper is a simple and fast wallpaper utility for Hyprland with the ability to dynamically change wallpapers through sockets.
|
||||
|
||||
# Features
|
||||
- Per-output wallpapers
|
||||
- fill, tile or contain modes
|
||||
- fill, tile, cover or contain modes
|
||||
- fractional scaling support
|
||||
- IPC for blazing fast wallpaper switches
|
||||
- preloading targets into memory
|
||||
- IPC for fast wallpaper switches
|
||||
|
||||
# Installation
|
||||
|
||||
|
|
@ -20,35 +19,10 @@ Hyprpaper is a blazing fast wallpaper utility for Hyprland with the ability to d
|
|||
### Dependencies
|
||||
The development files of these packages need to be installed on the system for `hyprpaper` to build correctly.
|
||||
(Development packages are usually suffixed with `-dev` or `-devel` in most distros' repos).
|
||||
- wayland
|
||||
- wayland-protocols
|
||||
- pango
|
||||
- cairo
|
||||
- file
|
||||
- libglvnd
|
||||
- libglvnd-core
|
||||
- libjpeg-turbo
|
||||
- libwebp
|
||||
- libjxl
|
||||
- hyprtoolkit
|
||||
- hyprlang
|
||||
- hyprutils
|
||||
- hyprwayland-scanner
|
||||
- hyprgraphics
|
||||
|
||||
To install all of these in Fedora, run this command:
|
||||
```
|
||||
sudo dnf install wayland-devel wayland-protocols-devel hyprlang-devel pango-devel cairo-devel file-devel libglvnd-devel libglvnd-core-devel libjpeg-turbo-devel libwebp-devel libjxl-devel gcc-c++ hyprutils-devel hyprwayland-scanner
|
||||
```
|
||||
|
||||
On Arch:
|
||||
```
|
||||
sudo pacman -S ninja gcc wayland-protocols libjpeg-turbo libwebp libjxl pango cairo pkgconf cmake libglvnd wayland hyprutils hyprwayland-scanner hyprlang
|
||||
```
|
||||
|
||||
On OpenSUSE:
|
||||
```
|
||||
sudo zypper install ninja gcc-c++ wayland-protocols-devel Mesa-libGLESv3-devel file-devel hyprutils-devel hyprwayland-scanner
|
||||
```
|
||||
- hyprwire
|
||||
|
||||
### Building
|
||||
|
||||
|
|
@ -63,129 +37,4 @@ Install with:
|
|||
|
||||
```sh
|
||||
cmake --install ./build
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
||||
Hyprpaper is controlled by the config, like this:
|
||||
|
||||
*~/.config/hypr/hyprpaper.conf*
|
||||
```
|
||||
preload = /path/to/image.png
|
||||
#if more than one preload is desired then continue to preload other backgrounds
|
||||
preload = /path/to/next_image.png
|
||||
# .. more preloads
|
||||
|
||||
#set the default wallpaper(s) seen on initial workspace(s) --depending on the number of monitors used
|
||||
wallpaper = monitor1,/path/to/image.png
|
||||
#if more than one monitor in use, can load a 2nd image
|
||||
wallpaper = monitor2,/path/to/next_image.png
|
||||
# .. more monitors
|
||||
|
||||
#enable splash text rendering over the wallpaper
|
||||
splash = true
|
||||
|
||||
#fully disable ipc
|
||||
# ipc = off
|
||||
|
||||
|
||||
```
|
||||
|
||||
Preload will tell Hyprland to load a particular image (supported formats: png, jpg, jpeg, jpeg xl, webp). Wallpaper will apply the wallpaper to the selected output (`monitor` is the monitor's name, easily can be retrieved with `hyprctl monitors`. You can leave it empty to set all monitors without an active wallpaper. You can also use `desc:` followed by the monitor's description without the (PORT) at the end)
|
||||
|
||||
You may add `contain:` or `tile:` before the file path in `wallpaper=` to set the mode to either contain or tile, respectively, instead of cover:
|
||||
|
||||
```
|
||||
wallpaper = monitor,contain:/path/to/image.jpg
|
||||
```
|
||||
|
||||
A Wallpaper ***cannot*** be applied without preloading. The config is ***not*** reloaded dynamically.
|
||||
|
||||
## Important note to the inner workings
|
||||
Preload does exactly what it says. It loads the entire wallpaper into memory. This can result in around 8 - 20MB of mem usage. It is not recommended to preload every wallpaper you have, as it will be a) taking a couple seconds at the beginning to load and b) take 100s of MBs of disk and RAM usage.
|
||||
|
||||
Preload is meant only for situations in which you want a wallpaper to switch INSTANTLY when you issue a wallpaper keyword (e.g. wallpaper per workspace)
|
||||
|
||||
In any and all cases when you don't mind waiting 300ms for the wallpaper to change, consider making a script that:
|
||||
- preloads the new wallpaper
|
||||
- sets the new wallpaper
|
||||
- unloads the old wallpaper (to free memory)
|
||||
|
||||
# IPC
|
||||
You can use `hyprctl hyprpaper` (if on Hyprland) to issue a keyword, for example
|
||||
|
||||
Example:
|
||||
|
||||
If your wallpapers are stored in *~/Pictures*, then make sure you have already preloaded the desired wallpapers in hyprpaper.conf.
|
||||
|
||||
*~/.config/hypr/hyprpaper.conf*
|
||||
```
|
||||
preload = ~/Pictures/myepicpng.png
|
||||
preload = ~/Pictures/myepicpngToo.png
|
||||
preload = ~/Pictures/myepicpngAlso.png
|
||||
#... continue as desired, but be mindful of the impact on memory.
|
||||
```
|
||||
|
||||
In the actual configuration for Hyprland, *hyprland.conf*, variables can be set for ease of reading and to be used as shortcuts in the bind command. The following example uses $w shorthand wallpaper variables:
|
||||
|
||||
*~/.config/hypr/hyprland.conf*
|
||||
```
|
||||
$w1 = hyprctl hyprpaper wallpaper "DP-1,~/Pictures/myepicpng.png"
|
||||
$w2 = hyprctl hyprpaper wallpaper "DP-1,~/Pictures/myepicpngToo.png"
|
||||
$w3 = hyprctl hyprpaper wallpaper "DP-1,~/Pictures/myepicpngAlso.png"
|
||||
#yes use quotes around desired monitor and wallpaper
|
||||
#... continued with desired amount
|
||||
```
|
||||
With the variables created we can now "exec" the actions.
|
||||
|
||||
Remember in Hyprland we can bind more than one action to a key so in the case where we'd like to change the wallpaper when we switch workspace we have to ensure that the actions are bound to the same key such as...
|
||||
|
||||
*~/.config/hypr/hyprland.conf*
|
||||
```
|
||||
bind=SUPER,1,workspace,1 #Superkey + 1 switches to workspace 1
|
||||
bind=SUPER,1,exec,$w1 #SuperKey + 1 switches to wallpaper $w1 on DP-1 as defined in the variable
|
||||
|
||||
bind=SUPER,2,workspace,2 #Superkey + 2 switches to workspace 2
|
||||
bind=SUPER,2,exec,$w2 #SuperKey + 2 switches to wallpaper $w2 on DP-1 as defined in the variable
|
||||
|
||||
bind=SUPER,3,workspace,3 #Superkey + 3 switches to workspace 3
|
||||
bind=SUPER,3,exec,$w3 #SuperKey + 3 switches to wallpaper $w3 on DP-1 as defined in the variable
|
||||
|
||||
#... and so on
|
||||
```
|
||||
Because the default behavior in Hyprland is to also switch the workspace whenever movetoworkspace is used to move a window to another workspace you may want to include the following:
|
||||
|
||||
```
|
||||
bind=SUPERSHIFT,1,movetoworkspace,1 #Superkey + Shift + 1 moves windows and switches to workspace 1
|
||||
bind=SUPERSHIFT,1,exec,$w1 #SuperKey + Shift + 1 switches to wallpaper $w1 on DP-1 as defined in the variable
|
||||
```
|
||||
|
||||
## Getting information from hyprpaper
|
||||
You can also use `hyprctl hyprpaper` to get information about the state of hyprpaper using the following commands:
|
||||
```
|
||||
listloaded - lists the wallpapers that are currently preloaded (useful for dynamically preloading and unloading)
|
||||
listactive - prints the active wallpapers hyprpaper is displaying, along with its accociated monitor
|
||||
```
|
||||
|
||||
# Battery life
|
||||
Since the IPC has to tick every now and then, and poll in the background, battery life might be a tiny bit worse with IPC on. If you want to fully disable it, use
|
||||
```
|
||||
ipc = off
|
||||
```
|
||||
in the config.
|
||||
|
||||
# Misc
|
||||
You can set `splash = true` to enable the splash rendering over the wallpaper.
|
||||
|
||||
The value for `splash_offset` sets, in percentage, the splash rendering offset relative to the bottom of the display.
|
||||
|
||||
## Unloading
|
||||
If you use a lot of wallpapers, consider unloading those that you no longer need. This will mean you need to load them again if you wish to use them for a second time, but will free the memory used by the preloaded bitmap. (Usually 8 - 20MB, depending on the resolution)
|
||||
|
||||
You can issue a `hyprctl hyprpaper unload [PATH]` to do that.
|
||||
|
||||
You can also issue a `hyprctl hyprpaper unload all` to unload all inactive wallpapers.
|
||||
|
||||
<br/>
|
||||
|
||||
For other compositors, the socket works like socket1 of Hyprland, and is located in `/tmp/hypr/.hyprpaper.sock` (this path only when Hyprland is not running!)
|
||||
```
|
||||
126
flake.lock
generated
126
flake.lock
generated
|
|
@ -1,5 +1,34 @@
|
|||
{
|
||||
"nodes": {
|
||||
"aquamarine": {
|
||||
"inputs": {
|
||||
"hyprutils": [
|
||||
"hyprutils"
|
||||
],
|
||||
"hyprwayland-scanner": [
|
||||
"hyprwayland-scanner"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"systems": [
|
||||
"systems"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1764714051,
|
||||
"narHash": "sha256-AjcMlM3UoavFoLzr0YrcvsIxALShjyvwe+o7ikibpCM=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "aquamarine",
|
||||
"rev": "a43bedcceced5c21ad36578ed823e6099af78214",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hyprwm",
|
||||
"repo": "aquamarine",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"hyprgraphics": {
|
||||
"inputs": {
|
||||
"hyprutils": [
|
||||
|
|
@ -13,11 +42,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1750621377,
|
||||
"narHash": "sha256-8u6b5oAdX0rCuoR8wFenajBRmI+mzbpNig6hSCuWUzE=",
|
||||
"lastModified": 1763733840,
|
||||
"narHash": "sha256-JnET78yl5RvpGuDQy3rCycOCkiKoLr5DN1fPhRNNMco=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprgraphics",
|
||||
"rev": "b3d628d01693fb9bb0a6690cd4e7b80abda04310",
|
||||
"rev": "8f1bec691b2d198c60cccabca7a94add2df4ed1a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -39,11 +68,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1750371198,
|
||||
"narHash": "sha256-/iuJ1paQOBoSLqHflRNNGyroqfF/yvPNurxzcCT0cAE=",
|
||||
"lastModified": 1764612430,
|
||||
"narHash": "sha256-54ltTSbI6W+qYGMchAgCR6QnC1kOdKXN6X6pJhOWxFg=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprlang",
|
||||
"rev": "cee01452bca58d6cadb3224e21e370de8bc20f0b",
|
||||
"rev": "0d00dc118981531aa731150b6ea551ef037acddd",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -52,6 +81,44 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"hyprtoolkit": {
|
||||
"inputs": {
|
||||
"aquamarine": [
|
||||
"aquamarine"
|
||||
],
|
||||
"hyprgraphics": [
|
||||
"hyprgraphics"
|
||||
],
|
||||
"hyprlang": [
|
||||
"hyprlang"
|
||||
],
|
||||
"hyprutils": [
|
||||
"hyprutils"
|
||||
],
|
||||
"hyprwayland-scanner": [
|
||||
"hyprwayland-scanner"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"systems": [
|
||||
"systems"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1764773470,
|
||||
"narHash": "sha256-IAGHOvvuCnJ6zWB3efxboJ/HUfSaXJ6pzrMwOMS2lJY=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprtoolkit",
|
||||
"rev": "a07c89acce89709bed02160136a612e70021cd91",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprtoolkit",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"hyprutils": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
|
|
@ -62,11 +129,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1750371096,
|
||||
"narHash": "sha256-JB1IeJ41y7kWc/dPGV6RMcCUM0Xj2NEK26A2Ap7EM9c=",
|
||||
"lastModified": 1764637132,
|
||||
"narHash": "sha256-vSyiKCzSY48kA3v39GFu6qgRfigjKCU/9k1KTK475gg=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprutils",
|
||||
"rev": "38f3a211657ce82a1123bf19402199b67a410f08",
|
||||
"rev": "2f2413801beee37303913fc3c964bbe92252a963",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -85,11 +152,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1750371869,
|
||||
"narHash": "sha256-lGk4gLjgZQ/rndUkzmPYcgbHr8gKU5u71vyrjnwfpB4=",
|
||||
"lastModified": 1763640274,
|
||||
"narHash": "sha256-Uan1Nl9i4TF/kyFoHnTq1bd/rsWh4GAK/9/jDqLbY5A=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprwayland-scanner",
|
||||
"rev": "aa38edd6e3e277ae6a97ea83a69261a5c3aab9fd",
|
||||
"rev": "f6cf414ca0e16a4d30198fd670ec86df3c89f671",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -98,13 +165,39 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"hyprwire": {
|
||||
"inputs": {
|
||||
"hyprutils": [
|
||||
"hyprutils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"systems": [
|
||||
"systems"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1764773840,
|
||||
"narHash": "sha256-9UcCdwe7vPgEcJJ64JseBQL0ZJZoxp/2iFuvfRI+9zk=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprwire",
|
||||
"rev": "3f1997d6aeced318fb141810fded2255da811293",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprwire",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1750365781,
|
||||
"narHash": "sha256-XE/lFNhz5lsriMm/yjXkvSZz5DfvKJLUjsS6pP8EC50=",
|
||||
"lastModified": 1764517877,
|
||||
"narHash": "sha256-pp3uT4hHijIC8JUK5MEqeAWmParJrgBVzHLNfJDZxg4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "08f22084e6085d19bcfb4be30d1ca76ecb96fe54",
|
||||
"rev": "2d293cbfa5a793b4c50d17c05ef9e385b90edf6c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -116,10 +209,13 @@
|
|||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"aquamarine": "aquamarine",
|
||||
"hyprgraphics": "hyprgraphics",
|
||||
"hyprlang": "hyprlang",
|
||||
"hyprtoolkit": "hyprtoolkit",
|
||||
"hyprutils": "hyprutils",
|
||||
"hyprwayland-scanner": "hyprwayland-scanner",
|
||||
"hyprwire": "hyprwire",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"systems": "systems"
|
||||
}
|
||||
|
|
|
|||
29
flake.nix
29
flake.nix
|
|
@ -5,6 +5,14 @@
|
|||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
systems.url = "github:nix-systems/default-linux";
|
||||
|
||||
aquamarine = {
|
||||
url = "github:hyprwm/aquamarine";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.systems.follows = "systems";
|
||||
inputs.hyprutils.follows = "hyprutils";
|
||||
inputs.hyprwayland-scanner.follows = "hyprwayland-scanner";
|
||||
};
|
||||
|
||||
hyprgraphics = {
|
||||
url = "github:hyprwm/hyprgraphics";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
|
|
@ -30,6 +38,24 @@
|
|||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.systems.follows = "systems";
|
||||
};
|
||||
|
||||
hyprwire = {
|
||||
url = "github:hyprwm/hyprwire";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.systems.follows = "systems";
|
||||
inputs.hyprutils.follows = "hyprutils";
|
||||
};
|
||||
|
||||
hyprtoolkit = {
|
||||
url = "github:hyprwm/hyprtoolkit";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
inputs.systems.follows = "systems";
|
||||
inputs.aquamarine.follows = "aquamarine";
|
||||
inputs.hyprutils.follows = "hyprutils";
|
||||
inputs.hyprlang.follows = "hyprlang";
|
||||
inputs.hyprgraphics.follows = "hyprgraphics";
|
||||
inputs.hyprwayland-scanner.follows = "hyprwayland-scanner";
|
||||
};
|
||||
};
|
||||
|
||||
outputs = {
|
||||
|
|
@ -56,10 +82,13 @@
|
|||
overlays = {
|
||||
default = self.overlays.hyprpaper;
|
||||
hyprpaper = lib.composeManyExtensions [
|
||||
inputs.aquamarine.overlays.default
|
||||
inputs.hyprgraphics.overlays.default
|
||||
inputs.hyprlang.overlays.default
|
||||
inputs.hyprutils.overlays.default
|
||||
inputs.hyprwayland-scanner.overlays.default
|
||||
inputs.hyprtoolkit.overlays.default
|
||||
inputs.hyprwire.overlays.default
|
||||
(final: prev: rec {
|
||||
hyprpaper = final.callPackage ./nix/default.nix {
|
||||
stdenv = final.gcc15Stdenv;
|
||||
|
|
|
|||
144
hw-protocols/hyprpaper_core.xml
Normal file
144
hw-protocols/hyprpaper_core.xml
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="hyprpaper_core" version="1">
|
||||
<copyright>
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2025, Hypr Development
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
</copyright>
|
||||
|
||||
<object name="hyprpaper_core_manager" version="1">
|
||||
<description summary="manager object">
|
||||
This is the core manager object for hyprpaper operations
|
||||
</description>
|
||||
|
||||
<c2s name="get_wallpaper_object">
|
||||
<description summary="Get a wallpaper object">
|
||||
Creates a wallpaper object
|
||||
</description>
|
||||
<returns iface="hyprpaper_wallpaper"/>
|
||||
</c2s>
|
||||
|
||||
<s2c name="add_monitor">
|
||||
<description summary="New monitor added">
|
||||
Emitted when a new monitor is added.
|
||||
</description>
|
||||
<arg name="monitor_name" type="varchar" summary="the monitor's name"/>
|
||||
</s2c>
|
||||
|
||||
<s2c name="remove_monitor">
|
||||
<description summary="A monitor was removed">
|
||||
Emitted when a monitor is removed.
|
||||
</description>
|
||||
<arg name="monitor_name" type="varchar" summary="the monitor's name"/>
|
||||
</s2c>
|
||||
|
||||
<c2s name="destroy" destructor="true">
|
||||
<description summary="Destroy this object">
|
||||
Destroys this object. Children remain alive until destroyed.
|
||||
</description>
|
||||
</c2s>
|
||||
</object>
|
||||
|
||||
<enum name="wallpaper_fit_mode">
|
||||
<value idx="0" name="stretch"/>
|
||||
<value idx="1" name="cover"/>
|
||||
<value idx="2" name="contain"/>
|
||||
<value idx="3" name="tile"/>
|
||||
</enum>
|
||||
|
||||
<enum name="wallpaper_errors">
|
||||
<value idx="0" name="inert_wallpaper_object" description="attempted to use an inert wallpaper object"/>
|
||||
</enum>
|
||||
|
||||
<enum name="applying_error">
|
||||
<value idx="0" name="invalid_path" description="path provided was invalid"/>
|
||||
<value idx="1" name="invalid_monitor" description="monitor provided was invalid"/>
|
||||
<value idx="2" name="unknown_error" description="unknown error"/>
|
||||
</enum>
|
||||
|
||||
<object name="hyprpaper_wallpaper" version="1">
|
||||
<description summary="wallpaper object">
|
||||
This is an object describing a wallpaper
|
||||
</description>
|
||||
|
||||
<c2s name="path">
|
||||
<description summary="Set a path">
|
||||
Set a file path for the wallpaper. This has to be an absolute path from the fs root.
|
||||
This is required.
|
||||
</description>
|
||||
<arg name="wallpaper" type="varchar" summary="path"/>
|
||||
</c2s>
|
||||
|
||||
<c2s name="fit_mode">
|
||||
<description summary="Set a fit mode">
|
||||
Set a fit mode for the wallpaper. This is set to cover by default.
|
||||
</description>
|
||||
<arg name="fit_mode" type="enum" interface="wallpaper_fit_mode" summary="path"/>
|
||||
</c2s>
|
||||
|
||||
<c2s name="monitor_name">
|
||||
<description summary="Set the monitor name">
|
||||
Set a monitor for the wallpaper. Setting this to empty (or not setting at all) will
|
||||
treat this as a wildcard fallback.
|
||||
|
||||
See hyprpaper_core_manager.add_monitor and hyprpaper_core_manager.remove_monitor
|
||||
for tracking monitor names.
|
||||
</description>
|
||||
<arg name="monitor_name" type="varchar" summary="monitor name"/>
|
||||
</c2s>
|
||||
|
||||
<c2s name="apply">
|
||||
<description summary="Apply this wallpaper">
|
||||
Applies this object's state to the wallpaper state. Will emit .success on success,
|
||||
and .failed on failure.
|
||||
|
||||
This object becomes inert after .succeess or .failed, the only valid operation
|
||||
is to destroy it afterwards.
|
||||
</description>
|
||||
</c2s>
|
||||
|
||||
<s2c name="success">
|
||||
<description summary="Operation succeeded">
|
||||
Wallpaper was applied successfully.
|
||||
</description>
|
||||
</s2c>
|
||||
|
||||
<s2c name="failed">
|
||||
<description summary="Operation failed">
|
||||
Wallpaper was not applied. See the error field for more information.
|
||||
</description>
|
||||
<arg name="error" type="enum" interface="hyprpaper_wallpaper_application_error" summary="path"/>
|
||||
</s2c>
|
||||
|
||||
<c2s name="destroy" destructor="true">
|
||||
<description summary="Destroy this object">
|
||||
Destroys this object.
|
||||
</description>
|
||||
</c2s>
|
||||
</object>
|
||||
</protocol>
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
stdenv,
|
||||
pkg-config,
|
||||
cmake,
|
||||
aquamarine,
|
||||
cairo,
|
||||
expat,
|
||||
file,
|
||||
|
|
@ -10,9 +11,12 @@
|
|||
hyprgraphics,
|
||||
hyprlang,
|
||||
hyprutils,
|
||||
hyprtoolkit,
|
||||
hyprwire,
|
||||
hyprwayland-scanner,
|
||||
libdatrie,
|
||||
libGL,
|
||||
libdatrie,
|
||||
libdrm,
|
||||
libjpeg,
|
||||
libjxl,
|
||||
libselinux,
|
||||
|
|
@ -54,11 +58,13 @@ stdenv.mkDerivation {
|
|||
nativeBuildInputs = [
|
||||
cmake
|
||||
hyprwayland-scanner
|
||||
hyprwire
|
||||
pkg-config
|
||||
wayland-scanner
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
aquamarine
|
||||
cairo
|
||||
expat
|
||||
file
|
||||
|
|
@ -66,8 +72,11 @@ stdenv.mkDerivation {
|
|||
hyprgraphics
|
||||
hyprlang
|
||||
hyprutils
|
||||
libdatrie
|
||||
hyprtoolkit
|
||||
hyprwire
|
||||
libGL
|
||||
libdatrie
|
||||
libdrm
|
||||
libjpeg
|
||||
libjxl
|
||||
libselinux
|
||||
|
|
|
|||
|
|
@ -1,689 +0,0 @@
|
|||
#include "Hyprpaper.hpp"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <signal.h>
|
||||
#include <sys/types.h>
|
||||
#include <malloc.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static void setMallocThreshold() {
|
||||
#ifdef M_TRIM_THRESHOLD
|
||||
// The default is 128 pages,
|
||||
// which is very large and can lead to a lot of memory used for no reason
|
||||
// because trimming hasn't happened
|
||||
static const int PAGESIZE = sysconf(_SC_PAGESIZE);
|
||||
mallopt(M_TRIM_THRESHOLD, 6 * PAGESIZE);
|
||||
#endif
|
||||
}
|
||||
|
||||
CHyprpaper::CHyprpaper() {
|
||||
setMallocThreshold();
|
||||
}
|
||||
|
||||
static void handleGlobal(CCWlRegistry* registry, uint32_t name, const char* interface, uint32_t version) {
|
||||
if (strcmp(interface, wl_compositor_interface.name) == 0) {
|
||||
g_pHyprpaper->m_pCompositor = makeShared<CCWlCompositor>((wl_proxy*)wl_registry_bind((wl_registry*)registry->resource(), name, &wl_compositor_interface, 4));
|
||||
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
|
||||
g_pHyprpaper->m_pSHM = makeShared<CCWlShm>((wl_proxy*)wl_registry_bind((wl_registry*)registry->resource(), name, &wl_shm_interface, 1));
|
||||
} else if (strcmp(interface, wl_output_interface.name) == 0) {
|
||||
g_pHyprpaper->m_mtTickMutex.lock();
|
||||
|
||||
const auto PMONITOR = g_pHyprpaper->m_vMonitors.emplace_back(std::make_unique<SMonitor>()).get();
|
||||
PMONITOR->wayland_name = name;
|
||||
PMONITOR->name = "";
|
||||
PMONITOR->output = makeShared<CCWlOutput>((wl_proxy*)wl_registry_bind((wl_registry*)registry->resource(), name, &wl_output_interface, 4));
|
||||
PMONITOR->registerListeners();
|
||||
|
||||
g_pHyprpaper->m_mtTickMutex.unlock();
|
||||
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
|
||||
g_pHyprpaper->createSeat(makeShared<CCWlSeat>((wl_proxy*)wl_registry_bind((wl_registry*)registry->resource(), name, &wl_seat_interface, 7)));
|
||||
} else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
|
||||
g_pHyprpaper->m_pLayerShell = makeShared<CCZwlrLayerShellV1>((wl_proxy*)wl_registry_bind((wl_registry*)registry->resource(), name, &zwlr_layer_shell_v1_interface, 1));
|
||||
} else if (strcmp(interface, wp_fractional_scale_manager_v1_interface.name) == 0 && !g_pHyprpaper->m_bNoFractionalScale) {
|
||||
g_pHyprpaper->m_pFractionalScale =
|
||||
makeShared<CCWpFractionalScaleManagerV1>((wl_proxy*)wl_registry_bind((wl_registry*)registry->resource(), name, &wp_fractional_scale_manager_v1_interface, 1));
|
||||
} else if (strcmp(interface, wp_viewporter_interface.name) == 0) {
|
||||
g_pHyprpaper->m_pViewporter = makeShared<CCWpViewporter>((wl_proxy*)wl_registry_bind((wl_registry*)registry->resource(), name, &wp_viewporter_interface, 1));
|
||||
} else if (strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) {
|
||||
g_pHyprpaper->m_pCursorShape =
|
||||
makeShared<CCWpCursorShapeManagerV1>((wl_proxy*)wl_registry_bind((wl_registry*)registry->resource(), name, &wp_cursor_shape_manager_v1_interface, 1));
|
||||
}
|
||||
}
|
||||
|
||||
static void handleGlobalRemove(CCWlRegistry* registry, uint32_t name) {
|
||||
for (auto& m : g_pHyprpaper->m_vMonitors) {
|
||||
if (m->wayland_name == name) {
|
||||
Debug::log(LOG, "Destroying output {}", m->name);
|
||||
g_pHyprpaper->clearWallpaperFromMonitor(m->name);
|
||||
std::erase_if(g_pHyprpaper->m_vMonitors, [&](const auto& other) { return other->wayland_name == name; });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CHyprpaper::init() {
|
||||
|
||||
if (!lockSingleInstance()) {
|
||||
Debug::log(CRIT, "Cannot launch multiple instances of Hyprpaper at once!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
removeOldHyprpaperImages();
|
||||
|
||||
m_sDisplay = (wl_display*)wl_display_connect(nullptr);
|
||||
|
||||
if (!m_sDisplay) {
|
||||
Debug::log(CRIT, "No wayland compositor running!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// run
|
||||
auto REGISTRY = makeShared<CCWlRegistry>((wl_proxy*)wl_display_get_registry(m_sDisplay));
|
||||
REGISTRY->setGlobal(::handleGlobal);
|
||||
REGISTRY->setGlobalRemove(::handleGlobalRemove);
|
||||
|
||||
wl_display_roundtrip(m_sDisplay);
|
||||
|
||||
while (m_vMonitors.size() < 1 || m_vMonitors[0]->name.empty()) {
|
||||
wl_display_dispatch(m_sDisplay);
|
||||
}
|
||||
|
||||
g_pConfigManager = std::make_unique<CConfigManager>();
|
||||
g_pIPCSocket = std::make_unique<CIPCSocket>();
|
||||
|
||||
g_pConfigManager->parse();
|
||||
|
||||
preloadAllWallpapersFromConfig();
|
||||
|
||||
if (std::any_cast<Hyprlang::INT>(g_pConfigManager->config->getConfigValue("ipc")))
|
||||
g_pIPCSocket->initialize();
|
||||
|
||||
do {
|
||||
std::lock_guard<std::mutex> lg(m_mtTickMutex);
|
||||
tick(true);
|
||||
} while (wl_display_dispatch(m_sDisplay) != -1);
|
||||
|
||||
unlockSingleInstance();
|
||||
}
|
||||
|
||||
void CHyprpaper::tick(bool force) {
|
||||
bool reload = g_pIPCSocket && g_pIPCSocket->mainThreadParseRequest();
|
||||
|
||||
if (!reload && !force)
|
||||
return;
|
||||
|
||||
preloadAllWallpapersFromConfig();
|
||||
ensurePoolBuffersPresent();
|
||||
|
||||
recheckAllMonitors();
|
||||
}
|
||||
|
||||
bool CHyprpaper::isPreloaded(const std::string& path) {
|
||||
for (auto& [pt, wt] : m_mWallpaperTargets) {
|
||||
if (pt == path)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CHyprpaper::unloadWallpaper(const std::string& path) {
|
||||
bool found = false;
|
||||
|
||||
for (auto& [ewp, cls] : m_mWallpaperTargets) {
|
||||
if (ewp == path) {
|
||||
// found
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
Debug::log(LOG, "Cannot unload a target that was not loaded!");
|
||||
return;
|
||||
}
|
||||
|
||||
// clean buffers
|
||||
for (auto it = m_vBuffers.begin(); it != m_vBuffers.end();) {
|
||||
|
||||
if (it->get()->target != path) {
|
||||
it++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto PRELOADPATH = it->get()->name;
|
||||
|
||||
Debug::log(LOG, "Unloading target {}, preload path {}", path, PRELOADPATH);
|
||||
|
||||
std::filesystem::remove(PRELOADPATH);
|
||||
|
||||
destroyBuffer(it->get());
|
||||
|
||||
it = m_vBuffers.erase(it);
|
||||
}
|
||||
|
||||
m_mWallpaperTargets.erase(path); // will free the cairo surface
|
||||
}
|
||||
|
||||
void CHyprpaper::preloadAllWallpapersFromConfig() {
|
||||
if (g_pConfigManager->m_dRequestedPreloads.empty())
|
||||
return;
|
||||
|
||||
for (auto& wp : g_pConfigManager->m_dRequestedPreloads) {
|
||||
|
||||
// check if it doesnt exist
|
||||
bool exists = false;
|
||||
for (auto& [ewp, cls] : m_mWallpaperTargets) {
|
||||
if (ewp == wp) {
|
||||
Debug::log(LOG, "Ignoring request to preload {} as it already is preloaded!", ewp);
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (exists)
|
||||
continue;
|
||||
|
||||
m_mWallpaperTargets[wp] = CWallpaperTarget();
|
||||
if (std::filesystem::is_symlink(wp)) {
|
||||
auto real_wp = std::filesystem::read_symlink(wp);
|
||||
std::filesystem::path absolute_path = std::filesystem::path(wp).parent_path() / real_wp;
|
||||
absolute_path = absolute_path.lexically_normal();
|
||||
m_mWallpaperTargets[wp].create(absolute_path);
|
||||
} else {
|
||||
m_mWallpaperTargets[wp].create(wp);
|
||||
}
|
||||
}
|
||||
|
||||
g_pConfigManager->m_dRequestedPreloads.clear();
|
||||
}
|
||||
|
||||
void CHyprpaper::recheckAllMonitors() {
|
||||
for (auto& m : m_vMonitors) {
|
||||
recheckMonitor(m.get());
|
||||
}
|
||||
}
|
||||
|
||||
void CHyprpaper::createSeat(SP<CCWlSeat> pSeat) {
|
||||
m_pSeat = pSeat;
|
||||
|
||||
pSeat->setCapabilities([this](CCWlSeat* r, wl_seat_capability caps) {
|
||||
if (caps & WL_SEAT_CAPABILITY_POINTER) {
|
||||
m_pSeatPointer = makeShared<CCWlPointer>(m_pSeat->sendGetPointer());
|
||||
if (!m_pCursorShape)
|
||||
Debug::log(WARN, "No cursor-shape-v1 support from the compositor: cursor will be blank");
|
||||
else
|
||||
m_pSeatCursorShapeDevice = makeShared<CCWpCursorShapeDeviceV1>(m_pCursorShape->sendGetPointer(m_pSeatPointer->resource()));
|
||||
|
||||
m_pSeatPointer->setEnter([this](CCWlPointer* r, uint32_t serial, wl_proxy* surface, wl_fixed_t x, wl_fixed_t y) {
|
||||
if (!m_pCursorShape) {
|
||||
m_pSeatPointer->sendSetCursor(serial, nullptr, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
m_pSeatCursorShapeDevice->sendSetShape(serial, wpCursorShapeDeviceV1Shape::WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);
|
||||
});
|
||||
} else
|
||||
Debug::log(LOG, "No pointer capability from the compositor");
|
||||
});
|
||||
}
|
||||
|
||||
void CHyprpaper::recheckMonitor(SMonitor* pMonitor) {
|
||||
ensureMonitorHasActiveWallpaper(pMonitor);
|
||||
|
||||
if (pMonitor->wantsACK) {
|
||||
pMonitor->wantsACK = false;
|
||||
pMonitor->pCurrentLayerSurface->pLayerSurface->sendAckConfigure(pMonitor->configureSerial);
|
||||
}
|
||||
|
||||
if (pMonitor->wantsReload) {
|
||||
pMonitor->wantsReload = false;
|
||||
renderWallpaperForMonitor(pMonitor);
|
||||
}
|
||||
}
|
||||
|
||||
void CHyprpaper::removeOldHyprpaperImages() {
|
||||
int cleaned = 0;
|
||||
uint64_t memoryFreed = 0;
|
||||
|
||||
for (const auto& entry : std::filesystem::directory_iterator(std::string(getenv("XDG_RUNTIME_DIR")))) {
|
||||
if (entry.is_directory())
|
||||
continue;
|
||||
|
||||
const auto FILENAME = entry.path().filename().string();
|
||||
|
||||
if (FILENAME.contains(".hyprpaper_")) {
|
||||
// unlink it
|
||||
|
||||
memoryFreed += entry.file_size();
|
||||
if (!std::filesystem::remove(entry.path()))
|
||||
Debug::log(LOG, "Couldn't remove {}", entry.path().string());
|
||||
cleaned++;
|
||||
}
|
||||
}
|
||||
|
||||
if (cleaned != 0)
|
||||
Debug::log(LOG, "Cleaned old hyprpaper preloads ({}), removing {:.1f}MB", cleaned, ((float)memoryFreed) / 1000000.f);
|
||||
}
|
||||
|
||||
SMonitor* CHyprpaper::getMonitorFromName(const std::string& monname) {
|
||||
bool useDesc = false;
|
||||
std::string desc = "";
|
||||
if (monname.find("desc:") == 0) {
|
||||
useDesc = true;
|
||||
desc = monname.substr(5);
|
||||
}
|
||||
|
||||
for (auto& m : m_vMonitors) {
|
||||
if (useDesc && m->description.find(desc) == 0)
|
||||
return m.get();
|
||||
|
||||
if (m->name == monname)
|
||||
return m.get();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CHyprpaper::ensurePoolBuffersPresent() {
|
||||
bool anyNewBuffers = false;
|
||||
|
||||
for (auto& [file, wt] : m_mWallpaperTargets) {
|
||||
for (auto& m : m_vMonitors) {
|
||||
|
||||
if (m->size == Vector2D())
|
||||
continue;
|
||||
|
||||
auto it = std::find_if(m_vBuffers.begin(), m_vBuffers.end(), [wt = &wt, &m](const std::unique_ptr<SPoolBuffer>& el) {
|
||||
auto scale = std::round((m->pCurrentLayerSurface && m->pCurrentLayerSurface->pFractionalScaleInfo ? m->pCurrentLayerSurface->fScale : m->scale) * 120.0) / 120.0;
|
||||
return el->target == wt->m_szPath && vectorDeltaLessThan(el->pixelSize, m->size * scale, 1);
|
||||
});
|
||||
|
||||
if (it == m_vBuffers.end()) {
|
||||
// create
|
||||
const auto PBUFFER = m_vBuffers.emplace_back(std::make_unique<SPoolBuffer>()).get();
|
||||
auto scale = std::round((m->pCurrentLayerSurface && m->pCurrentLayerSurface->pFractionalScaleInfo ? m->pCurrentLayerSurface->fScale : m->scale) * 120.0) / 120.0;
|
||||
createBuffer(PBUFFER, m->size.x * scale, m->size.y * scale, WL_SHM_FORMAT_ARGB8888);
|
||||
|
||||
PBUFFER->target = wt.m_szPath;
|
||||
|
||||
Debug::log(LOG, "Buffer created for target {}, Shared Memory usage: {:.1f}MB", wt.m_szPath, PBUFFER->size / 1000000.f);
|
||||
|
||||
anyNewBuffers = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (anyNewBuffers) {
|
||||
uint64_t bytesUsed = 0;
|
||||
|
||||
for (auto& bf : m_vBuffers) {
|
||||
bytesUsed += bf->size;
|
||||
}
|
||||
|
||||
Debug::log(LOG, "Total SM usage for all buffers: {:.1f}MB", bytesUsed / 1000000.f);
|
||||
}
|
||||
}
|
||||
|
||||
void CHyprpaper::clearWallpaperFromMonitor(const std::string& monname) {
|
||||
|
||||
const auto PMONITOR = getMonitorFromName(monname);
|
||||
|
||||
if (!PMONITOR)
|
||||
return;
|
||||
|
||||
auto it = m_mMonitorActiveWallpaperTargets.find(PMONITOR);
|
||||
|
||||
if (it != m_mMonitorActiveWallpaperTargets.end())
|
||||
m_mMonitorActiveWallpaperTargets.erase(it);
|
||||
|
||||
PMONITOR->hasATarget = true;
|
||||
|
||||
if (PMONITOR->pCurrentLayerSurface) {
|
||||
|
||||
PMONITOR->pCurrentLayerSurface = nullptr;
|
||||
|
||||
PMONITOR->wantsACK = false;
|
||||
PMONITOR->wantsReload = false;
|
||||
PMONITOR->initialized = false;
|
||||
PMONITOR->readyForLS = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CHyprpaper::ensureMonitorHasActiveWallpaper(SMonitor* pMonitor) {
|
||||
if (!pMonitor->readyForLS || !pMonitor->hasATarget)
|
||||
return;
|
||||
|
||||
auto it = m_mMonitorActiveWallpaperTargets.find(pMonitor);
|
||||
|
||||
if (it == m_mMonitorActiveWallpaperTargets.end()) {
|
||||
m_mMonitorActiveWallpaperTargets[pMonitor] = nullptr;
|
||||
it = m_mMonitorActiveWallpaperTargets.find(pMonitor);
|
||||
}
|
||||
|
||||
if (it->second)
|
||||
return; // has
|
||||
|
||||
// get the target
|
||||
for (auto& [mon, path1] : m_mMonitorActiveWallpapers) {
|
||||
if (mon.find("desc:") != 0)
|
||||
continue;
|
||||
|
||||
if (pMonitor->description.find(mon.substr(5)) == 0) {
|
||||
for (auto& [path2, target] : m_mWallpaperTargets) {
|
||||
if (path1 == path2) {
|
||||
it->second = ⌖
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!it->second) {
|
||||
for (auto& [mon, path1] : m_mMonitorActiveWallpapers) {
|
||||
if (mon == pMonitor->name) {
|
||||
for (auto& [path2, target] : m_mWallpaperTargets) {
|
||||
if (path1 == path2) {
|
||||
it->second = ⌖
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!it->second) {
|
||||
// try to find a wildcard
|
||||
for (auto& [mon, path1] : m_mMonitorActiveWallpapers) {
|
||||
if (mon.empty()) {
|
||||
for (auto& [path2, target] : m_mWallpaperTargets) {
|
||||
if (path1 == path2) {
|
||||
it->second = ⌖
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!it->second) {
|
||||
pMonitor->hasATarget = false;
|
||||
Debug::log(WARN, "Monitor {} does not have a target! A wallpaper will not be created.", pMonitor->name);
|
||||
return;
|
||||
}
|
||||
|
||||
// create it for thy if it doesnt have
|
||||
if (!pMonitor->pCurrentLayerSurface)
|
||||
createLSForMonitor(pMonitor);
|
||||
else
|
||||
pMonitor->wantsReload = true;
|
||||
}
|
||||
|
||||
void CHyprpaper::createLSForMonitor(SMonitor* pMonitor) {
|
||||
pMonitor->pCurrentLayerSurface = pMonitor->layerSurfaces.emplace_back(std::make_unique<CLayerSurface>(pMonitor)).get();
|
||||
}
|
||||
|
||||
bool CHyprpaper::setCloexec(const int& FD) {
|
||||
long flags = fcntl(FD, F_GETFD);
|
||||
if (flags == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fcntl(FD, F_SETFD, flags | FD_CLOEXEC) == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int CHyprpaper::createPoolFile(size_t size, std::string& name) {
|
||||
const auto XDGRUNTIMEDIR = getenv("XDG_RUNTIME_DIR");
|
||||
if (!XDGRUNTIMEDIR) {
|
||||
Debug::log(CRIT, "XDG_RUNTIME_DIR not set!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
name = std::string(XDGRUNTIMEDIR) + "/.hyprpaper_XXXXXX";
|
||||
|
||||
const auto FD = mkstemp((char*)name.c_str());
|
||||
if (FD < 0) {
|
||||
Debug::log(CRIT, "createPoolFile: fd < 0");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!setCloexec(FD)) {
|
||||
close(FD);
|
||||
Debug::log(CRIT, "createPoolFile: !setCloexec");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (ftruncate(FD, size) < 0) {
|
||||
close(FD);
|
||||
Debug::log(CRIT, "createPoolFile: ftruncate < 0");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return FD;
|
||||
}
|
||||
|
||||
void CHyprpaper::createBuffer(SPoolBuffer* pBuffer, int32_t w, int32_t h, uint32_t format) {
|
||||
const size_t STRIDE = w * 4;
|
||||
const size_t SIZE = STRIDE * h;
|
||||
|
||||
std::string name;
|
||||
const auto FD = createPoolFile(SIZE, name);
|
||||
|
||||
if (FD == -1) {
|
||||
Debug::log(CRIT, "Unable to create pool file!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
const auto DATA = mmap(nullptr, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, FD, 0);
|
||||
auto POOL = makeShared<CCWlShmPool>(g_pHyprpaper->m_pSHM->sendCreatePool(FD, SIZE));
|
||||
pBuffer->buffer = makeShared<CCWlBuffer>(POOL->sendCreateBuffer(0, w, h, STRIDE, format));
|
||||
POOL.reset();
|
||||
|
||||
close(FD);
|
||||
|
||||
pBuffer->size = SIZE;
|
||||
pBuffer->data = DATA;
|
||||
pBuffer->surface = cairo_image_surface_create_for_data((unsigned char*)DATA, CAIRO_FORMAT_ARGB32, w, h, STRIDE);
|
||||
pBuffer->cairo = cairo_create(pBuffer->surface);
|
||||
pBuffer->pixelSize = Vector2D(w, h);
|
||||
pBuffer->name = name;
|
||||
}
|
||||
|
||||
void CHyprpaper::destroyBuffer(SPoolBuffer* pBuffer) {
|
||||
pBuffer->buffer.reset();
|
||||
cairo_destroy(pBuffer->cairo);
|
||||
cairo_surface_destroy(pBuffer->surface);
|
||||
munmap(pBuffer->data, pBuffer->size);
|
||||
|
||||
pBuffer->buffer = nullptr;
|
||||
}
|
||||
|
||||
SPoolBuffer* CHyprpaper::getPoolBuffer(SMonitor* pMonitor, CWallpaperTarget* pWallpaperTarget) {
|
||||
const auto IT = std::find_if(m_vBuffers.begin(), m_vBuffers.end(), [&](const std::unique_ptr<SPoolBuffer>& el) {
|
||||
auto scale =
|
||||
std::round((pMonitor->pCurrentLayerSurface && pMonitor->pCurrentLayerSurface->pFractionalScaleInfo ? pMonitor->pCurrentLayerSurface->fScale : pMonitor->scale) *
|
||||
120.0) /
|
||||
120.0;
|
||||
return el->target == pWallpaperTarget->m_szPath && vectorDeltaLessThan(el->pixelSize, pMonitor->size * scale, 1);
|
||||
});
|
||||
|
||||
if (IT == m_vBuffers.end())
|
||||
return nullptr;
|
||||
return IT->get();
|
||||
}
|
||||
|
||||
void CHyprpaper::renderWallpaperForMonitor(SMonitor* pMonitor) {
|
||||
static auto PRENDERSPLASH = Hyprlang::CSimpleConfigValue<Hyprlang::INT>(g_pConfigManager->config.get(), "splash");
|
||||
static auto PSPLASHOFFSET = Hyprlang::CSimpleConfigValue<Hyprlang::FLOAT>(g_pConfigManager->config.get(), "splash_offset");
|
||||
|
||||
if (!m_mMonitorActiveWallpaperTargets[pMonitor])
|
||||
recheckMonitor(pMonitor);
|
||||
|
||||
const auto PWALLPAPERTARGET = m_mMonitorActiveWallpaperTargets[pMonitor];
|
||||
const auto CONTAIN = m_mMonitorWallpaperRenderData[pMonitor->name].contain;
|
||||
const auto TILE = m_mMonitorWallpaperRenderData[pMonitor->name].tile;
|
||||
|
||||
if (!PWALLPAPERTARGET) {
|
||||
Debug::log(CRIT, "wallpaper target null in render??");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
auto* PBUFFER = getPoolBuffer(pMonitor, PWALLPAPERTARGET);
|
||||
|
||||
if (!PBUFFER) {
|
||||
Debug::log(LOG, "Pool buffer missing for available target??");
|
||||
ensurePoolBuffersPresent();
|
||||
|
||||
PBUFFER = getPoolBuffer(pMonitor, PWALLPAPERTARGET);
|
||||
|
||||
if (!PBUFFER) {
|
||||
Debug::log(LOG, "Pool buffer failed #2. Ignoring WP.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const double SURFACESCALE = pMonitor->pCurrentLayerSurface && pMonitor->pCurrentLayerSurface->pFractionalScaleInfo ? pMonitor->pCurrentLayerSurface->fScale : pMonitor->scale;
|
||||
const Vector2D DIMENSIONS = Vector2D{std::round(pMonitor->size.x * SURFACESCALE), std::round(pMonitor->size.y * SURFACESCALE)};
|
||||
|
||||
const auto PCAIRO = PBUFFER->cairo;
|
||||
cairo_save(PCAIRO);
|
||||
cairo_set_operator(PCAIRO, CAIRO_OPERATOR_CLEAR);
|
||||
cairo_paint(PCAIRO);
|
||||
cairo_restore(PCAIRO);
|
||||
|
||||
// always draw a black background behind the wallpaper
|
||||
cairo_set_source_rgb(PCAIRO, 0, 0, 0);
|
||||
cairo_rectangle(PCAIRO, 0, 0, DIMENSIONS.x, DIMENSIONS.y);
|
||||
cairo_fill(PCAIRO);
|
||||
cairo_surface_flush(PBUFFER->surface);
|
||||
|
||||
// get scale
|
||||
// we always do cover
|
||||
double scale;
|
||||
Vector2D origin;
|
||||
|
||||
const bool LOWASPECTRATIO = pMonitor->size.x / pMonitor->size.y > PWALLPAPERTARGET->m_vSize.x / PWALLPAPERTARGET->m_vSize.y;
|
||||
if ((CONTAIN && !LOWASPECTRATIO) || (!CONTAIN && LOWASPECTRATIO)) {
|
||||
scale = DIMENSIONS.x / PWALLPAPERTARGET->m_vSize.x;
|
||||
origin.y = -(PWALLPAPERTARGET->m_vSize.y * scale - DIMENSIONS.y) / 2.0 / scale;
|
||||
} else {
|
||||
scale = DIMENSIONS.y / PWALLPAPERTARGET->m_vSize.y;
|
||||
origin.x = -(PWALLPAPERTARGET->m_vSize.x * scale - DIMENSIONS.x) / 2.0 / scale;
|
||||
}
|
||||
|
||||
Debug::log(LOG, "Image data for {}: {} at [{:.2f}, {:.2f}], scale: {:.2f} (original image size: [{}, {}])", pMonitor->name, PWALLPAPERTARGET->m_szPath, origin.x, origin.y,
|
||||
scale, (int)PWALLPAPERTARGET->m_vSize.x, (int)PWALLPAPERTARGET->m_vSize.y);
|
||||
|
||||
if (TILE) {
|
||||
cairo_pattern_t* pattern = cairo_pattern_create_for_surface(PWALLPAPERTARGET->m_pCairoSurface->cairo());
|
||||
cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
|
||||
cairo_set_source(PCAIRO, pattern);
|
||||
} else {
|
||||
cairo_scale(PCAIRO, scale, scale);
|
||||
cairo_set_source_surface(PCAIRO, PWALLPAPERTARGET->m_pCairoSurface->cairo(), origin.x, origin.y);
|
||||
}
|
||||
|
||||
cairo_paint(PCAIRO);
|
||||
|
||||
if (*PRENDERSPLASH && getenv("HYPRLAND_INSTANCE_SIGNATURE")) {
|
||||
auto SPLASH = execAndGet("hyprctl splash");
|
||||
if (!SPLASH.empty())
|
||||
SPLASH.pop_back();
|
||||
|
||||
Debug::log(LOG, "Rendering splash: {}", SPLASH);
|
||||
|
||||
cairo_select_font_face(PCAIRO, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
|
||||
|
||||
const auto FONTSIZE = (int)(DIMENSIONS.y / 76.0 / scale);
|
||||
cairo_set_font_size(PCAIRO, FONTSIZE);
|
||||
|
||||
static auto PSPLASHCOLOR = Hyprlang::CSimpleConfigValue<Hyprlang::INT>(g_pConfigManager->config.get(), "splash_color");
|
||||
|
||||
Debug::log(LOG, "Splash color: {:x}", *PSPLASHCOLOR);
|
||||
|
||||
cairo_set_source_rgba(PCAIRO, ((*PSPLASHCOLOR >> 16) & 0xFF) / 255.0, ((*PSPLASHCOLOR >> 8) & 0xFF) / 255.0, (*PSPLASHCOLOR & 0xFF) / 255.0,
|
||||
((*PSPLASHCOLOR >> 24) & 0xFF) / 255.0);
|
||||
|
||||
cairo_text_extents_t textExtents;
|
||||
cairo_text_extents(PCAIRO, SPLASH.c_str(), &textExtents);
|
||||
|
||||
cairo_move_to(PCAIRO, ((DIMENSIONS.x - textExtents.width * scale) / 2.0) / scale, ((DIMENSIONS.y * (100 - *PSPLASHOFFSET)) / 100 - textExtents.height * scale) / scale);
|
||||
|
||||
Debug::log(LOG, "Splash font size: {}, pos: {:.2f}, {:.2f}", FONTSIZE, (DIMENSIONS.x - textExtents.width) / 2.0 / scale,
|
||||
((DIMENSIONS.y * (100 - *PSPLASHOFFSET)) / 100 - textExtents.height * scale) / scale);
|
||||
|
||||
cairo_show_text(PCAIRO, SPLASH.c_str());
|
||||
|
||||
cairo_surface_flush(PWALLPAPERTARGET->m_pCairoSurface->cairo());
|
||||
}
|
||||
|
||||
cairo_restore(PCAIRO);
|
||||
|
||||
if (pMonitor->pCurrentLayerSurface) {
|
||||
pMonitor->pCurrentLayerSurface->pSurface->sendAttach(PBUFFER->buffer.get(), 0, 0);
|
||||
pMonitor->pCurrentLayerSurface->pSurface->sendSetBufferScale(pMonitor->pCurrentLayerSurface->pFractionalScaleInfo ? 1 : pMonitor->scale);
|
||||
pMonitor->pCurrentLayerSurface->pSurface->sendDamageBuffer(0, 0, 0xFFFF, 0xFFFF);
|
||||
|
||||
// our wps are always opaque
|
||||
auto opaqueRegion = makeShared<CCWlRegion>(g_pHyprpaper->m_pCompositor->sendCreateRegion());
|
||||
opaqueRegion->sendAdd(0, 0, PBUFFER->pixelSize.x, PBUFFER->pixelSize.y);
|
||||
pMonitor->pCurrentLayerSurface->pSurface->sendSetOpaqueRegion(opaqueRegion.get());
|
||||
|
||||
if (pMonitor->pCurrentLayerSurface->pFractionalScaleInfo) {
|
||||
Debug::log(LOG, "Submitting viewport dest size {}x{} for {:x}", static_cast<int>(std::round(pMonitor->size.x)), static_cast<int>(std::round(pMonitor->size.y)),
|
||||
(uintptr_t)pMonitor->pCurrentLayerSurface);
|
||||
pMonitor->pCurrentLayerSurface->pViewport->sendSetDestination(static_cast<int>(std::round(pMonitor->size.x)), static_cast<int>(std::round(pMonitor->size.y)));
|
||||
}
|
||||
pMonitor->pCurrentLayerSurface->pSurface->sendCommit();
|
||||
}
|
||||
|
||||
// check if we dont need to remove a wallpaper
|
||||
if (pMonitor->layerSurfaces.size() > 1) {
|
||||
for (auto it = pMonitor->layerSurfaces.begin(); it != pMonitor->layerSurfaces.end(); it++) {
|
||||
if (pMonitor->pCurrentLayerSurface != it->get()) {
|
||||
pMonitor->layerSurfaces.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CHyprpaper::lockSingleInstance() {
|
||||
const std::string XDG_RUNTIME_DIR = getenv("XDG_RUNTIME_DIR");
|
||||
|
||||
const auto LOCKFILE = XDG_RUNTIME_DIR + "/hyprpaper.lock";
|
||||
|
||||
if (std::filesystem::exists(LOCKFILE)) {
|
||||
std::ifstream ifs(LOCKFILE);
|
||||
std::string content((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
|
||||
|
||||
try {
|
||||
kill(std::stoull(content), 0);
|
||||
|
||||
if (errno != ESRCH)
|
||||
return false;
|
||||
} catch (std::exception& e) { ; }
|
||||
}
|
||||
|
||||
// create lockfile
|
||||
std::ofstream ofs(LOCKFILE, std::ios::trunc);
|
||||
|
||||
ofs << std::to_string(getpid());
|
||||
|
||||
ofs.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CHyprpaper::unlockSingleInstance() {
|
||||
const std::string XDG_RUNTIME_DIR = getenv("XDG_RUNTIME_DIR");
|
||||
const auto LOCKFILE = XDG_RUNTIME_DIR + "/hyprpaper.lock";
|
||||
unlink(LOCKFILE.c_str());
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "config/ConfigManager.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "helpers/MiscFunctions.hpp"
|
||||
#include "helpers/Monitor.hpp"
|
||||
#include "helpers/PoolBuffer.hpp"
|
||||
#include "ipc/Socket.hpp"
|
||||
#include "render/WallpaperTarget.hpp"
|
||||
#include <mutex>
|
||||
|
||||
#include "protocols/cursor-shape-v1.hpp"
|
||||
#include "protocols/fractional-scale-v1.hpp"
|
||||
#include "protocols/linux-dmabuf-v1.hpp"
|
||||
#include "protocols/viewporter.hpp"
|
||||
#include "protocols/wayland.hpp"
|
||||
#include "protocols/wlr-layer-shell-unstable-v1.hpp"
|
||||
|
||||
struct SWallpaperRenderData {
|
||||
bool contain = false;
|
||||
bool tile = false;
|
||||
};
|
||||
|
||||
class CHyprpaper {
|
||||
public:
|
||||
// important
|
||||
wl_display* m_sDisplay = nullptr;
|
||||
SP<CCWlCompositor> m_pCompositor;
|
||||
SP<CCWlShm> m_pSHM;
|
||||
SP<CCZwlrLayerShellV1> m_pLayerShell;
|
||||
SP<CCWpFractionalScaleManagerV1> m_pFractionalScale;
|
||||
SP<CCWpViewporter> m_pViewporter;
|
||||
SP<CCWlSeat> m_pSeat;
|
||||
SP<CCWlPointer> m_pSeatPointer;
|
||||
SP<CCWpCursorShapeDeviceV1> m_pSeatCursorShapeDevice;
|
||||
SP<CCWpCursorShapeManagerV1> m_pCursorShape;
|
||||
|
||||
// init the utility
|
||||
CHyprpaper();
|
||||
void init();
|
||||
void tick(bool force);
|
||||
|
||||
std::unordered_map<std::string, CWallpaperTarget> m_mWallpaperTargets;
|
||||
std::unordered_map<std::string, std::string> m_mMonitorActiveWallpapers;
|
||||
std::unordered_map<std::string, SWallpaperRenderData> m_mMonitorWallpaperRenderData;
|
||||
std::unordered_map<SMonitor*, CWallpaperTarget*> m_mMonitorActiveWallpaperTargets;
|
||||
std::vector<std::unique_ptr<SPoolBuffer>> m_vBuffers;
|
||||
std::vector<std::unique_ptr<SMonitor>> m_vMonitors;
|
||||
|
||||
std::string m_szExplicitConfigPath;
|
||||
bool m_bNoFractionalScale = false;
|
||||
|
||||
void removeOldHyprpaperImages();
|
||||
void preloadAllWallpapersFromConfig();
|
||||
void recheckAllMonitors();
|
||||
void ensureMonitorHasActiveWallpaper(SMonitor*);
|
||||
void createLSForMonitor(SMonitor*);
|
||||
void renderWallpaperForMonitor(SMonitor*);
|
||||
void createBuffer(SPoolBuffer*, int32_t, int32_t, uint32_t);
|
||||
void destroyBuffer(SPoolBuffer*);
|
||||
int createPoolFile(size_t, std::string&);
|
||||
bool setCloexec(const int&);
|
||||
void clearWallpaperFromMonitor(const std::string&);
|
||||
SMonitor* getMonitorFromName(const std::string&);
|
||||
bool isPreloaded(const std::string&);
|
||||
void recheckMonitor(SMonitor*);
|
||||
void ensurePoolBuffersPresent();
|
||||
SPoolBuffer* getPoolBuffer(SMonitor*, CWallpaperTarget*);
|
||||
void unloadWallpaper(const std::string&);
|
||||
void createSeat(SP<CCWlSeat>);
|
||||
bool lockSingleInstance(); // fails on multi-instance
|
||||
void unlockSingleInstance();
|
||||
|
||||
std::mutex m_mtTickMutex;
|
||||
|
||||
SMonitor* m_pLastMonitor = nullptr;
|
||||
|
||||
private:
|
||||
bool m_bShouldExit = false;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CHyprpaper> g_pHyprpaper;
|
||||
|
|
@ -1,261 +1,102 @@
|
|||
#include "ConfigManager.hpp"
|
||||
#include "../Hyprpaper.hpp"
|
||||
#include <hyprutils/path/Path.hpp>
|
||||
#include <filesystem>
|
||||
#include <hyprlang.hpp>
|
||||
#include <hyprutils/path/Path.hpp>
|
||||
#include <string>
|
||||
#include <sys/ucontext.h>
|
||||
#include "../helpers/Logger.hpp"
|
||||
#include "WallpaperMatcher.hpp"
|
||||
|
||||
static Hyprlang::CParseResult handleWallpaper(const char* C, const char* V) {
|
||||
const std::string COMMAND = C;
|
||||
const std::string VALUE = V;
|
||||
Hyprlang::CParseResult result;
|
||||
|
||||
if (VALUE.find_first_of(',') == std::string::npos) {
|
||||
result.setError("wallpaper failed (syntax)");
|
||||
return result;
|
||||
}
|
||||
|
||||
auto MONITOR = VALUE.substr(0, VALUE.find_first_of(','));
|
||||
auto WALLPAPER = g_pConfigManager->trimPath(VALUE.substr(VALUE.find_first_of(',') + 1));
|
||||
|
||||
bool contain = false;
|
||||
|
||||
if (WALLPAPER.find("contain:") == 0) {
|
||||
WALLPAPER = WALLPAPER.substr(8);
|
||||
contain = true;
|
||||
}
|
||||
|
||||
bool tile = false;
|
||||
|
||||
if (WALLPAPER.find("tile:") == 0) {
|
||||
WALLPAPER = WALLPAPER.substr(5);
|
||||
tile = true;
|
||||
}
|
||||
|
||||
if (WALLPAPER[0] == '~') {
|
||||
static const char* const ENVHOME = getenv("HOME");
|
||||
WALLPAPER = std::string(ENVHOME) + WALLPAPER.substr(1);
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
|
||||
if (!std::filesystem::exists(WALLPAPER, ec)) {
|
||||
result.setError((std::string{"wallpaper failed ("} + (ec ? ec.message() : std::string{"no such file"}) + std::string{": "} + WALLPAPER + std::string{")"}).c_str());
|
||||
return result;
|
||||
}
|
||||
|
||||
if (std::find(g_pConfigManager->m_dRequestedPreloads.begin(), g_pConfigManager->m_dRequestedPreloads.end(), WALLPAPER) == g_pConfigManager->m_dRequestedPreloads.end() &&
|
||||
!g_pHyprpaper->isPreloaded(WALLPAPER)) {
|
||||
result.setError("wallpaper failed (not preloaded)");
|
||||
return result;
|
||||
}
|
||||
|
||||
g_pHyprpaper->clearWallpaperFromMonitor(MONITOR);
|
||||
g_pHyprpaper->m_mMonitorActiveWallpapers[MONITOR] = WALLPAPER;
|
||||
g_pHyprpaper->m_mMonitorWallpaperRenderData[MONITOR].contain = contain;
|
||||
g_pHyprpaper->m_mMonitorWallpaperRenderData[MONITOR].tile = tile;
|
||||
|
||||
if (MONITOR.empty()) {
|
||||
for (auto& m : g_pHyprpaper->m_vMonitors) {
|
||||
if (!m->hasATarget || m->wildcard) {
|
||||
g_pHyprpaper->clearWallpaperFromMonitor(m->name);
|
||||
g_pHyprpaper->m_mMonitorActiveWallpapers[m->name] = WALLPAPER;
|
||||
g_pHyprpaper->m_mMonitorWallpaperRenderData[m->name].contain = contain;
|
||||
g_pHyprpaper->m_mMonitorWallpaperRenderData[m->name].tile = tile;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const auto PMON = g_pHyprpaper->getMonitorFromName(MONITOR);
|
||||
if (PMON)
|
||||
PMON->wildcard = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static Hyprlang::CParseResult handlePreload(const char* C, const char* V) {
|
||||
const std::string COMMAND = C;
|
||||
const std::string VALUE = V;
|
||||
auto WALLPAPER = VALUE;
|
||||
|
||||
if (WALLPAPER[0] == '~') {
|
||||
static const char* const ENVHOME = getenv("HOME");
|
||||
WALLPAPER = std::string(ENVHOME) + WALLPAPER.substr(1);
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
|
||||
if (!std::filesystem::exists(WALLPAPER, ec)) {
|
||||
Hyprlang::CParseResult result;
|
||||
result.setError(((ec ? ec.message() : std::string{"no such file"}) + std::string{": "} + WALLPAPER).c_str());
|
||||
return result;
|
||||
}
|
||||
|
||||
g_pConfigManager->m_dRequestedPreloads.emplace_back(WALLPAPER);
|
||||
|
||||
return Hyprlang::CParseResult{};
|
||||
}
|
||||
|
||||
static Hyprlang::CParseResult handleUnloadAll(const char* C, const char* V) {
|
||||
const std::string COMMAND = C;
|
||||
const std::string VALUE = V;
|
||||
std::vector<std::string> toUnload;
|
||||
|
||||
for (auto& [name, target] : g_pHyprpaper->m_mWallpaperTargets) {
|
||||
if (VALUE == "unused") {
|
||||
bool exists = false;
|
||||
for (auto& [mon, target2] : g_pHyprpaper->m_mMonitorActiveWallpaperTargets) {
|
||||
if (&target == target2) {
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (exists)
|
||||
continue;
|
||||
}
|
||||
|
||||
toUnload.emplace_back(name);
|
||||
}
|
||||
|
||||
for (auto& tu : toUnload)
|
||||
g_pHyprpaper->unloadWallpaper(tu);
|
||||
|
||||
return Hyprlang::CParseResult{};
|
||||
}
|
||||
|
||||
static Hyprlang::CParseResult handleUnload(const char* C, const char* V) {
|
||||
const std::string COMMAND = C;
|
||||
const std::string VALUE = V;
|
||||
auto WALLPAPER = VALUE;
|
||||
|
||||
if (VALUE == "all" || VALUE == "unused")
|
||||
return handleUnloadAll(C, V);
|
||||
|
||||
if (WALLPAPER[0] == '~') {
|
||||
static const char* const ENVHOME = getenv("HOME");
|
||||
WALLPAPER = std::string(ENVHOME) + WALLPAPER.substr(1);
|
||||
}
|
||||
|
||||
g_pHyprpaper->unloadWallpaper(WALLPAPER);
|
||||
|
||||
return Hyprlang::CParseResult{};
|
||||
}
|
||||
|
||||
static Hyprlang::CParseResult handleReload(const char* C, const char* V) {
|
||||
const std::string COMMAND = C;
|
||||
const std::string VALUE = V;
|
||||
|
||||
auto WALLPAPER = g_pConfigManager->trimPath(VALUE.substr(VALUE.find_first_of(',') + 1));
|
||||
|
||||
if (WALLPAPER.find("contain:") == 0) {
|
||||
WALLPAPER = WALLPAPER.substr(8);
|
||||
}
|
||||
|
||||
if (WALLPAPER.find("tile:") == 0)
|
||||
WALLPAPER = WALLPAPER.substr(5);
|
||||
|
||||
auto preloadResult = handlePreload(C, WALLPAPER.c_str());
|
||||
if (preloadResult.error)
|
||||
return preloadResult;
|
||||
|
||||
auto MONITOR = VALUE.substr(0, VALUE.find_first_of(','));
|
||||
|
||||
if (MONITOR.empty()) {
|
||||
for (auto& m : g_pHyprpaper->m_vMonitors) {
|
||||
auto OLD_WALLPAPER = g_pHyprpaper->m_mMonitorActiveWallpapers[m->name];
|
||||
g_pHyprpaper->unloadWallpaper(OLD_WALLPAPER);
|
||||
}
|
||||
} else {
|
||||
auto OLD_WALLPAPER = g_pHyprpaper->m_mMonitorActiveWallpapers[MONITOR];
|
||||
g_pHyprpaper->unloadWallpaper(OLD_WALLPAPER);
|
||||
}
|
||||
|
||||
auto wallpaperResult = handleWallpaper(C, V);
|
||||
if (wallpaperResult.error)
|
||||
return wallpaperResult;
|
||||
|
||||
return Hyprlang::CParseResult{};
|
||||
}
|
||||
|
||||
static Hyprlang::CParseResult handleSource(const char* C, const char* V) {
|
||||
Hyprlang::CParseResult result;
|
||||
|
||||
const std::string path = g_pConfigManager->absolutePath(V);
|
||||
|
||||
std::error_code ec;
|
||||
if (!std::filesystem::exists(path, ec)) {
|
||||
result.setError((ec ? ec.message() : "no such file").c_str());
|
||||
return result;
|
||||
}
|
||||
|
||||
g_pConfigManager->config->parseFile(path.c_str());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
CConfigManager::CConfigManager() {
|
||||
// Initialize the configuration
|
||||
// Read file from default location
|
||||
// or from an explicit location given by user
|
||||
|
||||
std::string configPath = getMainConfigPath();
|
||||
|
||||
config = std::make_unique<Hyprlang::CConfig>(configPath.c_str(), Hyprlang::SConfigOptions{.allowMissingConfig = true});
|
||||
|
||||
config->addConfigValue("ipc", Hyprlang::INT{1L});
|
||||
config->addConfigValue("splash", Hyprlang::INT{0L});
|
||||
config->addConfigValue("splash_offset", Hyprlang::FLOAT{2.F});
|
||||
config->addConfigValue("splash_color", Hyprlang::INT{0x55ffffff});
|
||||
|
||||
config->registerHandler(&handleWallpaper, "wallpaper", {.allowFlags = false});
|
||||
config->registerHandler(&handleUnload, "unload", {.allowFlags = false});
|
||||
config->registerHandler(&handlePreload, "preload", {.allowFlags = false});
|
||||
config->registerHandler(&handleUnloadAll, "unloadAll", {.allowFlags = false});
|
||||
config->registerHandler(&handleReload, "reload", {.allowFlags = false});
|
||||
config->registerHandler(&handleSource, "source", {.allowFlags = false});
|
||||
|
||||
config->commence();
|
||||
}
|
||||
|
||||
void CConfigManager::parse() {
|
||||
const auto ERROR = config->parse();
|
||||
|
||||
if (ERROR.error)
|
||||
std::cout << "Error in config: \n" << ERROR.getError() << "\n";
|
||||
}
|
||||
|
||||
std::string CConfigManager::getMainConfigPath() {
|
||||
if (!g_pHyprpaper->m_szExplicitConfigPath.empty())
|
||||
return g_pHyprpaper->m_szExplicitConfigPath;
|
||||
using namespace std::string_literals;
|
||||
|
||||
static std::string getMainConfigPath() {
|
||||
static const auto paths = Hyprutils::Path::findConfig("hyprpaper");
|
||||
if (paths.first.has_value())
|
||||
return paths.first.value();
|
||||
else
|
||||
return "";
|
||||
|
||||
return paths.first.value_or("");
|
||||
}
|
||||
|
||||
// trim from both ends
|
||||
std::string CConfigManager::trimPath(std::string path) {
|
||||
if (path.empty())
|
||||
return "";
|
||||
|
||||
// trims whitespaces, tabs and new line feeds
|
||||
size_t pathStartIndex = path.find_first_not_of(" \t\r\n");
|
||||
size_t pathEndIndex = path.find_last_not_of(" \t\r\n");
|
||||
return path.substr(pathStartIndex, pathEndIndex - pathStartIndex + 1);
|
||||
CConfigManager::CConfigManager(const std::string& configPath) :
|
||||
m_config(configPath.empty() ? getMainConfigPath().c_str() : configPath.c_str(), Hyprlang::SConfigOptions{.throwAllErrors = true, .allowMissingConfig = true}) {
|
||||
m_currentConfigPath = configPath.empty() ? getMainConfigPath() : configPath;
|
||||
}
|
||||
|
||||
std::string CConfigManager::absolutePath(const std::string& path) {
|
||||
if (path.empty())
|
||||
return "";
|
||||
void CConfigManager::init() {
|
||||
m_config.addConfigValue("splash", Hyprlang::INT{1});
|
||||
m_config.addConfigValue("splash_offset", Hyprlang::INT{20});
|
||||
m_config.addConfigValue("splash_opacity", Hyprlang::FLOAT{0.8});
|
||||
m_config.addConfigValue("ipc", Hyprlang::INT{1});
|
||||
|
||||
std::string result = path;
|
||||
m_config.addSpecialCategory("wallpaper", Hyprlang::SSpecialCategoryOptions{.key = "monitor"});
|
||||
m_config.addSpecialConfigValue("wallpaper", "monitor", Hyprlang::STRING{""});
|
||||
m_config.addSpecialConfigValue("wallpaper", "path", Hyprlang::STRING{""});
|
||||
m_config.addSpecialConfigValue("wallpaper", "fit_mode", Hyprlang::STRING{"contain"});
|
||||
|
||||
if (result[0] == '~') {
|
||||
const char* home = getenv("HOME");
|
||||
if (home)
|
||||
result = std::string(home) + result.substr(1);
|
||||
m_config.commence();
|
||||
|
||||
auto result = m_config.parse();
|
||||
|
||||
if (result.error)
|
||||
g_logger->log(LOG_ERR, "Config has errors:\n{}\nProceeding ignoring faulty entries", result.getError());
|
||||
|
||||
g_matcher->addStates(getSettings());
|
||||
}
|
||||
|
||||
Hyprlang::CConfig* CConfigManager::hyprlang() {
|
||||
return &m_config;
|
||||
}
|
||||
|
||||
static std::expected<std::string, std::string> resolvePath(const std::string_view& sv) {
|
||||
std::error_code ec;
|
||||
const auto CAN = std::filesystem::canonical(sv, ec);
|
||||
|
||||
if (ec)
|
||||
return std::unexpected(std::format("invalid path: {}", ec.message()));
|
||||
|
||||
return CAN;
|
||||
}
|
||||
|
||||
static std::expected<std::string, std::string> getFullPath(const std::string_view& sv) {
|
||||
if (sv.empty())
|
||||
return std::unexpected("empty path");
|
||||
|
||||
if (sv[0] == '~') {
|
||||
static auto HOME = getenv("HOME");
|
||||
if (!HOME || HOME[0] == '\0')
|
||||
return std::unexpected("home path but no $HOME");
|
||||
|
||||
return resolvePath(std::string{HOME} + "/"s + std::string{sv.substr(1)});
|
||||
}
|
||||
|
||||
return std::filesystem::absolute(result).string();
|
||||
return resolvePath(sv);
|
||||
}
|
||||
|
||||
std::vector<CConfigManager::SSetting> CConfigManager::getSettings() {
|
||||
std::vector<CConfigManager::SSetting> result;
|
||||
|
||||
auto keys = m_config.listKeysForSpecialCategory("wallpaper");
|
||||
result.reserve(keys.size());
|
||||
|
||||
for (auto& key : keys) {
|
||||
std::string monitor, fitMode, path;
|
||||
|
||||
try {
|
||||
monitor = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("wallpaper", "monitor", key.c_str()));
|
||||
fitMode = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("wallpaper", "fit_mode", key.c_str()));
|
||||
path = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("wallpaper", "path", key.c_str()));
|
||||
} catch (...) {
|
||||
g_logger->log(LOG_ERR, "Failed parsing wallpaper for key {}", key);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto RESOLVE_PATH = getFullPath(path);
|
||||
|
||||
if (!RESOLVE_PATH) {
|
||||
g_logger->log(LOG_ERR, "Failed to resolve path {}: {}", path, RESOLVE_PATH.error());
|
||||
continue;
|
||||
}
|
||||
|
||||
result.emplace_back(SSetting{.monitor = std::move(monitor), .fitMode = std::move(fitMode), .path = RESOLVE_PATH.value()});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,34 @@
|
|||
#pragma once
|
||||
#include "../defines.hpp"
|
||||
#include <hyprlang.hpp>
|
||||
|
||||
class CIPCSocket;
|
||||
#include "../helpers/Memory.hpp"
|
||||
#include <hyprlang.hpp>
|
||||
#include <vector>
|
||||
|
||||
class CConfigManager {
|
||||
public:
|
||||
// gets all the data from the config
|
||||
CConfigManager();
|
||||
void parse();
|
||||
CConfigManager(const std::string& configPath);
|
||||
~CConfigManager() = default;
|
||||
|
||||
std::deque<std::string> m_dRequestedPreloads;
|
||||
std::string getMainConfigPath();
|
||||
std::string trimPath(std::string path);
|
||||
std::string absolutePath(const std::string& path);
|
||||
CConfigManager(const CConfigManager&) = delete;
|
||||
CConfigManager(CConfigManager&) = delete;
|
||||
CConfigManager(CConfigManager&&) = delete;
|
||||
|
||||
std::unique_ptr<Hyprlang::CConfig> config;
|
||||
struct SSetting {
|
||||
std::string monitor, fitMode, path;
|
||||
uint32_t id = 0;
|
||||
};
|
||||
|
||||
constexpr static const uint32_t SETTING_INVALID = 0;
|
||||
|
||||
void init();
|
||||
Hyprlang::CConfig* hyprlang();
|
||||
|
||||
std::vector<SSetting> getSettings();
|
||||
|
||||
private:
|
||||
friend class CIPCSocket;
|
||||
Hyprlang::CConfig m_config;
|
||||
|
||||
std::string m_currentConfigPath;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CConfigManager> g_pConfigManager;
|
||||
inline UP<CConfigManager> g_config;
|
||||
|
|
|
|||
103
src/config/WallpaperMatcher.cpp
Normal file
103
src/config/WallpaperMatcher.cpp
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
#include "WallpaperMatcher.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
void CWallpaperMatcher::addState(CConfigManager::SSetting&& s) {
|
||||
s.id = ++m_maxId;
|
||||
|
||||
std::erase_if(m_settings, [&s](const auto& e) { return e.monitor == s.monitor; });
|
||||
m_settings.emplace_back(std::move(s));
|
||||
recalcStates();
|
||||
}
|
||||
|
||||
void CWallpaperMatcher::addStates(std::vector<CConfigManager::SSetting>&& s) {
|
||||
for (auto& ss : s) {
|
||||
ss.id = ++m_maxId;
|
||||
}
|
||||
|
||||
std::erase_if(m_settings, [&s](const auto& e) { return std::ranges::any_of(s, [&e](const auto& el) { return el.monitor == e.monitor; }); });
|
||||
m_settings.append_range(std::move(s));
|
||||
recalcStates();
|
||||
}
|
||||
|
||||
void CWallpaperMatcher::registerOutput(const std::string_view& s) {
|
||||
m_monitorNames.emplace_back(s);
|
||||
recalcStates();
|
||||
}
|
||||
|
||||
void CWallpaperMatcher::unregisterOutput(const std::string_view& s) {
|
||||
std::erase(m_monitorNames, s);
|
||||
std::erase_if(m_monitorStates, [&s](const auto& e) { return e.name == s; });
|
||||
recalcStates();
|
||||
}
|
||||
|
||||
bool CWallpaperMatcher::outputExists(const std::string_view& s) {
|
||||
return std::ranges::contains(m_monitorNames, s);
|
||||
}
|
||||
|
||||
std::optional<CWallpaperMatcher::rw<const CConfigManager::SSetting>> CWallpaperMatcher::getSetting(const std::string_view& monName) {
|
||||
for (const auto& m : m_monitorStates) {
|
||||
if (m.name != monName)
|
||||
continue;
|
||||
|
||||
for (const auto& s : m_settings) {
|
||||
if (s.id != m.currentID)
|
||||
continue;
|
||||
return s;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<CWallpaperMatcher::rw<const CConfigManager::SSetting>> CWallpaperMatcher::matchSetting(const std::string_view& monName) {
|
||||
// match explicit
|
||||
for (const auto& s : m_settings) {
|
||||
if (s.monitor != monName)
|
||||
continue;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// match wildcard
|
||||
for (const auto& s : m_settings) {
|
||||
if (s.monitor.empty())
|
||||
return s;
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
CWallpaperMatcher::SMonitorState& CWallpaperMatcher::getState(const std::string_view& monName) {
|
||||
for (auto& s : m_monitorStates) {
|
||||
if (s.name == monName)
|
||||
return s;
|
||||
}
|
||||
|
||||
return m_monitorStates.emplace_back();
|
||||
}
|
||||
|
||||
void CWallpaperMatcher::recalcStates() {
|
||||
std::vector<std::string_view> namesChanged;
|
||||
|
||||
for (const auto& name : m_monitorNames) {
|
||||
const auto STATE = matchSetting(name);
|
||||
auto& activeState = getState(name);
|
||||
|
||||
if (!STATE)
|
||||
activeState = {.name = name};
|
||||
else {
|
||||
activeState.name = name;
|
||||
if (activeState.currentID != STATE->get().id) {
|
||||
activeState.currentID = STATE->get().id;
|
||||
namesChanged.emplace_back(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& n : namesChanged) {
|
||||
m_events.monitorConfigChanged.emit(n);
|
||||
}
|
||||
}
|
||||
53
src/config/WallpaperMatcher.hpp
Normal file
53
src/config/WallpaperMatcher.hpp
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "ConfigManager.hpp"
|
||||
|
||||
#include <hyprutils/signal/Signal.hpp>
|
||||
|
||||
class CWallpaperMatcher {
|
||||
public:
|
||||
template <typename T>
|
||||
using rw = std::reference_wrapper<T>;
|
||||
|
||||
CWallpaperMatcher() = default;
|
||||
~CWallpaperMatcher() = default;
|
||||
|
||||
CWallpaperMatcher(const CWallpaperMatcher&) = delete;
|
||||
CWallpaperMatcher(CWallpaperMatcher&) = delete;
|
||||
CWallpaperMatcher(CWallpaperMatcher&&) = delete;
|
||||
|
||||
void addState(CConfigManager::SSetting&&);
|
||||
void addStates(std::vector<CConfigManager::SSetting>&&);
|
||||
|
||||
void registerOutput(const std::string_view&);
|
||||
void unregisterOutput(const std::string_view&);
|
||||
bool outputExists(const std::string_view&);
|
||||
|
||||
std::optional<rw<const CConfigManager::SSetting>> getSetting(const std::string_view& monName);
|
||||
|
||||
struct {
|
||||
Hyprutils::Signal::CSignalT<const std::string_view&> monitorConfigChanged;
|
||||
} m_events;
|
||||
|
||||
private:
|
||||
void recalcStates();
|
||||
std::optional<rw<const CConfigManager::SSetting>> matchSetting(const std::string_view& monName);
|
||||
|
||||
std::vector<CConfigManager::SSetting> m_settings;
|
||||
|
||||
struct SMonitorState {
|
||||
std::string name;
|
||||
uint32_t currentID = CConfigManager::SETTING_INVALID;
|
||||
};
|
||||
|
||||
std::vector<std::string> m_monitorNames;
|
||||
std::vector<SMonitorState> m_monitorStates;
|
||||
|
||||
uint32_t m_maxId = 0;
|
||||
|
||||
SMonitorState& getState(const std::string_view& monName);
|
||||
};
|
||||
|
||||
inline UP<CWallpaperMatcher> g_matcher = makeUnique<CWallpaperMatcher>();
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
#pragma once
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
enum eLogLevel {
|
||||
TRACE = 0,
|
||||
INFO,
|
||||
LOG,
|
||||
WARN,
|
||||
ERR,
|
||||
CRIT,
|
||||
NONE
|
||||
};
|
||||
|
||||
#define RASSERT(expr, reason, ...) \
|
||||
if (!(expr)) { \
|
||||
Debug::log(CRIT, "\n==========================================================================================\nASSERTION FAILED! \n\n{}\n\nat: line {} in {}", \
|
||||
std::format(reason, ##__VA_ARGS__), __LINE__, \
|
||||
([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find_last_of('/') + 1); })().c_str()); \
|
||||
std::abort(); \
|
||||
}
|
||||
|
||||
#define ASSERT(expr) RASSERT(expr, "?")
|
||||
|
||||
namespace Debug {
|
||||
inline bool quiet = false;
|
||||
inline bool verbose = false;
|
||||
|
||||
template <typename... Args>
|
||||
void log(eLogLevel level, const std::string& fmt, Args&&... args) {
|
||||
|
||||
if (!verbose && level == TRACE)
|
||||
return;
|
||||
|
||||
if (quiet)
|
||||
return;
|
||||
|
||||
if (level != NONE) {
|
||||
std::cout << '[';
|
||||
|
||||
switch (level) {
|
||||
case TRACE: std::cout << "TRACE"; break;
|
||||
case INFO: std::cout << "INFO"; break;
|
||||
case LOG: std::cout << "LOG"; break;
|
||||
case WARN: std::cout << "WARN"; break;
|
||||
case ERR: std::cout << "ERR"; break;
|
||||
case CRIT: std::cout << "CRITICAL"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
std::cout << "] ";
|
||||
}
|
||||
|
||||
std::cout << std::vformat(fmt, std::make_format_args(args...)) << std::endl;
|
||||
}
|
||||
};
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include "includes.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include "helpers/Logger.hpp"
|
||||
|
||||
// git stuff
|
||||
#ifndef GIT_COMMIT_HASH
|
||||
|
|
@ -17,10 +18,9 @@
|
|||
#define GIT_DIRTY "?"
|
||||
#endif
|
||||
|
||||
#include <hyprutils/math/Vector2D.hpp>
|
||||
using namespace Hyprutils::Math;
|
||||
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
using namespace Hyprutils::Memory;
|
||||
#define SP Hyprutils::Memory::CSharedPointer
|
||||
#define WP Hyprutils::Memory::CWeakPointer
|
||||
#define ASSERT(expr) \
|
||||
if (!(expr)) { \
|
||||
g_logger->log(LOG_CRIT, "Failed assertion at line {} in {}: {} was false", __LINE__, \
|
||||
([]() constexpr -> std::string { return std::string(__FILE__).substr(std::string(__FILE__).find("/src/") + 1); })(), #expr); \
|
||||
std::abort(); \
|
||||
}
|
||||
|
|
|
|||
12
src/helpers/Logger.hpp
Normal file
12
src/helpers/Logger.hpp
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <hyprutils/cli/Logger.hpp>
|
||||
#include "Memory.hpp"
|
||||
|
||||
#define LOG_DEBUG Hyprutils::CLI::LOG_DEBUG
|
||||
#define LOG_ERR Hyprutils::CLI::LOG_ERR
|
||||
#define LOG_WARN Hyprutils::CLI::LOG_WARN
|
||||
#define LOG_TRACE Hyprutils::CLI::LOG_TRACE
|
||||
#define LOG_CRIT Hyprutils::CLI::LOG_CRIT
|
||||
|
||||
inline UP<Hyprutils::CLI::CLogger> g_logger = makeUnique<Hyprutils::CLI::CLogger>();
|
||||
12
src/helpers/Memory.hpp
Normal file
12
src/helpers/Memory.hpp
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <hyprutils/memory/SharedPtr.hpp>
|
||||
#include <hyprutils/memory/UniquePtr.hpp>
|
||||
#include <hyprutils/memory/Atomic.hpp>
|
||||
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
#define SP CSharedPointer
|
||||
#define WP CWeakPointer
|
||||
#define UP CUniquePointer
|
||||
#define ASP CAtomicSharedPointer
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
#include "MiscFunctions.hpp"
|
||||
#include <array>
|
||||
#include "../debug/Log.hpp"
|
||||
#include <memory>
|
||||
|
||||
#include <hyprutils/os/Process.hpp>
|
||||
using namespace Hyprutils::OS;
|
||||
|
||||
bool vectorDeltaLessThan(const Vector2D& a, const Vector2D& b, const float& delta) {
|
||||
return std::abs(a.x - b.x) < delta && std::abs(a.y - b.y) < delta;
|
||||
}
|
||||
|
||||
bool vectorDeltaLessThan(const Vector2D& a, const Vector2D& b, const Vector2D& delta) {
|
||||
return std::abs(a.x - b.x) < delta.x && std::abs(a.y - b.y) < delta.y;
|
||||
}
|
||||
|
||||
std::string execAndGet(const char* cmd) {
|
||||
CProcess proc("/bin/bash", {"-c", cmd});
|
||||
if (!proc.runSync())
|
||||
return "";
|
||||
return proc.stdOut();
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include "../defines.hpp"
|
||||
|
||||
bool vectorDeltaLessThan(const Vector2D& a, const Vector2D& b, const float& delta);
|
||||
bool vectorDeltaLessThan(const Vector2D& a, const Vector2D& b, const Vector2D& delta);
|
||||
std::string execAndGet(const char*);
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
#include "Monitor.hpp"
|
||||
#include "../Hyprpaper.hpp"
|
||||
#include "MiscFunctions.hpp"
|
||||
|
||||
void SMonitor::registerListeners() {
|
||||
output->setMode([this](CCWlOutput* r, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
|
||||
size = Vector2D(width, height);
|
||||
|
||||
//ensures any transforms are also taken care of when setting the mode
|
||||
if (transform & 1)
|
||||
std::swap(size.x, size.y);
|
||||
});
|
||||
|
||||
output->setDone([this](CCWlOutput* r) {
|
||||
readyForLS = true;
|
||||
std::lock_guard<std::mutex> lg(g_pHyprpaper->m_mtTickMutex);
|
||||
if (g_pConfigManager) // don't tick if this is the first roundtrip
|
||||
g_pHyprpaper->tick(true);
|
||||
});
|
||||
|
||||
output->setScale([this](CCWlOutput* r, int32_t scale_) { scale = scale_; });
|
||||
|
||||
output->setName([this](CCWlOutput* r, const char* name_) { name = name_; });
|
||||
|
||||
output->setDescription([this](CCWlOutput* r, const char* desc_) {
|
||||
std::string desc = desc_;
|
||||
std::erase(desc, ',');
|
||||
|
||||
description = desc;
|
||||
});
|
||||
|
||||
output->setGeometry([this](CCWlOutput* r, int32_t x, int32_t y, int32_t width_mm, int32_t height_mm, int32_t subpixel, const char* make, const char* model,
|
||||
int32_t transform_) { //
|
||||
/*
|
||||
see https://wayland.freedesktop.org/docs/html/apa.html#protocol-spec-wl_output-enum-transform
|
||||
If there is a difference in parity of the old vs new transforms, the size needs to be swapped.
|
||||
*/
|
||||
if ((transform ^ transform_) & 1)
|
||||
std::swap(size.x, size.y);
|
||||
|
||||
transform = (wl_output_transform)transform_;
|
||||
});
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "../defines.hpp"
|
||||
#include "../render/LayerSurface.hpp"
|
||||
#include "PoolBuffer.hpp"
|
||||
#include "protocols/wayland.hpp"
|
||||
|
||||
struct SMonitor {
|
||||
std::string name = "";
|
||||
std::string description = "";
|
||||
SP<CCWlOutput> output;
|
||||
uint32_t wayland_name = 0;
|
||||
Vector2D size;
|
||||
int scale;
|
||||
wl_output_transform transform = WL_OUTPUT_TRANSFORM_NORMAL;
|
||||
|
||||
bool readyForLS = false;
|
||||
bool hasATarget = true;
|
||||
|
||||
bool wildcard = true;
|
||||
|
||||
uint32_t configureSerial = 0;
|
||||
SPoolBuffer buffer;
|
||||
|
||||
bool wantsReload = false;
|
||||
bool wantsACK = false;
|
||||
bool initialized = false;
|
||||
|
||||
std::vector<std::unique_ptr<CLayerSurface>> layerSurfaces;
|
||||
CLayerSurface* pCurrentLayerSurface = nullptr;
|
||||
|
||||
void registerListeners();
|
||||
};
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "../defines.hpp"
|
||||
#include "protocols/wayland.hpp"
|
||||
|
||||
class CWallpaperTarget;
|
||||
|
||||
struct SPoolBuffer {
|
||||
SP<CCWlBuffer> buffer = nullptr;
|
||||
cairo_surface_t* surface = nullptr;
|
||||
cairo_t* cairo = nullptr;
|
||||
void* data = nullptr;
|
||||
size_t size = 0;
|
||||
std::string name = "";
|
||||
|
||||
std::string target = "";
|
||||
Vector2D pixelSize;
|
||||
};
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <cmath>
|
||||
|
||||
#include <GLES3/gl32.h>
|
||||
#include <GLES3/gl3ext.h>
|
||||
#include <cassert>
|
||||
#include <cairo.h>
|
||||
#include <cairo/cairo.h>
|
||||
#include <fcntl.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#include <wayland-client.h>
|
||||
86
src/ipc/HyprlandSocket.cpp
Normal file
86
src/ipc/HyprlandSocket.cpp
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
#include "HyprlandSocket.hpp"
|
||||
|
||||
#include <pwd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <format>
|
||||
|
||||
#include <hyprutils/memory/Casts.hpp>
|
||||
|
||||
using namespace Hyprutils::Memory;
|
||||
|
||||
static int getUID() {
|
||||
const auto UID = getuid();
|
||||
const auto PWUID = getpwuid(UID);
|
||||
return PWUID ? PWUID->pw_uid : UID;
|
||||
}
|
||||
|
||||
static std::string getRuntimeDir() {
|
||||
const auto XDG = getenv("XDG_RUNTIME_DIR");
|
||||
|
||||
if (!XDG) {
|
||||
const std::string USERID = std::to_string(getUID());
|
||||
return "/run/user/" + USERID + "/hypr";
|
||||
}
|
||||
|
||||
return std::string{XDG} + "/hypr";
|
||||
}
|
||||
|
||||
std::expected<std::string, std::string> HyprlandSocket::getFromSocket(const std::string& cmd) {
|
||||
static const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
|
||||
if (!HIS || HIS[0] == '\0')
|
||||
return std::unexpected("HYPRLAND_INSTANCE_SIGNATURE empty: are we under hyprland?");
|
||||
|
||||
const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
|
||||
auto t = timeval{.tv_sec = 5, .tv_usec = 0};
|
||||
setsockopt(SERVERSOCKET, SOL_SOCKET, SO_RCVTIMEO, &t, sizeof(struct timeval));
|
||||
|
||||
if (SERVERSOCKET < 0)
|
||||
return std::unexpected("couldn't open a socket (1)");
|
||||
|
||||
sockaddr_un serverAddress = {0};
|
||||
serverAddress.sun_family = AF_UNIX;
|
||||
|
||||
std::string socketPath = getRuntimeDir() + "/" + HIS + "/.socket.sock";
|
||||
|
||||
strncpy(serverAddress.sun_path, socketPath.c_str(), sizeof(serverAddress.sun_path) - 1);
|
||||
|
||||
if (connect(SERVERSOCKET, rc<sockaddr*>(&serverAddress), SUN_LEN(&serverAddress)) < 0)
|
||||
return std::unexpected(std::format("couldn't connect to the hyprland socket at {}", socketPath));
|
||||
|
||||
auto sizeWritten = write(SERVERSOCKET, cmd.c_str(), cmd.length());
|
||||
|
||||
if (sizeWritten < 0)
|
||||
return std::unexpected("couldn't write (4)");
|
||||
|
||||
std::string reply = "";
|
||||
char buffer[8192] = {0};
|
||||
|
||||
sizeWritten = read(SERVERSOCKET, buffer, 8192);
|
||||
|
||||
if (sizeWritten < 0) {
|
||||
if (errno == EWOULDBLOCK)
|
||||
return std::unexpected("Hyprland IPC didn't respond in time");
|
||||
return std::unexpected("couldn't read (5)");
|
||||
}
|
||||
|
||||
reply += std::string(buffer, sizeWritten);
|
||||
|
||||
while (sizeWritten == 8192) {
|
||||
sizeWritten = read(SERVERSOCKET, buffer, 8192);
|
||||
if (sizeWritten < 0) {
|
||||
return std::unexpected("couldn't read (5)");
|
||||
}
|
||||
reply += std::string(buffer, sizeWritten);
|
||||
}
|
||||
|
||||
close(SERVERSOCKET);
|
||||
|
||||
return reply;
|
||||
}
|
||||
9
src/ipc/HyprlandSocket.hpp
Normal file
9
src/ipc/HyprlandSocket.hpp
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include <string>
|
||||
#include <expected>
|
||||
|
||||
namespace HyprlandSocket {
|
||||
std::expected<std::string, std::string> getFromSocket(const std::string& cmd);
|
||||
};
|
||||
147
src/ipc/IPC.cpp
Normal file
147
src/ipc/IPC.cpp
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
#include "IPC.hpp"
|
||||
#include "../helpers/Logger.hpp"
|
||||
#include "../config/WallpaperMatcher.hpp"
|
||||
#include "../ui/UI.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
using namespace IPC;
|
||||
using namespace std::string_literals;
|
||||
|
||||
constexpr const char* SOCKET_NAME = ".hyprpaper.sock";
|
||||
|
||||
static SP<CHyprpaperCoreImpl> g_coreImpl;
|
||||
|
||||
CWallpaperObject::CWallpaperObject(SP<CHyprpaperWallpaperObject>&& obj) : m_object(std::move(obj)) {
|
||||
m_object->setDestroy([this]() { std::erase_if(g_IPCSocket->m_wallpaperObjects, [this](const auto& e) { return e.get() == this; }); });
|
||||
m_object->setOnDestroy([this]() { std::erase_if(g_IPCSocket->m_wallpaperObjects, [this](const auto& e) { return e.get() == this; }); });
|
||||
|
||||
m_object->setPath([this](const char* s) {
|
||||
if (m_inert)
|
||||
m_object->error(HYPRPAPER_CORE_WALLPAPER_ERRORS_INERT_WALLPAPER_OBJECT, "Object is inert");
|
||||
|
||||
m_path = s;
|
||||
});
|
||||
|
||||
m_object->setFitMode([this](hyprpaperCoreWallpaperFitMode f) {
|
||||
if (m_inert)
|
||||
m_object->error(HYPRPAPER_CORE_WALLPAPER_ERRORS_INERT_WALLPAPER_OBJECT, "Object is inert");
|
||||
|
||||
if (f > HYPRPAPER_CORE_WALLPAPER_FIT_MODE_TILE)
|
||||
m_object->error(HYPRPAPER_CORE_APPLYING_ERROR_UNKNOWN_ERROR, "Invalid fit mode");
|
||||
m_fitMode = f;
|
||||
});
|
||||
|
||||
m_object->setMonitorName([this](const char* s) {
|
||||
if (m_inert)
|
||||
m_object->error(HYPRPAPER_CORE_WALLPAPER_ERRORS_INERT_WALLPAPER_OBJECT, "Object is inert");
|
||||
|
||||
m_monitor = s;
|
||||
});
|
||||
|
||||
m_object->setApply([this]() {
|
||||
if (m_inert)
|
||||
m_object->error(HYPRPAPER_CORE_WALLPAPER_ERRORS_INERT_WALLPAPER_OBJECT, "Object is inert");
|
||||
|
||||
apply();
|
||||
});
|
||||
}
|
||||
|
||||
static std::string fitModeToStr(hyprpaperCoreWallpaperFitMode m) {
|
||||
switch (m) {
|
||||
case HYPRPAPER_CORE_WALLPAPER_FIT_MODE_CONTAIN: return "contain";
|
||||
case HYPRPAPER_CORE_WALLPAPER_FIT_MODE_COVER: return "cover";
|
||||
case HYPRPAPER_CORE_WALLPAPER_FIT_MODE_TILE: return "tile";
|
||||
case HYPRPAPER_CORE_WALLPAPER_FIT_MODE_STRETCH: return "fit";
|
||||
default: return "cover";
|
||||
}
|
||||
}
|
||||
|
||||
void CWallpaperObject::apply() {
|
||||
|
||||
m_inert = true;
|
||||
|
||||
if (!m_monitor.empty() && !g_matcher->outputExists(m_monitor)) {
|
||||
m_object->sendFailed(HYPRPAPER_CORE_APPLYING_ERROR_INVALID_MONITOR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_path.empty()) {
|
||||
m_object->sendFailed(HYPRPAPER_CORE_APPLYING_ERROR_INVALID_PATH);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_path[0] != '/') {
|
||||
m_object->sendFailed(HYPRPAPER_CORE_APPLYING_ERROR_INVALID_PATH);
|
||||
return;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
if (!std::filesystem::exists(m_path, ec) || ec) {
|
||||
m_object->sendFailed(HYPRPAPER_CORE_APPLYING_ERROR_INVALID_PATH);
|
||||
return;
|
||||
}
|
||||
|
||||
g_matcher->addState(CConfigManager::SSetting{
|
||||
.monitor = std::move(m_monitor),
|
||||
.fitMode = fitModeToStr(m_fitMode),
|
||||
.path = std::move(m_path),
|
||||
});
|
||||
|
||||
m_object->sendSuccess();
|
||||
}
|
||||
|
||||
CSocket::CSocket() {
|
||||
const auto RTDIR = getenv("XDG_RUNTIME_DIR");
|
||||
|
||||
if (!RTDIR)
|
||||
return;
|
||||
|
||||
const auto HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
|
||||
if (!HIS) {
|
||||
g_logger->log(LOG_WARN, "not running under hyprland, IPC will be disabled.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_socketPath = RTDIR + "/hypr/"s + HIS + "/"s + SOCKET_NAME;
|
||||
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(m_socketPath, ec);
|
||||
|
||||
m_socket = Hyprwire::IServerSocket::open(m_socketPath);
|
||||
|
||||
if (!m_socket)
|
||||
return;
|
||||
|
||||
g_coreImpl = makeShared<CHyprpaperCoreImpl>(1, [this](SP<Hyprwire::IObject> obj) {
|
||||
auto manager = m_managers.emplace_back(makeShared<CHyprpaperCoreManagerObject>(std::move(obj)));
|
||||
|
||||
manager->setDestroy([this, weak = WP<CHyprpaperCoreManagerObject>{manager}] { std::erase(m_managers, weak); });
|
||||
manager->setOnDestroy([this, weak = WP<CHyprpaperCoreManagerObject>{manager}] { std::erase(m_managers, weak); });
|
||||
|
||||
manager->setGetWallpaperObject([this, weak = WP<CHyprpaperCoreManagerObject>{manager}](uint32_t id) {
|
||||
if (!weak)
|
||||
return;
|
||||
|
||||
m_wallpaperObjects.emplace_back(makeShared<CWallpaperObject>(
|
||||
makeShared<CHyprpaperWallpaperObject>(m_socket->createObject(weak->getObject()->client(), weak->getObject(), "hyprpaper_wallpaper", id))));
|
||||
});
|
||||
});
|
||||
|
||||
m_socket->addImplementation(g_coreImpl);
|
||||
|
||||
g_ui->backend()->addFd(m_socket->extractLoopFD(), [this]() { m_socket->dispatchEvents(); });
|
||||
}
|
||||
|
||||
void CSocket::onNewDisplay(const std::string& sv) {
|
||||
for (const auto& m : m_managers) {
|
||||
m->sendAddMonitor(sv.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void CSocket::onRemovedDisplay(const std::string& sv) {
|
||||
for (const auto& m : m_managers) {
|
||||
m->sendRemoveMonitor(sv.c_str());
|
||||
}
|
||||
}
|
||||
46
src/ipc/IPC.hpp
Normal file
46
src/ipc/IPC.hpp
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#pragma once
|
||||
|
||||
#include "../helpers/Memory.hpp"
|
||||
|
||||
#include <hyprwire/hyprwire.hpp>
|
||||
#include <hyprpaper_core-server.hpp>
|
||||
|
||||
namespace IPC {
|
||||
class CWallpaperObject {
|
||||
public:
|
||||
CWallpaperObject(SP<CHyprpaperWallpaperObject>&& obj);
|
||||
~CWallpaperObject() = default;
|
||||
|
||||
private:
|
||||
void apply();
|
||||
|
||||
SP<CHyprpaperWallpaperObject> m_object;
|
||||
|
||||
std::string m_path;
|
||||
hyprpaperCoreWallpaperFitMode m_fitMode = HYPRPAPER_CORE_WALLPAPER_FIT_MODE_COVER;
|
||||
std::string m_monitor;
|
||||
|
||||
bool m_inert = false;
|
||||
};
|
||||
|
||||
class CSocket {
|
||||
public:
|
||||
CSocket();
|
||||
~CSocket() = default;
|
||||
|
||||
void onNewDisplay(const std::string& sv);
|
||||
void onRemovedDisplay(const std::string& sv);
|
||||
|
||||
private:
|
||||
SP<Hyprwire::IServerSocket> m_socket;
|
||||
|
||||
std::string m_socketPath = "";
|
||||
|
||||
std::vector<SP<CHyprpaperCoreManagerObject>> m_managers;
|
||||
std::vector<SP<CWallpaperObject>> m_wallpaperObjects;
|
||||
|
||||
friend class CWallpaperObject;
|
||||
};
|
||||
|
||||
inline UP<CSocket> g_IPCSocket;
|
||||
};
|
||||
|
|
@ -1,169 +0,0 @@
|
|||
#include "Socket.hpp"
|
||||
#include "../Hyprpaper.hpp"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
#include <thread>
|
||||
|
||||
void CIPCSocket::initialize() {
|
||||
std::thread([&]() {
|
||||
const auto SOCKET = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
|
||||
if (SOCKET < 0) {
|
||||
Debug::log(ERR, "Couldn't start the hyprpaper Socket. (1) IPC will not work.");
|
||||
return;
|
||||
}
|
||||
|
||||
sockaddr_un SERVERADDRESS = {.sun_family = AF_UNIX};
|
||||
|
||||
const auto HISenv = getenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
const auto RUNTIMEdir = getenv("XDG_RUNTIME_DIR");
|
||||
const std::string USERID = std::to_string(getpwuid(getuid())->pw_uid);
|
||||
|
||||
const auto USERDIR = RUNTIMEdir ? RUNTIMEdir + std::string{"/hypr/"} : "/run/user/" + USERID + "/hypr/";
|
||||
|
||||
std::string socketPath = HISenv ? USERDIR + std::string(HISenv) + "/.hyprpaper.sock" : USERDIR + ".hyprpaper.sock";
|
||||
|
||||
if (!HISenv)
|
||||
mkdir(USERDIR.c_str(), S_IRWXU);
|
||||
|
||||
unlink(socketPath.c_str());
|
||||
|
||||
strcpy(SERVERADDRESS.sun_path, socketPath.c_str());
|
||||
|
||||
bind(SOCKET, (sockaddr*)&SERVERADDRESS, SUN_LEN(&SERVERADDRESS));
|
||||
|
||||
// 10 max queued.
|
||||
listen(SOCKET, 10);
|
||||
|
||||
sockaddr_in clientAddress = {};
|
||||
socklen_t clientSize = sizeof(clientAddress);
|
||||
|
||||
char readBuffer[1024] = {0};
|
||||
|
||||
Debug::log(LOG, "hyprpaper socket started at {} (fd: {})", socketPath, SOCKET);
|
||||
while (1) {
|
||||
const auto ACCEPTEDCONNECTION = accept(SOCKET, (sockaddr*)&clientAddress, &clientSize);
|
||||
if (ACCEPTEDCONNECTION < 0) {
|
||||
Debug::log(ERR, "Couldn't listen on the hyprpaper Socket. (3) IPC will not work.");
|
||||
break;
|
||||
} else {
|
||||
do {
|
||||
Debug::log(LOG, "Accepted incoming socket connection request on fd {}", ACCEPTEDCONNECTION);
|
||||
std::lock_guard<std::mutex> lg(g_pHyprpaper->m_mtTickMutex);
|
||||
|
||||
auto messageSize = read(ACCEPTEDCONNECTION, readBuffer, 1024);
|
||||
readBuffer[messageSize == 1024 ? 1023 : messageSize] = '\0';
|
||||
if (messageSize == 0)
|
||||
break;
|
||||
std::string request(readBuffer);
|
||||
|
||||
m_szRequest = request;
|
||||
m_bRequestReady = true;
|
||||
|
||||
g_pHyprpaper->tick(true);
|
||||
while (!m_bReplyReady) { // wait for Hyprpaper to finish processing the request
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
write(ACCEPTEDCONNECTION, m_szReply.c_str(), m_szReply.length());
|
||||
m_bReplyReady = false;
|
||||
m_szReply = "";
|
||||
|
||||
} while (1);
|
||||
Debug::log(LOG, "Closing Accepted Connection");
|
||||
close(ACCEPTEDCONNECTION);
|
||||
}
|
||||
}
|
||||
|
||||
close(SOCKET);
|
||||
}).detach();
|
||||
}
|
||||
|
||||
bool CIPCSocket::mainThreadParseRequest() {
|
||||
|
||||
if (!m_bRequestReady)
|
||||
return false;
|
||||
|
||||
std::string copy = m_szRequest;
|
||||
|
||||
if (copy == "")
|
||||
return false;
|
||||
|
||||
// now we can work on the copy
|
||||
|
||||
Debug::log(LOG, "Received a request: {}", copy);
|
||||
|
||||
// set default reply
|
||||
m_szReply = "ok";
|
||||
m_bReplyReady = true;
|
||||
m_bRequestReady = false;
|
||||
|
||||
// config commands
|
||||
if (copy.find("wallpaper") == 0 || copy.find("preload") == 0 || copy.find("unload") == 0 || copy.find("reload") == 0) {
|
||||
|
||||
const auto RESULT = g_pConfigManager->config->parseDynamic(copy.substr(0, copy.find_first_of(' ')).c_str(), copy.substr(copy.find_first_of(' ') + 1).c_str());
|
||||
|
||||
if (RESULT.error) {
|
||||
m_szReply = RESULT.getError();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (copy.find("listloaded") == 0) {
|
||||
|
||||
const auto numWallpapersLoaded = g_pHyprpaper->m_mWallpaperTargets.size();
|
||||
Debug::log(LOG, "numWallpapersLoaded: {}", numWallpapersLoaded);
|
||||
|
||||
if (numWallpapersLoaded == 0) {
|
||||
m_szReply = "no wallpapers loaded";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_szReply = "";
|
||||
long unsigned int i = 0;
|
||||
for (auto& [name, target] : g_pHyprpaper->m_mWallpaperTargets) {
|
||||
m_szReply += name;
|
||||
i++;
|
||||
if (i < numWallpapersLoaded)
|
||||
m_szReply += '\n'; // dont add newline on last entry
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (copy.find("listactive") == 0) {
|
||||
|
||||
const auto numWallpapersActive = g_pHyprpaper->m_mMonitorActiveWallpapers.size();
|
||||
Debug::log(LOG, "numWallpapersActive: {}", numWallpapersActive);
|
||||
|
||||
if (numWallpapersActive == 0) {
|
||||
m_szReply = "no wallpapers active";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_szReply = "";
|
||||
long unsigned int i = 0;
|
||||
for (auto& [mon, path1] : g_pHyprpaper->m_mMonitorActiveWallpapers) {
|
||||
m_szReply += mon + " = " + path1;
|
||||
i++;
|
||||
if (i < numWallpapersActive)
|
||||
m_szReply += '\n'; // dont add newline on last entry
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
m_szReply = "invalid command";
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "../defines.hpp"
|
||||
#include <mutex>
|
||||
|
||||
class CIPCSocket {
|
||||
public:
|
||||
void initialize();
|
||||
|
||||
bool mainThreadParseRequest();
|
||||
|
||||
private:
|
||||
std::mutex m_mtRequestMutex;
|
||||
std::string m_szRequest = "";
|
||||
std::string m_szReply = "";
|
||||
|
||||
bool m_bRequestReady = false;
|
||||
bool m_bReplyReady = false;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CIPCSocket> g_pIPCSocket;
|
||||
58
src/main.cpp
58
src/main.cpp
|
|
@ -1,34 +1,40 @@
|
|||
#include <iostream>
|
||||
#include "defines.hpp"
|
||||
#include "Hyprpaper.hpp"
|
||||
#include "helpers/Logger.hpp"
|
||||
#include "ui/UI.hpp"
|
||||
#include "config/ConfigManager.hpp"
|
||||
|
||||
int main(int argc, char** argv, char** envp) {
|
||||
Debug::log(LOG, "Welcome to hyprpaper!\nbuilt from commit {} ({})", GIT_COMMIT_HASH, GIT_COMMIT_MESSAGE);
|
||||
#include <hyprutils/cli/ArgumentParser.hpp>
|
||||
|
||||
// parse some args
|
||||
std::string configPath;
|
||||
bool noFractional = false;
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if ((!strcmp(argv[i], "-c") || !strcmp(argv[i], "--config")) && argc >= i + 2) {
|
||||
configPath = std::string(argv[++i]);
|
||||
Debug::log(LOG, "Using config location {}.", configPath);
|
||||
} else if (!strcmp(argv[i], "--no-fractional") || !strcmp(argv[i], "-n")) {
|
||||
noFractional = true;
|
||||
Debug::log(LOG, "Disabling fractional scaling support!");
|
||||
} else {
|
||||
std::cout << "Hyprpaper usage: hyprpaper [arg [...]].\n\nArguments:\n"
|
||||
<< "--help -h | Show this help message\n"
|
||||
<< "--config -c | Specify config file to use\n"
|
||||
<< "--no-fractional -n | Disable fractional scaling support\n";
|
||||
return 1;
|
||||
}
|
||||
using namespace Hyprutils::CLI;
|
||||
|
||||
int main(int argc, const char** argv, const char** envp) {
|
||||
|
||||
CArgumentParser parser({argv, argc});
|
||||
|
||||
ASSERT(parser.registerStringOption("config", "c", "Set a custom config path"));
|
||||
ASSERT(parser.registerBoolOption("verbose", "", "Enable more logging"));
|
||||
ASSERT(parser.registerBoolOption("help", "h", "Show the help menu"));
|
||||
|
||||
if (const auto ret = parser.parse(); !ret) {
|
||||
g_logger->log(LOG_ERR, "Failed parsing arguments: {}", ret.error());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// starts
|
||||
g_pHyprpaper = std::make_unique<CHyprpaper>();
|
||||
g_pHyprpaper->m_szExplicitConfigPath = configPath;
|
||||
g_pHyprpaper->m_bNoFractionalScale = noFractional;
|
||||
g_pHyprpaper->init();
|
||||
if (parser.getBool("help").value_or(false)) {
|
||||
std::println("{}", parser.getDescription(std::format("hyprpaper v{}", HYPRPAPER_VERSION)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (parser.getBool("verbose").value_or(false))
|
||||
g_logger->setLogLevel(LOG_TRACE);
|
||||
|
||||
g_logger->log(LOG_DEBUG, "Welcome to hyprpaper!\nbuilt from commit {} ({})", GIT_COMMIT_HASH, GIT_COMMIT_MESSAGE);
|
||||
|
||||
g_config = makeUnique<CConfigManager>(std::string{parser.getString("config").value_or("")});
|
||||
g_config->init();
|
||||
|
||||
g_ui = makeUnique<CUI>();
|
||||
g_ui->run();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,96 +0,0 @@
|
|||
#include "LayerSurface.hpp"
|
||||
|
||||
#include "../Hyprpaper.hpp"
|
||||
|
||||
CLayerSurface::CLayerSurface(SMonitor* pMonitor) {
|
||||
m_pMonitor = pMonitor;
|
||||
|
||||
pSurface = makeShared<CCWlSurface>(g_pHyprpaper->m_pCompositor->sendCreateSurface());
|
||||
|
||||
if (!pSurface) {
|
||||
Debug::log(CRIT, "The compositor did not allow hyprpaper a surface!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
const auto PINPUTREGION = makeShared<CCWlRegion>(g_pHyprpaper->m_pCompositor->sendCreateRegion());
|
||||
|
||||
if (!PINPUTREGION) {
|
||||
Debug::log(CRIT, "The compositor did not allow hyprpaper a region!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
pSurface->sendSetInputRegion(PINPUTREGION.get());
|
||||
|
||||
pLayerSurface = makeShared<CCZwlrLayerSurfaceV1>(
|
||||
g_pHyprpaper->m_pLayerShell->sendGetLayerSurface(pSurface->resource(), pMonitor->output->resource(), ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, "hyprpaper"));
|
||||
|
||||
if (!pLayerSurface) {
|
||||
Debug::log(CRIT, "The compositor did not allow hyprpaper a layersurface!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
pLayerSurface->sendSetSize(0, 0);
|
||||
pLayerSurface->sendSetAnchor((zwlrLayerSurfaceV1Anchor)(ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |
|
||||
ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT));
|
||||
pLayerSurface->sendSetExclusiveZone(-1);
|
||||
|
||||
pLayerSurface->setConfigure([this](CCZwlrLayerSurfaceV1* r, uint32_t serial, uint32_t x, uint32_t y) {
|
||||
m_pMonitor->size = Vector2D((double)x, (double)y);
|
||||
m_pMonitor->wantsReload = true;
|
||||
m_pMonitor->configureSerial = serial;
|
||||
m_pMonitor->wantsACK = true;
|
||||
m_pMonitor->initialized = true;
|
||||
|
||||
Debug::log(LOG, "configure for {}", m_pMonitor->name);
|
||||
});
|
||||
|
||||
pLayerSurface->setClosed([this](CCZwlrLayerSurfaceV1* r) {
|
||||
for (auto& m : g_pHyprpaper->m_vMonitors) {
|
||||
std::erase_if(m->layerSurfaces, [&](const auto& other) { return other.get() == this; });
|
||||
if (m->pCurrentLayerSurface == this) {
|
||||
if (m->layerSurfaces.empty()) {
|
||||
m->pCurrentLayerSurface = nullptr;
|
||||
} else {
|
||||
m->pCurrentLayerSurface = m->layerSurfaces.begin()->get();
|
||||
g_pHyprpaper->recheckMonitor(m.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
pSurface->sendCommit();
|
||||
|
||||
// fractional scale, if supported by the compositor
|
||||
if (g_pHyprpaper->m_pFractionalScale && g_pHyprpaper->m_pViewporter) {
|
||||
pFractionalScaleInfo = makeShared<CCWpFractionalScaleV1>(g_pHyprpaper->m_pFractionalScale->sendGetFractionalScale(pSurface->resource()));
|
||||
pFractionalScaleInfo->setPreferredScale([this](CCWpFractionalScaleV1* r, uint32_t sc120) {
|
||||
const double SCALE = sc120 / 120.0;
|
||||
|
||||
Debug::log(LOG, "handlePreferredScale: {:.2f} for {:x}", SCALE, (uintptr_t)this);
|
||||
|
||||
if (fScale != SCALE) {
|
||||
fScale = SCALE;
|
||||
std::lock_guard<std::mutex> lg(g_pHyprpaper->m_mtTickMutex);
|
||||
m_pMonitor->wantsReload = true;
|
||||
g_pHyprpaper->tick(true);
|
||||
}
|
||||
});
|
||||
|
||||
pViewport = makeShared<CCWpViewport>(g_pHyprpaper->m_pViewporter->sendGetViewport(pSurface->resource()));
|
||||
|
||||
pSurface->sendCommit();
|
||||
} else
|
||||
Debug::log(ERR, "No fractional-scale-v1 / wp-viewporter support from the compositor! fractional scaling will not work.");
|
||||
|
||||
wl_display_flush(g_pHyprpaper->m_sDisplay);
|
||||
}
|
||||
|
||||
CLayerSurface::~CLayerSurface() {
|
||||
// hyprwayland-scanner will send the destructors automatically. Neat.
|
||||
pLayerSurface.reset();
|
||||
pFractionalScaleInfo.reset();
|
||||
pViewport.reset();
|
||||
pSurface.reset();
|
||||
|
||||
wl_display_flush(g_pHyprpaper->m_sDisplay);
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "../defines.hpp"
|
||||
#include "protocols/fractional-scale-v1.hpp"
|
||||
#include "protocols/viewporter.hpp"
|
||||
#include "protocols/wayland.hpp"
|
||||
#include "protocols/wlr-layer-shell-unstable-v1.hpp"
|
||||
|
||||
struct SMonitor;
|
||||
|
||||
class CLayerSurface {
|
||||
public:
|
||||
explicit CLayerSurface(SMonitor*);
|
||||
~CLayerSurface();
|
||||
|
||||
SMonitor* m_pMonitor = nullptr;
|
||||
|
||||
SP<CCZwlrLayerSurfaceV1> pLayerSurface = nullptr;
|
||||
SP<CCWlSurface> pSurface = nullptr;
|
||||
SP<CCWpFractionalScaleV1> pFractionalScaleInfo = nullptr;
|
||||
SP<CCWpViewport> pViewport = nullptr;
|
||||
double fScale = 1.0;
|
||||
};
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
#include "WallpaperTarget.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <hyprgraphics/image/Image.hpp>
|
||||
using namespace Hyprgraphics;
|
||||
|
||||
CWallpaperTarget::~CWallpaperTarget() {
|
||||
;
|
||||
}
|
||||
|
||||
void CWallpaperTarget::create(const std::string& path) {
|
||||
m_szPath = path;
|
||||
|
||||
const auto BEGINLOAD = std::chrono::system_clock::now();
|
||||
|
||||
auto loadedImage = CImage(path);
|
||||
if (!loadedImage.success()) {
|
||||
Debug::log(CRIT, "Cannot load image {}: {}", path, loadedImage.getError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
m_vSize = loadedImage.cairoSurface()->size();
|
||||
|
||||
const auto MS = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - BEGINLOAD).count() / 1000.f;
|
||||
|
||||
Debug::log(LOG, "Preloaded target {} in {:.2f}ms -> Pixel size: [{}, {}]", path, MS, (int)m_vSize.x, (int)m_vSize.y);
|
||||
|
||||
m_pCairoSurface = loadedImage.cairoSurface();
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "../defines.hpp"
|
||||
#include <hyprgraphics/cairo/CairoSurface.hpp>
|
||||
|
||||
class CWallpaperTarget {
|
||||
public:
|
||||
~CWallpaperTarget();
|
||||
|
||||
void create(const std::string& path);
|
||||
|
||||
std::string m_szPath;
|
||||
|
||||
Vector2D m_vSize;
|
||||
|
||||
bool m_bHasAlpha = true;
|
||||
|
||||
SP<Hyprgraphics::CCairoSurface> m_pCairoSurface;
|
||||
};
|
||||
161
src/ui/UI.cpp
Normal file
161
src/ui/UI.cpp
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
#include "UI.hpp"
|
||||
#include "../helpers/Logger.hpp"
|
||||
#include "../ipc/HyprlandSocket.hpp"
|
||||
#include "../ipc/IPC.hpp"
|
||||
#include "../config/WallpaperMatcher.hpp"
|
||||
|
||||
#include <hyprtoolkit/core/Output.hpp>
|
||||
|
||||
CUI::CUI() = default;
|
||||
|
||||
CUI::~CUI() {
|
||||
m_targets.clear();
|
||||
}
|
||||
|
||||
CWallpaperTarget::CWallpaperTarget(SP<Hyprtoolkit::IOutput> output, const std::string_view& path, Hyprtoolkit::eImageFitMode fitMode) : m_monitorName(output->port()) {
|
||||
static const auto SPLASH_REPLY = HyprlandSocket::getFromSocket("/splash");
|
||||
|
||||
static const auto PENABLESPLASH = Hyprlang::CSimpleConfigValue<Hyprlang::INT>(g_config->hyprlang(), "splash");
|
||||
static const auto PSPLASHOFFSET = Hyprlang::CSimpleConfigValue<Hyprlang::INT>(g_config->hyprlang(), "splash_offset");
|
||||
static const auto PSPLASHALPHA = Hyprlang::CSimpleConfigValue<Hyprlang::FLOAT>(g_config->hyprlang(), "splash_opacity");
|
||||
|
||||
m_window = Hyprtoolkit::CWindowBuilder::begin()
|
||||
->type(Hyprtoolkit::HT_WINDOW_LAYER)
|
||||
->prefferedOutput(output)
|
||||
->anchor(0xF)
|
||||
->layer(0)
|
||||
->preferredSize({0, 0})
|
||||
->exclusiveZone(-1)
|
||||
->commence();
|
||||
|
||||
m_bg = Hyprtoolkit::CRectangleBuilder::begin()
|
||||
->size({Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, {1, 1}})
|
||||
->color([] { return Hyprtoolkit::CHyprColor{0xFF000000}; })
|
||||
->commence();
|
||||
m_null = Hyprtoolkit::CNullBuilder::begin()->size({Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, {1, 1}})->commence();
|
||||
m_image = Hyprtoolkit::CImageBuilder::begin()
|
||||
->path(std::string{path})
|
||||
->size({Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, Hyprtoolkit::CDynamicSize::HT_SIZE_PERCENT, {1.F, 1.F}})
|
||||
->sync(true)
|
||||
->fitMode(fitMode)
|
||||
->commence();
|
||||
|
||||
m_image->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE);
|
||||
m_image->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_CENTER, true);
|
||||
|
||||
m_window->m_rootElement->addChild(m_bg);
|
||||
m_window->m_rootElement->addChild(m_null);
|
||||
m_null->addChild(m_image);
|
||||
|
||||
if (!SPLASH_REPLY)
|
||||
g_logger->log(LOG_ERR, "Can't get splash: {}", SPLASH_REPLY.error());
|
||||
|
||||
if (SPLASH_REPLY && *PENABLESPLASH) {
|
||||
m_splash = Hyprtoolkit::CTextBuilder::begin()
|
||||
->text(std::string{SPLASH_REPLY.value()})
|
||||
->fontSize({Hyprtoolkit::CFontSize::HT_FONT_TEXT, 1.15F})
|
||||
->color([] { return g_ui->backend()->getPalette()->m_colors.text; })
|
||||
->a(*PSPLASHALPHA)
|
||||
->commence();
|
||||
m_splash->setPositionMode(Hyprtoolkit::IElement::HT_POSITION_ABSOLUTE);
|
||||
m_splash->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_HCENTER, true);
|
||||
m_splash->setPositionFlag(Hyprtoolkit::IElement::HT_POSITION_FLAG_BOTTOM, true);
|
||||
m_splash->setAbsolutePosition({0.F, sc<float>(-*PSPLASHOFFSET)});
|
||||
m_null->addChild(m_splash);
|
||||
}
|
||||
|
||||
m_window->open();
|
||||
}
|
||||
|
||||
void CUI::registerOutput(const SP<Hyprtoolkit::IOutput>& mon) {
|
||||
g_matcher->registerOutput(mon->port());
|
||||
if (IPC::g_IPCSocket)
|
||||
IPC::g_IPCSocket->onNewDisplay(mon->port());
|
||||
mon->m_events.removed.listenStatic([this, m = WP<Hyprtoolkit::IOutput>{mon}] {
|
||||
g_matcher->unregisterOutput(m->port());
|
||||
if (IPC::g_IPCSocket)
|
||||
IPC::g_IPCSocket->onRemovedDisplay(m->port());
|
||||
std::erase_if(m_targets, [&m](const auto& e) { return e->m_monitorName == m->port(); });
|
||||
});
|
||||
}
|
||||
|
||||
bool CUI::run() {
|
||||
static const auto PENABLEIPC = Hyprlang::CSimpleConfigValue<Hyprlang::INT>(g_config->hyprlang(), "ipc");
|
||||
|
||||
m_backend = Hyprtoolkit::IBackend::create();
|
||||
|
||||
if (!m_backend)
|
||||
return false;
|
||||
|
||||
if (*PENABLEIPC)
|
||||
IPC::g_IPCSocket = makeUnique<IPC::CSocket>();
|
||||
|
||||
const auto MONITORS = m_backend->getOutputs();
|
||||
|
||||
for (const auto& m : MONITORS) {
|
||||
registerOutput(m);
|
||||
}
|
||||
|
||||
m_listeners.newMon = m_backend->m_events.outputAdded.listen([this](SP<Hyprtoolkit::IOutput> mon) { registerOutput(mon); });
|
||||
|
||||
g_logger->log(LOG_DEBUG, "Found {} output(s)", MONITORS.size());
|
||||
|
||||
// load the config now, then bind
|
||||
for (const auto& m : MONITORS) {
|
||||
targetChanged(m);
|
||||
}
|
||||
|
||||
m_listeners.targetChanged = g_matcher->m_events.monitorConfigChanged.listen([this](const std::string_view& m) { targetChanged(m); });
|
||||
|
||||
m_backend->enterLoop();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SP<Hyprtoolkit::IBackend> CUI::backend() {
|
||||
return m_backend;
|
||||
}
|
||||
|
||||
static Hyprtoolkit::eImageFitMode toFitMode(const std::string_view& sv) {
|
||||
if (sv.starts_with("contain"))
|
||||
return Hyprtoolkit::IMAGE_FIT_MODE_CONTAIN;
|
||||
if (sv.starts_with("cover"))
|
||||
return Hyprtoolkit::IMAGE_FIT_MODE_COVER;
|
||||
if (sv.starts_with("tile"))
|
||||
return Hyprtoolkit::IMAGE_FIT_MODE_TILE;
|
||||
if (sv.starts_with("fill"))
|
||||
return Hyprtoolkit::IMAGE_FIT_MODE_STRETCH;
|
||||
return Hyprtoolkit::IMAGE_FIT_MODE_COVER;
|
||||
}
|
||||
|
||||
void CUI::targetChanged(const std::string_view& monName) {
|
||||
const auto MONITORS = m_backend->getOutputs();
|
||||
SP<Hyprtoolkit::IOutput> monitor;
|
||||
|
||||
for (const auto& m : MONITORS) {
|
||||
if (m->port() != monName)
|
||||
continue;
|
||||
|
||||
monitor = m;
|
||||
}
|
||||
|
||||
if (!monitor) {
|
||||
g_logger->log(LOG_ERR, "targetChanged but {} has no output?", monName);
|
||||
return;
|
||||
}
|
||||
|
||||
targetChanged(monitor);
|
||||
}
|
||||
|
||||
void CUI::targetChanged(const SP<Hyprtoolkit::IOutput>& mon) {
|
||||
const auto TARGET = g_matcher->getSetting(mon->port());
|
||||
|
||||
if (!TARGET) {
|
||||
g_logger->log(LOG_DEBUG, "Monitor {} has no target: no wp will be created", mon->port());
|
||||
return;
|
||||
}
|
||||
|
||||
std::erase_if(m_targets, [&mon](const auto& e) { return e->m_monitorName == mon->port(); });
|
||||
|
||||
m_targets.emplace_back(makeShared<CWallpaperTarget>(mon, TARGET->get().path, toFitMode(TARGET->get().fitMode)));
|
||||
}
|
||||
58
src/ui/UI.hpp
Normal file
58
src/ui/UI.hpp
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <hyprtoolkit/core/Backend.hpp>
|
||||
#include <hyprtoolkit/window/Window.hpp>
|
||||
#include <hyprtoolkit/element/Text.hpp>
|
||||
#include <hyprtoolkit/element/Null.hpp>
|
||||
#include <hyprtoolkit/element/Image.hpp>
|
||||
#include <hyprtoolkit/element/Rectangle.hpp>
|
||||
|
||||
#include <hyprutils/signal/Listener.hpp>
|
||||
|
||||
#include "../helpers/Memory.hpp"
|
||||
|
||||
class CWallpaperTarget {
|
||||
public:
|
||||
CWallpaperTarget(SP<Hyprtoolkit::IOutput> output, const std::string_view& path, Hyprtoolkit::eImageFitMode fitMode = Hyprtoolkit::IMAGE_FIT_MODE_COVER);
|
||||
~CWallpaperTarget() = default;
|
||||
|
||||
CWallpaperTarget(const CWallpaperTarget&) = delete;
|
||||
CWallpaperTarget(CWallpaperTarget&) = delete;
|
||||
CWallpaperTarget(CWallpaperTarget&&) = delete;
|
||||
|
||||
std::string m_monitorName;
|
||||
|
||||
private:
|
||||
SP<Hyprtoolkit::IWindow> m_window;
|
||||
SP<Hyprtoolkit::CNullElement> m_null;
|
||||
SP<Hyprtoolkit::CRectangleElement> m_bg;
|
||||
SP<Hyprtoolkit::CImageElement> m_image;
|
||||
SP<Hyprtoolkit::CTextElement> m_splash;
|
||||
};
|
||||
|
||||
class CUI {
|
||||
public:
|
||||
CUI();
|
||||
~CUI();
|
||||
|
||||
bool run();
|
||||
SP<Hyprtoolkit::IBackend> backend();
|
||||
|
||||
private:
|
||||
void targetChanged(const SP<Hyprtoolkit::IOutput>& mon);
|
||||
void targetChanged(const std::string_view& monName);
|
||||
void registerOutput(const SP<Hyprtoolkit::IOutput>& mon);
|
||||
|
||||
SP<Hyprtoolkit::IBackend> m_backend;
|
||||
|
||||
std::vector<SP<CWallpaperTarget>> m_targets;
|
||||
|
||||
struct {
|
||||
Hyprutils::Signal::CHyprSignalListener targetChanged;
|
||||
Hyprutils::Signal::CHyprSignalListener newMon;
|
||||
} m_listeners;
|
||||
};
|
||||
|
||||
inline UP<CUI> g_ui;
|
||||
Loading…
Add table
Reference in a new issue