New upstream version 0.6.0
This commit is contained in:
commit
05069a0b11
|
|
@ -0,0 +1,114 @@
|
|||
---
|
||||
Language: Cpp
|
||||
Standard: Auto
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
AlignArrayOfStructures: None
|
||||
AlignConsecutiveAssignments: None
|
||||
AlignConsecutiveBitFields: Consecutive
|
||||
AlignConsecutiveDeclarations: None
|
||||
AlignConsecutiveMacros: None
|
||||
AlignEscapedNewlines: Right
|
||||
AlignOperands: DontAlign
|
||||
AlignTrailingComments: true
|
||||
AllowAllArgumentsOnNextLine: false
|
||||
AllowAllConstructorInitializersOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: MultiLine
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BitFieldColonSpacing: Both
|
||||
BreakAfterJavaFieldAnnotations: true
|
||||
BreakBeforeBinaryOperators: NonAssignment
|
||||
BreakBeforeConceptDeclarations: true
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: BeforeComma
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 0
|
||||
CompactNamespaces: true
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ConstructorInitializerIndentWidth: 8
|
||||
ContinuationIndentWidth: 4
|
||||
DeriveLineEnding: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||
FixNamespaceComments: false
|
||||
IncludeBlocks: Preserve
|
||||
IncludeIsMainRegex: (Test)?$
|
||||
IncludeIsMainSourceRegex: ''
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseBlocks: false
|
||||
IndentCaseLabels: true
|
||||
IndentExternBlock: AfterExternBlock
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
InsertTrailingCommas: None
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
LambdaBodyIndentation: Signature
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: All
|
||||
PPIndentWidth: -1
|
||||
PackConstructorInitializers: BinPack
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 19
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakOpenParenthesis: 0
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyIndentedWhitespace: 0
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
PointerAlignment: Left
|
||||
QualifierAlignment: Leave
|
||||
ReferenceAlignment: Pointer
|
||||
ReflowComments: true
|
||||
RemoveBracesLLVM: false
|
||||
SeparateDefinitionBlocks: Leave
|
||||
ShortNamespaceLines: 1
|
||||
SortIncludes: CaseSensitive
|
||||
SortJavaStaticImport: Before
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: true
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: Never
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
TabWidth: 4
|
||||
UseCRLF: false
|
||||
UseTab: Never
|
||||
...
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
Checks: '-*,clang-diagnostic-*,bugprone-*,cert-*,misc-*,modernize-*,performance-*,readability-*,hicpp-exception-baseclass,hicpp-avoid-goto,-clang-diagnostic-address-of-packed-member,-cert-dcl16-c,-cert-dcl21-cpp,-cert-err58-cpp,-misc-unused-parameters,-misc-non-private-member-variables-in-classes,-modernize-avoid-c-arrays,-modernize-concat-nested-namespaces,-modernize-raw-string-literal,-modernize-use-default-member-init,-modernize-use-nodiscard,-modernize-use-override,-modernize-use-trailing-return-type,-readability-else-after-return,-readability-identifier-naming,-readability-implicit-bool-cast,-readability-implicit-bool-conversion,-readability-magic-numbers,-readability-named-parameter,-readability-qualified-auto,-readability-redundant-access-specifiers,-readability-redundant-member-init,-readability-uppercase-literal-suffix'
|
||||
WarningsAsErrors: '*'
|
||||
HeaderFilterRegex: ''
|
||||
FormatStyle: none
|
||||
...
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
name: Build Hyprpaper (Nix)
|
||||
|
||||
on: [push, pull_request, workflow_dispatch]
|
||||
jobs:
|
||||
nix:
|
||||
name: "Build"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install nix
|
||||
uses: cachix/install-nix-action@v20
|
||||
with:
|
||||
install_url: https://nixos.org/nix/install
|
||||
extra_nix_config: |
|
||||
auto-optimise-store = true
|
||||
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
|
||||
experimental-features = nix-command flakes
|
||||
- uses: cachix/cachix-action@v12
|
||||
with:
|
||||
name: hyprland
|
||||
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
|
||||
- name: Build Hyprpaper with default settings
|
||||
run: nix build --print-build-logs --accept-flake-config
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
CMakeLists.txt.user
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
CMakeScripts
|
||||
Testing
|
||||
cmake_install.cmake
|
||||
install_manifest.txt
|
||||
compile_commands.json
|
||||
CTestTestfile.cmake
|
||||
_deps
|
||||
|
||||
build/
|
||||
result
|
||||
/.vscode/
|
||||
/.idea
|
||||
|
||||
*.o
|
||||
*-protocol.c
|
||||
*-protocol.h
|
||||
.ccls-cache
|
||||
|
||||
hyprctl/hyprctl
|
||||
|
||||
gmon.out
|
||||
*.out
|
||||
*.tar.gz
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
cmake_minimum_required(VERSION 3.4)
|
||||
project(hyprpaper
|
||||
DESCRIPTION "A blazing fast wayland wallpaper utility"
|
||||
)
|
||||
|
||||
set(CMAKE_MESSAGE_LOG_LEVEL "STATUS")
|
||||
|
||||
message(STATUS "Configuring hyprpaper!")
|
||||
|
||||
# Get git info
|
||||
# hash and branch
|
||||
execute_process(
|
||||
COMMAND git rev-parse --abbrev-ref HEAD
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_BRANCH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
execute_process(
|
||||
COMMAND git rev-parse HEAD
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_COMMIT_HASH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
execute_process(
|
||||
COMMAND bash -c "git show ${GIT_COMMIT_HASH} | head -n 5 | tail -n 1"
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_COMMIT_MESSAGE
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
|
||||
execute_process(
|
||||
COMMAND bash -c "git diff-index --quiet HEAD -- || echo \"dirty\""
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE GIT_DIRTY
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
#
|
||||
#
|
||||
|
||||
include_directories(.)
|
||||
add_compile_options(-std=c++2b -DWLR_USE_UNSTABLE )
|
||||
add_compile_options(-Wall -Wextra -Wno-unused-parameter -Wno-unused-value -Wno-missing-field-initializers -Wno-narrowing)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(deps REQUIRED IMPORTED_TARGET wayland-client wayland-protocols cairo pango pangocairo libjpeg libwebp hyprlang>=0.2.0)
|
||||
|
||||
file(GLOB_RECURSE SRCFILES "src/*.cpp")
|
||||
|
||||
add_executable(hyprpaper ${SRCFILES})
|
||||
|
||||
target_compile_definitions(hyprpaper PRIVATE "-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"")
|
||||
target_compile_definitions(hyprpaper PRIVATE "-DGIT_BRANCH=\"${GIT_BRANCH}\"")
|
||||
target_compile_definitions(hyprpaper PRIVATE "-DGIT_COMMIT_MESSAGE=\"${GIT_COMMIT_MESSAGE}\"")
|
||||
target_compile_definitions(hyprpaper PRIVATE "-DGIT_DIRTY=\"${GIT_DIRTY}\"")
|
||||
|
||||
target_link_libraries(hyprpaper rt)
|
||||
|
||||
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
|
||||
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
|
||||
include(CPack)
|
||||
|
||||
target_link_libraries(hyprpaper PkgConfig::deps)
|
||||
|
||||
target_link_libraries(hyprpaper
|
||||
OpenGL
|
||||
GLESv2
|
||||
pthread
|
||||
magic
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
${CMAKE_SOURCE_DIR}/wlr-layer-shell-unstable-v1-protocol.o
|
||||
${CMAKE_SOURCE_DIR}/xdg-shell-protocol.o
|
||||
${CMAKE_SOURCE_DIR}/fractional-scale-v1-protocol.o
|
||||
${CMAKE_SOURCE_DIR}/viewporter-protocol.o
|
||||
wayland-cursor
|
||||
)
|
||||
|
||||
IF(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg -no-pie -fno-builtin")
|
||||
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg -no-pie -fno-builtin")
|
||||
SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pg -no-pie -fno-builtin")
|
||||
ENDIF(CMAKE_BUILD_TYPE MATCHES Debug OR CMAKE_BUILD_TYPE MATCHES DEBUG)
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2022, Hypr Development
|
||||
All rights reserved.
|
||||
|
||||
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.
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
PREFIX ?= /usr
|
||||
CFLAGS ?= -g -Wall -Wextra -Werror -Wno-unused-parameter -Wno-sign-compare -Wno-unused-function -Wno-unused-variable -Wno-unused-result -Wdeclaration-after-statement
|
||||
|
||||
CFLAGS += -I. -DWLR_USE_UNSTABLE -std=c99
|
||||
|
||||
WAYLAND_PROTOCOLS=$(shell pkg-config --variable=pkgdatadir wayland-protocols)
|
||||
WAYLAND_SCANNER=$(shell pkg-config --variable=wayland_scanner wayland-scanner)
|
||||
|
||||
PKGS = wlroots wayland-server
|
||||
CFLAGS += $(foreach p,$(PKGS),$(shell pkg-config --cflags $(p)))
|
||||
LDLIBS += $(foreach p,$(PKGS),$(shell pkg-config --libs $(p)))
|
||||
|
||||
wlr-layer-shell-unstable-v1-protocol.h:
|
||||
$(WAYLAND_SCANNER) client-header \
|
||||
protocols/wlr-layer-shell-unstable-v1.xml $@
|
||||
|
||||
wlr-layer-shell-unstable-v1-protocol.c:
|
||||
$(WAYLAND_SCANNER) private-code \
|
||||
protocols/wlr-layer-shell-unstable-v1.xml $@
|
||||
|
||||
wlr-layer-shell-unstable-v1-protocol.o: wlr-layer-shell-unstable-v1-protocol.h
|
||||
|
||||
xdg-shell-protocol.h:
|
||||
$(WAYLAND_SCANNER) client-header \
|
||||
$(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@
|
||||
|
||||
xdg-shell-protocol.c:
|
||||
$(WAYLAND_SCANNER) private-code \
|
||||
$(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@
|
||||
|
||||
xdg-shell-protocol.o: xdg-shell-protocol.h
|
||||
|
||||
fractional-scale-v1-protocol.h:
|
||||
$(WAYLAND_SCANNER) client-header \
|
||||
$(WAYLAND_PROTOCOLS)/staging/fractional-scale/fractional-scale-v1.xml $@
|
||||
|
||||
fractional-scale-v1-protocol.c:
|
||||
$(WAYLAND_SCANNER) private-code \
|
||||
$(WAYLAND_PROTOCOLS)/staging/fractional-scale/fractional-scale-v1.xml $@
|
||||
|
||||
fractional-scale-v1-protocol.o: fractional-scale-v1-protocol.h
|
||||
|
||||
viewporter-protocol.h:
|
||||
$(WAYLAND_SCANNER) client-header \
|
||||
$(WAYLAND_PROTOCOLS)/stable/viewporter/viewporter.xml $@
|
||||
|
||||
viewporter-protocol.c:
|
||||
$(WAYLAND_SCANNER) private-code \
|
||||
$(WAYLAND_PROTOCOLS)/stable/viewporter/viewporter.xml $@
|
||||
|
||||
viewporter-protocol.o: viewporter-protocol.h
|
||||
|
||||
protocols: wlr-layer-shell-unstable-v1-protocol.o xdg-shell-protocol.o fractional-scale-v1-protocol.o viewporter-protocol.o
|
||||
|
||||
clear:
|
||||
rm -rf build
|
||||
rm -f *.o *-protocol.h *-protocol.c
|
||||
|
||||
release:
|
||||
mkdir -p build && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Release -H./ -B./build -G Ninja
|
||||
cmake --build ./build --config Release --target all -j 10
|
||||
|
||||
debug:
|
||||
mkdir -p build && cmake --no-warn-unused-cli -DCMAKE_BUILD_TYPE:STRING=Debug -H./ -B./build -G Ninja
|
||||
cmake --build ./build --config Debug --target all -j 10
|
||||
|
||||
all:
|
||||
make clear
|
||||
make protocols
|
||||
make release
|
||||
|
||||
install:
|
||||
make all
|
||||
cp ./build/hyprpaper $(PREFIX)/bin -f
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
# 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.
|
||||
|
||||
# Features
|
||||
- Per-output wallpapers
|
||||
- fill or contain modes
|
||||
- fractional scaling support
|
||||
- IPC for blazing fast wallpaper switches
|
||||
- preloading targets into memory
|
||||
|
||||
# Installation
|
||||
|
||||
[Arch Linux](https://archlinux.org/packages/community/x86_64/hyprpaper/): `pacman -S hyprpaper`
|
||||
|
||||
## Manual:
|
||||
|
||||
### 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
|
||||
- hyprlang
|
||||
|
||||
Please note hyprpaper > 0.5.0 depends on [hyprlang](https://github.com/hyprwm/hyprlang) which is new
|
||||
and might not be packaged for your distro yet. If that's the case, build and install it from source.
|
||||
|
||||
To install all of these in Fedora, run this command:
|
||||
```
|
||||
sudo dnf install wayland-devel wayland-protocols-devel pango-devel cairo-devel file-devel libglvnd-devel libglvnd-core-devel libjpeg-turbo-devel libwebp-devel gcc-c++
|
||||
```
|
||||
|
||||
On Arch:
|
||||
```
|
||||
sudo pacman -S ninja gcc wayland-protocols libjpeg-turbo libwebp pango cairo pkgconf cmake libglvnd wayland
|
||||
```
|
||||
|
||||
On OpenSUSE:
|
||||
```
|
||||
sudo zypper install ninja gcc-c++ wayland-protocols-devel Mesa-libGLESv3-devel file-devel
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
```
|
||||
git clone https://github.com/hyprwm/hyprpaper
|
||||
cd hyprpaper
|
||||
make all
|
||||
```
|
||||
|
||||
*the output binary will be in `./build/`, copy it to your PATH, e.g. `/usr/bin`*
|
||||
|
||||
# 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 inital 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
|
||||
```
|
||||
|
||||
Preload will tell Hyprland to load a particular image (supported formats: png, jpg, jpeg, 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 for a wildcard (aka fallback). You can also use `desc:` followed by the monitor's description without the (PORT) at the end)
|
||||
|
||||
You may add `contain:` before the file path in `wallpaper=` to set the mode to contain 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 varibles 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
|
||||
```
|
||||
|
||||
# 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!)
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
{
|
||||
"nodes": {
|
||||
"hyprlang": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1704230242,
|
||||
"narHash": "sha256-S8DM+frECqmAdaUb3y5n3RjY73ajZcL5rnmx5YO+CkY=",
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprlang",
|
||||
"rev": "db5e1399b90d5a339330bdd49c5bca6fe58d6f60",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hyprwm",
|
||||
"repo": "hyprlang",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1702645756,
|
||||
"narHash": "sha256-qKI6OR3TYJYQB3Q8mAZ+DG4o/BR9ptcv9UnRV2hzljc=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "40c3c94c241286dd2243ea34d3aef8a488f9e4d0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1703637592,
|
||||
"narHash": "sha256-8MXjxU0RfFfzl57Zy3OfXCITS0qWDNLzlBAdwxGZwfY=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "cfc3698c31b1fb9cdcf10f36c9643460264d0ca8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"hyprlang": "hyprlang",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
description = "Hyprpaper is a blazing fast Wayland wallpaper utility with IPC controls";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
hyprlang.url = "github:hyprwm/hyprlang";
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
nixpkgs,
|
||||
...
|
||||
} @ inputs: let
|
||||
inherit (nixpkgs) lib;
|
||||
genSystems = lib.genAttrs [
|
||||
# Add more systems if they are supported
|
||||
"x86_64-linux"
|
||||
"aarch64-linux"
|
||||
];
|
||||
pkgsFor = nixpkgs.legacyPackages;
|
||||
mkDate = longDate: (lib.concatStringsSep "-" [
|
||||
(__substring 0 4 longDate)
|
||||
(__substring 4 2 longDate)
|
||||
(__substring 6 2 longDate)
|
||||
]);
|
||||
in {
|
||||
overlays.default = _: prev: rec {
|
||||
hyprpaper = prev.callPackage ./nix/default.nix {
|
||||
stdenv = prev.gcc13Stdenv;
|
||||
version = "0.pre" + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty");
|
||||
inherit (prev.xorg) libXdmcp;
|
||||
inherit (inputs.hyprlang.packages.${prev.system}) hyprlang;
|
||||
};
|
||||
hyprpaper-debug = hyprpaper.override {debug = true;};
|
||||
};
|
||||
|
||||
packages = genSystems (system:
|
||||
(self.overlays.default null pkgsFor.${system})
|
||||
// {default = self.packages.${system}.hyprpaper;});
|
||||
|
||||
formatter = genSystems (system: pkgsFor.${system}.alejandra);
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
{
|
||||
lib,
|
||||
stdenv,
|
||||
pkg-config,
|
||||
cmake,
|
||||
ninja,
|
||||
cairo,
|
||||
expat,
|
||||
file,
|
||||
fribidi,
|
||||
hyprlang,
|
||||
libdatrie,
|
||||
libGL,
|
||||
libjpeg,
|
||||
libselinux,
|
||||
libsepol,
|
||||
libthai,
|
||||
libwebp,
|
||||
pango,
|
||||
pcre,
|
||||
pcre2,
|
||||
util-linux,
|
||||
wayland,
|
||||
wayland-protocols,
|
||||
wayland-scanner,
|
||||
libXdmcp,
|
||||
debug ? false,
|
||||
version ? "git",
|
||||
}:
|
||||
stdenv.mkDerivation {
|
||||
pname = "hyprpaper" + lib.optionalString debug "-debug";
|
||||
inherit version;
|
||||
src = ../.;
|
||||
|
||||
nativeBuildInputs = [
|
||||
cmake
|
||||
ninja
|
||||
pkg-config
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
cairo
|
||||
expat
|
||||
file
|
||||
fribidi
|
||||
hyprlang
|
||||
libdatrie
|
||||
libGL
|
||||
libjpeg
|
||||
libselinux
|
||||
libsepol
|
||||
libthai
|
||||
libwebp
|
||||
pango
|
||||
pcre
|
||||
pcre2
|
||||
wayland
|
||||
wayland-protocols
|
||||
wayland-scanner
|
||||
libXdmcp
|
||||
util-linux
|
||||
];
|
||||
|
||||
configurePhase = ''
|
||||
runHook preConfigure
|
||||
|
||||
make protocols
|
||||
|
||||
runHook postConfigure
|
||||
'';
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
make release
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
mkdir -p $out/{bin,share/licenses}
|
||||
|
||||
install -Dm755 build/hyprpaper -t $out/bin
|
||||
install -Dm644 LICENSE -t $out/share/licenses/hyprpaper
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
|
||||
meta = with lib; {
|
||||
homepage = "https://github.com/hyprwm/hyprpaper";
|
||||
description = "A blazing fast wayland wallpaper utility with IPC controls";
|
||||
license = licenses.bsd3;
|
||||
platforms = platforms.linux;
|
||||
mainProgram = "hyprpaper";
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,285 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<protocol name="wlr_layer_shell_v1_unstable_v1">
|
||||
<copyright>
|
||||
Copyright © 2017 Drew DeVault
|
||||
|
||||
Permission to use, copy, modify, distribute, and sell this
|
||||
software and its documentation for any purpose is hereby granted
|
||||
without fee, provided that the above copyright notice appear in
|
||||
all copies and that both that copyright notice and this permission
|
||||
notice appear in supporting documentation, and that the name of
|
||||
the copyright holders not be used in advertising or publicity
|
||||
pertaining to distribution of the software without specific,
|
||||
written prior permission. The copyright holders make no
|
||||
representations about the suitability of this software for any
|
||||
purpose. It is provided "as is" without express or implied
|
||||
warranty.
|
||||
|
||||
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
|
||||
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
</copyright>
|
||||
|
||||
<interface name="zwlr_layer_shell_v1" version="1">
|
||||
<description summary="create surfaces that are layers of the desktop">
|
||||
Clients can use this interface to assign the surface_layer role to
|
||||
wl_surfaces. Such surfaces are assigned to a "layer" of the output and
|
||||
rendered with a defined z-depth respective to each other. They may also be
|
||||
anchored to the edges and corners of a screen and specify input handling
|
||||
semantics. This interface should be suitable for the implementation of
|
||||
many desktop shell components, and a broad number of other applications
|
||||
that interact with the desktop.
|
||||
</description>
|
||||
|
||||
<request name="get_layer_surface">
|
||||
<description summary="create a layer_surface from a surface">
|
||||
Create a layer surface for an existing surface. This assigns the role of
|
||||
layer_surface, or raises a protocol error if another role is already
|
||||
assigned.
|
||||
|
||||
Creating a layer surface from a wl_surface which has a buffer attached
|
||||
or committed is a client error, and any attempts by a client to attach
|
||||
or manipulate a buffer prior to the first layer_surface.configure call
|
||||
must also be treated as errors.
|
||||
|
||||
You may pass NULL for output to allow the compositor to decide which
|
||||
output to use. Generally this will be the one that the user most
|
||||
recently interacted with.
|
||||
|
||||
Clients can specify a namespace that defines the purpose of the layer
|
||||
surface.
|
||||
</description>
|
||||
<arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/>
|
||||
<arg name="surface" type="object" interface="wl_surface"/>
|
||||
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
|
||||
<arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/>
|
||||
<arg name="namespace" type="string" summary="namespace for the layer surface"/>
|
||||
</request>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="role" value="0" summary="wl_surface has another role"/>
|
||||
<entry name="invalid_layer" value="1" summary="layer value is invalid"/>
|
||||
<entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/>
|
||||
</enum>
|
||||
|
||||
<enum name="layer">
|
||||
<description summary="available layers for surfaces">
|
||||
These values indicate which layers a surface can be rendered in. They
|
||||
are ordered by z depth, bottom-most first. Traditional shell surfaces
|
||||
will typically be rendered between the bottom and top layers.
|
||||
Fullscreen shell surfaces are typically rendered at the top layer.
|
||||
Multiple surfaces can share a single layer, and ordering within a
|
||||
single layer is undefined.
|
||||
</description>
|
||||
|
||||
<entry name="background" value="0"/>
|
||||
<entry name="bottom" value="1"/>
|
||||
<entry name="top" value="2"/>
|
||||
<entry name="overlay" value="3"/>
|
||||
</enum>
|
||||
</interface>
|
||||
|
||||
<interface name="zwlr_layer_surface_v1" version="1">
|
||||
<description summary="layer metadata interface">
|
||||
An interface that may be implemented by a wl_surface, for surfaces that
|
||||
are designed to be rendered as a layer of a stacked desktop-like
|
||||
environment.
|
||||
|
||||
Layer surface state (size, anchor, exclusive zone, margin, interactivity)
|
||||
is double-buffered, and will be applied at the time wl_surface.commit of
|
||||
the corresponding wl_surface is called.
|
||||
</description>
|
||||
|
||||
<request name="set_size">
|
||||
<description summary="sets the size of the surface">
|
||||
Sets the size of the surface in surface-local coordinates. The
|
||||
compositor will display the surface centered with respect to its
|
||||
anchors.
|
||||
|
||||
If you pass 0 for either value, the compositor will assign it and
|
||||
inform you of the assignment in the configure event. You must set your
|
||||
anchor to opposite edges in the dimensions you omit; not doing so is a
|
||||
protocol error. Both values are 0 by default.
|
||||
|
||||
Size is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="width" type="uint"/>
|
||||
<arg name="height" type="uint"/>
|
||||
</request>
|
||||
|
||||
<request name="set_anchor">
|
||||
<description summary="configures the anchor point of the surface">
|
||||
Requests that the compositor anchor the surface to the specified edges
|
||||
and corners. If two orthoginal edges are specified (e.g. 'top' and
|
||||
'left'), then the anchor point will be the intersection of the edges
|
||||
(e.g. the top left corner of the output); otherwise the anchor point
|
||||
will be centered on that edge, or in the center if none is specified.
|
||||
|
||||
Anchor is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="anchor" type="uint" enum="anchor"/>
|
||||
</request>
|
||||
|
||||
<request name="set_exclusive_zone">
|
||||
<description summary="configures the exclusive geometry of this surface">
|
||||
Requests that the compositor avoids occluding an area of the surface
|
||||
with other surfaces. The compositor's use of this information is
|
||||
implementation-dependent - do not assume that this region will not
|
||||
actually be occluded.
|
||||
|
||||
A positive value is only meaningful if the surface is anchored to an
|
||||
edge, rather than a corner. The zone is the number of surface-local
|
||||
coordinates from the edge that are considered exclusive.
|
||||
|
||||
Surfaces that do not wish to have an exclusive zone may instead specify
|
||||
how they should interact with surfaces that do. If set to zero, the
|
||||
surface indicates that it would like to be moved to avoid occluding
|
||||
surfaces with a positive excluzive zone. If set to -1, the surface
|
||||
indicates that it would not like to be moved to accommodate for other
|
||||
surfaces, and the compositor should extend it all the way to the edges
|
||||
it is anchored to.
|
||||
|
||||
For example, a panel might set its exclusive zone to 10, so that
|
||||
maximized shell surfaces are not shown on top of it. A notification
|
||||
might set its exclusive zone to 0, so that it is moved to avoid
|
||||
occluding the panel, but shell surfaces are shown underneath it. A
|
||||
wallpaper or lock screen might set their exclusive zone to -1, so that
|
||||
they stretch below or over the panel.
|
||||
|
||||
The default value is 0.
|
||||
|
||||
Exclusive zone is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="zone" type="int"/>
|
||||
</request>
|
||||
|
||||
<request name="set_margin">
|
||||
<description summary="sets a margin from the anchor point">
|
||||
Requests that the surface be placed some distance away from the anchor
|
||||
point on the output, in surface-local coordinates. Setting this value
|
||||
for edges you are not anchored to has no effect.
|
||||
|
||||
The exclusive zone includes the margin.
|
||||
|
||||
Margin is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="top" type="int"/>
|
||||
<arg name="right" type="int"/>
|
||||
<arg name="bottom" type="int"/>
|
||||
<arg name="left" type="int"/>
|
||||
</request>
|
||||
|
||||
<request name="set_keyboard_interactivity">
|
||||
<description summary="requests keyboard events">
|
||||
Set to 1 to request that the seat send keyboard events to this layer
|
||||
surface. For layers below the shell surface layer, the seat will use
|
||||
normal focus semantics. For layers above the shell surface layers, the
|
||||
seat will always give exclusive keyboard focus to the top-most layer
|
||||
which has keyboard interactivity set to true.
|
||||
|
||||
Layer surfaces receive pointer, touch, and tablet events normally. If
|
||||
you do not want to receive them, set the input region on your surface
|
||||
to an empty region.
|
||||
|
||||
Events is double-buffered, see wl_surface.commit.
|
||||
</description>
|
||||
<arg name="keyboard_interactivity" type="uint"/>
|
||||
</request>
|
||||
|
||||
<request name="get_popup">
|
||||
<description summary="assign this layer_surface as an xdg_popup parent">
|
||||
This assigns an xdg_popup's parent to this layer_surface. This popup
|
||||
should have been created via xdg_surface::get_popup with the parent set
|
||||
to NULL, and this request must be invoked before committing the popup's
|
||||
initial state.
|
||||
|
||||
See the documentation of xdg_popup for more details about what an
|
||||
xdg_popup is and how it is used.
|
||||
</description>
|
||||
<arg name="popup" type="object" interface="xdg_popup"/>
|
||||
</request>
|
||||
|
||||
<request name="ack_configure">
|
||||
<description summary="ack a configure event">
|
||||
When a configure event is received, if a client commits the
|
||||
surface in response to the configure event, then the client
|
||||
must make an ack_configure request sometime before the commit
|
||||
request, passing along the serial of the configure event.
|
||||
|
||||
If the client receives multiple configure events before it
|
||||
can respond to one, it only has to ack the last configure event.
|
||||
|
||||
A client is not required to commit immediately after sending
|
||||
an ack_configure request - it may even ack_configure several times
|
||||
before its next surface commit.
|
||||
|
||||
A client may send multiple ack_configure requests before committing, but
|
||||
only the last request sent before a commit indicates which configure
|
||||
event the client really is responding to.
|
||||
</description>
|
||||
<arg name="serial" type="uint" summary="the serial from the configure event"/>
|
||||
</request>
|
||||
|
||||
<request name="destroy" type="destructor">
|
||||
<description summary="destroy the layer_surface">
|
||||
This request destroys the layer surface.
|
||||
</description>
|
||||
</request>
|
||||
|
||||
<event name="configure">
|
||||
<description summary="suggest a surface change">
|
||||
The configure event asks the client to resize its surface.
|
||||
|
||||
Clients should arrange their surface for the new states, and then send
|
||||
an ack_configure request with the serial sent in this configure event at
|
||||
some point before committing the new surface.
|
||||
|
||||
The client is free to dismiss all but the last configure event it
|
||||
received.
|
||||
|
||||
The width and height arguments specify the size of the window in
|
||||
surface-local coordinates.
|
||||
|
||||
The size is a hint, in the sense that the client is free to ignore it if
|
||||
it doesn't resize, pick a smaller size (to satisfy aspect ratio or
|
||||
resize in steps of NxM pixels). If the client picks a smaller size and
|
||||
is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
|
||||
surface will be centered on this axis.
|
||||
|
||||
If the width or height arguments are zero, it means the client should
|
||||
decide its own window dimension.
|
||||
</description>
|
||||
<arg name="serial" type="uint"/>
|
||||
<arg name="width" type="uint"/>
|
||||
<arg name="height" type="uint"/>
|
||||
</event>
|
||||
|
||||
<event name="closed">
|
||||
<description summary="surface should be closed">
|
||||
The closed event is sent by the compositor when the surface will no
|
||||
longer be shown. The output may have been destroyed or the user may
|
||||
have asked for it to be removed. Further changes to the surface will be
|
||||
ignored. The client should destroy the resource after receiving this
|
||||
event, and create a new surface if they so choose.
|
||||
</description>
|
||||
</event>
|
||||
|
||||
<enum name="error">
|
||||
<entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
|
||||
<entry name="invalid_size" value="1" summary="size is invalid"/>
|
||||
<entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
|
||||
</enum>
|
||||
|
||||
<enum name="anchor" bitfield="true">
|
||||
<entry name="top" value="1" summary="the top edge of the anchor rectangle"/>
|
||||
<entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/>
|
||||
<entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
|
||||
<entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
|
||||
</enum>
|
||||
</interface>
|
||||
</protocol>
|
||||
|
|
@ -0,0 +1,604 @@
|
|||
#include "Hyprpaper.hpp"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <signal.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
CHyprpaper::CHyprpaper() = default;
|
||||
|
||||
void CHyprpaper::init() {
|
||||
|
||||
if (!lockSingleInstance()) {
|
||||
Debug::log(CRIT, "Cannot launch multiple instances of Hyprpaper at once!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
removeOldHyprpaperImages();
|
||||
|
||||
g_pConfigManager = std::make_unique<CConfigManager>();
|
||||
g_pIPCSocket = std::make_unique<CIPCSocket>();
|
||||
|
||||
g_pConfigManager->parse();
|
||||
|
||||
m_sDisplay = (wl_display*)wl_display_connect(nullptr);
|
||||
|
||||
if (!m_sDisplay) {
|
||||
Debug::log(CRIT, "No wayland compositor running!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
preloadAllWallpapersFromConfig();
|
||||
|
||||
if (std::any_cast<Hyprlang::INT>(g_pConfigManager->config->getConfigValue("ipc")))
|
||||
g_pIPCSocket->initialize();
|
||||
|
||||
// run
|
||||
wl_registry* registry = wl_display_get_registry(m_sDisplay);
|
||||
wl_registry_add_listener(registry, &Events::registryListener, nullptr);
|
||||
|
||||
while (wl_display_dispatch(m_sDisplay) != -1) {
|
||||
std::lock_guard<std::mutex> lg(m_mtTickMutex);
|
||||
tick(true);
|
||||
}
|
||||
|
||||
unlockSingleInstance();
|
||||
}
|
||||
|
||||
void CHyprpaper::tick(bool force) {
|
||||
bool reload = 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 %s, preload path %s", path.c_str(), PRELOADPATH.c_str());
|
||||
|
||||
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 %s as it already is preloaded!", ewp.c_str());
|
||||
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(wl_seat* pSeat) {
|
||||
wl_seat_add_listener(pSeat, &Events::seatListener, pSeat);
|
||||
}
|
||||
|
||||
void CHyprpaper::recheckMonitor(SMonitor* pMonitor) {
|
||||
ensureMonitorHasActiveWallpaper(pMonitor);
|
||||
|
||||
if (pMonitor->wantsACK) {
|
||||
pMonitor->wantsACK = false;
|
||||
zwlr_layer_surface_v1_ack_configure(pMonitor->pCurrentLayerSurface->pLayerSurface, pMonitor->configureSerial);
|
||||
|
||||
if (!pMonitor->pCurrentLayerSurface->pCursorImg) {
|
||||
int XCURSOR_SIZE = 24;
|
||||
if (const auto CURSORSIZENV = getenv("XCURSOR_SIZE"); CURSORSIZENV) {
|
||||
try {
|
||||
if (XCURSOR_SIZE = std::stoi(CURSORSIZENV); XCURSOR_SIZE <= 0) {
|
||||
throw std::exception();
|
||||
}
|
||||
} catch (...) {
|
||||
Debug::log(WARN, "XCURSOR_SIZE environment variable is set incorrectly");
|
||||
XCURSOR_SIZE = 24;
|
||||
}
|
||||
}
|
||||
|
||||
pMonitor->pCurrentLayerSurface->pCursorTheme = wl_cursor_theme_load(getenv("XCURSOR_THEME"), XCURSOR_SIZE * pMonitor->scale, m_sSHM);
|
||||
pMonitor->pCurrentLayerSurface->pCursorImg = wl_cursor_theme_get_cursor(pMonitor->pCurrentLayerSurface->pCursorTheme, "left_ptr")->images[0];
|
||||
}
|
||||
}
|
||||
|
||||
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 %s", entry.path().string().c_str());
|
||||
cleaned++;
|
||||
}
|
||||
}
|
||||
|
||||
if (cleaned != 0) {
|
||||
Debug::log(LOG, "Cleaned old hyprpaper preloads (%i), removing %.1fMB", 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 %s, Shared Memory usage: %.1fMB", wt.m_szPath.c_str(), 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: %.1fMB", 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;
|
||||
}
|
||||
}
|
||||
|
||||
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 %s does not have a target! A wallpaper will not be created.", pMonitor->name.c_str());
|
||||
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);
|
||||
const auto POOL = wl_shm_create_pool(g_pHyprpaper->m_sSHM, FD, SIZE);
|
||||
pBuffer->buffer = wl_shm_pool_create_buffer(POOL, 0, w, h, STRIDE, format);
|
||||
wl_shm_pool_destroy(POOL);
|
||||
|
||||
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) {
|
||||
wl_buffer_destroy(pBuffer->buffer);
|
||||
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* const PRENDERSPLASH = reinterpret_cast<Hyprlang::INT* const*>(g_pConfigManager->config->getConfigValuePtr("splash")->getDataStaticPtr());
|
||||
static auto* const PSPLASHOFFSET = reinterpret_cast<Hyprlang::FLOAT* const*>(g_pConfigManager->config->getConfigValuePtr("splash_offset")->getDataStaticPtr());
|
||||
const auto PWALLPAPERTARGET = m_mMonitorActiveWallpaperTargets[pMonitor];
|
||||
const auto CONTAIN = m_mMonitorWallpaperRenderData[pMonitor->name].contain;
|
||||
|
||||
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 %s: %s at [%.2f, %.2f], scale: %.2f (original image size: [%i, %i])", pMonitor->name.c_str(), PWALLPAPERTARGET->m_szPath.c_str(), origin.x, origin.y, scale, (int)PWALLPAPERTARGET->m_vSize.x, (int)PWALLPAPERTARGET->m_vSize.y);
|
||||
|
||||
cairo_scale(PCAIRO, scale, scale);
|
||||
cairo_set_source_surface(PCAIRO, PWALLPAPERTARGET->m_pCairoSurface, origin.x, origin.y);
|
||||
|
||||
cairo_paint(PCAIRO);
|
||||
|
||||
if (**PRENDERSPLASH && getenv("HYPRLAND_INSTANCE_SIGNATURE")) {
|
||||
auto SPLASH = execAndGet("hyprctl splash");
|
||||
SPLASH.pop_back();
|
||||
|
||||
Debug::log(LOG, "Rendering splash: %s", SPLASH.c_str());
|
||||
|
||||
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);
|
||||
|
||||
cairo_set_source_rgba(PCAIRO, 1.0, 1.0, 1.0, 0.32);
|
||||
|
||||
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: %d, 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_restore(PCAIRO);
|
||||
|
||||
if (pMonitor->pCurrentLayerSurface) {
|
||||
wl_surface_attach(pMonitor->pCurrentLayerSurface->pSurface, PBUFFER->buffer, 0, 0);
|
||||
wl_surface_set_buffer_scale(pMonitor->pCurrentLayerSurface->pSurface, pMonitor->pCurrentLayerSurface->pFractionalScaleInfo ? 1 : pMonitor->scale);
|
||||
wl_surface_damage_buffer(pMonitor->pCurrentLayerSurface->pSurface, 0, 0, 0xFFFF, 0xFFFF);
|
||||
|
||||
// our wps are always opaque
|
||||
auto opaqueRegion = wl_compositor_create_region(g_pHyprpaper->m_sCompositor);
|
||||
wl_region_add(opaqueRegion, 0, 0, PBUFFER->pixelSize.x, PBUFFER->pixelSize.y);
|
||||
wl_surface_set_opaque_region(pMonitor->pCurrentLayerSurface->pSurface, opaqueRegion);
|
||||
|
||||
if (pMonitor->pCurrentLayerSurface->pFractionalScaleInfo) {
|
||||
Debug::log(LOG, "Submitting viewport dest size %ix%i for %x", static_cast<int>(std::round(pMonitor->size.x)), static_cast<int>(std::round(pMonitor->size.y)), pMonitor->pCurrentLayerSurface);
|
||||
wp_viewport_set_destination(pMonitor->pCurrentLayerSurface->pViewport, static_cast<int>(std::round(pMonitor->size.x)), static_cast<int>(std::round(pMonitor->size.y)));
|
||||
}
|
||||
wl_surface_commit(pMonitor->pCurrentLayerSurface->pSurface);
|
||||
|
||||
wl_region_destroy(opaqueRegion);
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
#pragma once
|
||||
|
||||
#include "config/ConfigManager.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "events/Events.hpp"
|
||||
#include "helpers/MiscFunctions.hpp"
|
||||
#include "helpers/Monitor.hpp"
|
||||
#include "helpers/PoolBuffer.hpp"
|
||||
#include "ipc/Socket.hpp"
|
||||
#include "render/WallpaperTarget.hpp"
|
||||
#include <mutex>
|
||||
|
||||
struct SWallpaperRenderData {
|
||||
bool contain = false;
|
||||
};
|
||||
|
||||
class CHyprpaper {
|
||||
public:
|
||||
// important
|
||||
wl_display* m_sDisplay; // assured
|
||||
wl_compositor* m_sCompositor; // assured
|
||||
wl_shm* m_sSHM; // assured
|
||||
zwlr_layer_shell_v1* m_sLayerShell = nullptr; // expected
|
||||
wp_fractional_scale_manager_v1* m_sFractionalScale = nullptr; // will remain null if not bound
|
||||
wp_viewporter* m_sViewporter = nullptr; // expected
|
||||
|
||||
// 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(wl_seat*);
|
||||
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;
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
#include "ConfigManager.hpp"
|
||||
#include "../Hyprpaper.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;
|
||||
}
|
||||
|
||||
if (WALLPAPER[0] == '~') {
|
||||
static const char* const ENVHOME = getenv("HOME");
|
||||
WALLPAPER = std::string(ENVHOME) + WALLPAPER.substr(1);
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(WALLPAPER)) {
|
||||
result.setError("wallpaper failed (no such file)");
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (!std::filesystem::exists(WALLPAPER)) {
|
||||
Hyprlang::CParseResult result;
|
||||
result.setError((std::string{"no such file: "} + 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) {
|
||||
|
||||
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")
|
||||
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{};
|
||||
}
|
||||
|
||||
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", {1L});
|
||||
config->addConfigValue("splash", {1L});
|
||||
config->addConfigValue("splash_offset", {2.F});
|
||||
|
||||
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->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;
|
||||
|
||||
static const char* xdgConfigHome = getenv("XDG_CONFIG_HOME");
|
||||
std::string configPath;
|
||||
if (!xdgConfigHome)
|
||||
configPath = getenv("HOME") + std::string("/.config");
|
||||
else
|
||||
configPath = xdgConfigHome;
|
||||
|
||||
return configPath + "/hypr/hyprpaper.conf";
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
#include "../defines.hpp"
|
||||
#include <hyprlang.hpp>
|
||||
|
||||
class CIPCSocket;
|
||||
|
||||
class CConfigManager {
|
||||
public:
|
||||
// gets all the data from the config
|
||||
CConfigManager();
|
||||
void parse();
|
||||
|
||||
std::deque<std::string> m_dRequestedPreloads;
|
||||
std::string getMainConfigPath();
|
||||
std::string trimPath(std::string path);
|
||||
|
||||
std::unique_ptr<Hyprlang::CConfig> config;
|
||||
|
||||
private:
|
||||
friend class CIPCSocket;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<CConfigManager> g_pConfigManager;
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
#include "Log.hpp"
|
||||
#include "../includes.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
void Debug::log(LogLevel level, const char* fmt, ...) {
|
||||
|
||||
std::string levelstr = "";
|
||||
|
||||
switch (level) {
|
||||
case LOG:
|
||||
levelstr = "[LOG] ";
|
||||
break;
|
||||
case WARN:
|
||||
levelstr = "[WARN] ";
|
||||
break;
|
||||
case ERR:
|
||||
levelstr = "[ERR] ";
|
||||
break;
|
||||
case CRIT:
|
||||
levelstr = "[CRITICAL] ";
|
||||
break;
|
||||
case INFO:
|
||||
levelstr = "[INFO] ";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
char buf[LOGMESSAGESIZE] = "";
|
||||
char* outputStr;
|
||||
int logLen;
|
||||
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
logLen = vsnprintf(buf, sizeof buf, fmt, args);
|
||||
va_end(args);
|
||||
|
||||
if ((long unsigned int)logLen < sizeof buf) {
|
||||
outputStr = strdup(buf);
|
||||
} else {
|
||||
outputStr = (char*)malloc(logLen + 1);
|
||||
|
||||
if (!outputStr) {
|
||||
printf("CRITICAL: Cannot alloc size %d for log! (Out of memory?)", logLen + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
va_start(args, fmt);
|
||||
vsnprintf(outputStr, logLen + 1U, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
// hyprpaper only logs to stdout
|
||||
std::cout << levelstr << outputStr << "\n";
|
||||
|
||||
// free the log
|
||||
free(outputStr);
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
|
||||
#define LOGMESSAGESIZE 1024
|
||||
|
||||
enum LogLevel {
|
||||
NONE = -1,
|
||||
LOG = 0,
|
||||
WARN,
|
||||
ERR,
|
||||
CRIT,
|
||||
INFO
|
||||
};
|
||||
|
||||
namespace Debug {
|
||||
void log(LogLevel level, const char* fmt, ...);
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
#pragma once
|
||||
|
||||
#include "includes.hpp"
|
||||
#include "debug/Log.hpp"
|
||||
#include "helpers/Vector2D.hpp"
|
||||
|
||||
// git stuff
|
||||
#ifndef GIT_COMMIT_HASH
|
||||
#define GIT_COMMIT_HASH "?"
|
||||
#endif
|
||||
#ifndef GIT_BRANCH
|
||||
#define GIT_BRANCH "?"
|
||||
#endif
|
||||
#ifndef GIT_COMMIT_MESSAGE
|
||||
#define GIT_COMMIT_MESSAGE "?"
|
||||
#endif
|
||||
#ifndef GIT_DIRTY
|
||||
#define GIT_DIRTY "?"
|
||||
#endif
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
#include "Events.hpp"
|
||||
#include "../Hyprpaper.hpp"
|
||||
|
||||
void Events::geometry(void *data, wl_output *output, 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) {
|
||||
// ignored
|
||||
}
|
||||
|
||||
void Events::mode(void *data, wl_output *output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
|
||||
const auto PMONITOR = (SMonitor*)data;
|
||||
|
||||
PMONITOR->size = Vector2D(width, height);
|
||||
}
|
||||
|
||||
void Events::done(void *data, wl_output *wl_output) {
|
||||
const auto PMONITOR = (SMonitor*)data;
|
||||
|
||||
PMONITOR->readyForLS = true;
|
||||
|
||||
std::lock_guard<std::mutex> lg(g_pHyprpaper->m_mtTickMutex);
|
||||
g_pHyprpaper->tick(true);
|
||||
}
|
||||
|
||||
void Events::scale(void *data, wl_output *wl_output, int32_t scale) {
|
||||
const auto PMONITOR = (SMonitor*)data;
|
||||
|
||||
PMONITOR->scale = scale;
|
||||
}
|
||||
|
||||
void Events::name(void *data, wl_output *wl_output, const char *name) {
|
||||
const auto PMONITOR = (SMonitor*)data;
|
||||
|
||||
PMONITOR->name = name;
|
||||
}
|
||||
|
||||
void Events::description(void *data, wl_output *wl_output, const char *description) {
|
||||
const auto PMONITOR = (SMonitor*)data;
|
||||
|
||||
PMONITOR->description = description;
|
||||
}
|
||||
|
||||
void Events::handleCapabilities(void *data, wl_seat *wl_seat, uint32_t capabilities) {
|
||||
if (capabilities & WL_SEAT_CAPABILITY_POINTER) {
|
||||
wl_pointer_add_listener(wl_seat_get_pointer(wl_seat), &pointerListener, wl_seat);
|
||||
}
|
||||
}
|
||||
|
||||
void Events::handlePointerLeave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) {
|
||||
// ignored
|
||||
wl_surface_commit(surface);
|
||||
|
||||
g_pHyprpaper->m_pLastMonitor = nullptr;
|
||||
}
|
||||
|
||||
void Events::handlePointerAxis(void *data, wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) {
|
||||
// ignored
|
||||
}
|
||||
|
||||
void Events::handlePointerMotion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
|
||||
// ignored
|
||||
if (g_pHyprpaper->m_pLastMonitor) {
|
||||
wl_surface_commit(g_pHyprpaper->m_pLastMonitor->pCurrentLayerSurface->pSurface);
|
||||
}
|
||||
}
|
||||
|
||||
void Events::handlePointerButton(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t button_state) {
|
||||
// ignored
|
||||
}
|
||||
|
||||
void Events::handlePointerEnter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y) {
|
||||
for (auto& mon : g_pHyprpaper->m_vMonitors) {
|
||||
if (mon->pCurrentLayerSurface->pSurface == surface) {
|
||||
g_pHyprpaper->m_pLastMonitor = mon.get();
|
||||
|
||||
wl_surface_set_buffer_scale(mon->pCurrentLayerSurface->pCursorSurface, mon->scale);
|
||||
wl_surface_attach(mon->pCurrentLayerSurface->pCursorSurface, wl_cursor_image_get_buffer(mon->pCurrentLayerSurface->pCursorImg), 0, 0);
|
||||
wl_pointer_set_cursor(wl_pointer, serial, mon->pCurrentLayerSurface->pCursorSurface, mon->pCurrentLayerSurface->pCursorImg->hotspot_x / mon->scale, mon->pCurrentLayerSurface->pCursorImg->hotspot_y / mon->scale);
|
||||
wl_surface_commit(mon->pCurrentLayerSurface->pCursorSurface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Events::ls_configure(void *data, zwlr_layer_surface_v1 *surface, uint32_t serial, uint32_t width, uint32_t height) {
|
||||
const auto PLAYERSURFACE = (CLayerSurface*)data;
|
||||
|
||||
PLAYERSURFACE->m_pMonitor->size = Vector2D(width, height);
|
||||
PLAYERSURFACE->m_pMonitor->wantsReload = true;
|
||||
PLAYERSURFACE->m_pMonitor->configureSerial = serial;
|
||||
PLAYERSURFACE->m_pMonitor->wantsACK = true;
|
||||
PLAYERSURFACE->m_pMonitor->initialized = true;
|
||||
|
||||
Debug::log(LOG, "configure for %s", PLAYERSURFACE->m_pMonitor->name.c_str());
|
||||
}
|
||||
|
||||
void Events::handleLSClosed(void *data, zwlr_layer_surface_v1 *zwlr_layer_surface_v1) {
|
||||
const auto PLAYERSURFACE = (CLayerSurface*)data;
|
||||
|
||||
for (auto& m : g_pHyprpaper->m_vMonitors) {
|
||||
std::erase_if(m->layerSurfaces, [&](const auto& other) { return other.get() == PLAYERSURFACE; });
|
||||
if (m->pCurrentLayerSurface == PLAYERSURFACE) {
|
||||
if (m->layerSurfaces.empty()) {
|
||||
m->pCurrentLayerSurface = nullptr;
|
||||
} else {
|
||||
m->pCurrentLayerSurface = m->layerSurfaces.begin()->get();
|
||||
g_pHyprpaper->recheckMonitor(m.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Events::handleGlobal(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) {
|
||||
if (strcmp(interface, wl_compositor_interface.name) == 0) {
|
||||
g_pHyprpaper->m_sCompositor = (wl_compositor *)wl_registry_bind(registry, name, &wl_compositor_interface, 4);
|
||||
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
|
||||
g_pHyprpaper->m_sSHM = (wl_shm *)wl_registry_bind(registry, 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 = (wl_output *)wl_registry_bind(registry, name, &wl_output_interface, 4);
|
||||
wl_output_add_listener(PMONITOR->output, &Events::outputListener, PMONITOR);
|
||||
|
||||
g_pHyprpaper->m_mtTickMutex.unlock();
|
||||
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
|
||||
g_pHyprpaper->createSeat((wl_seat*)wl_registry_bind(registry, name, &wl_seat_interface, 1));
|
||||
} else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
|
||||
g_pHyprpaper->m_sLayerShell = (zwlr_layer_shell_v1*)wl_registry_bind(registry, 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_sFractionalScale = (wp_fractional_scale_manager_v1*)wl_registry_bind(registry, name, &wp_fractional_scale_manager_v1_interface, 1);
|
||||
} else if (strcmp(interface, wp_viewporter_interface.name) == 0) {
|
||||
g_pHyprpaper->m_sViewporter = (wp_viewporter*)wl_registry_bind(registry, name, &wp_viewporter_interface, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void Events::handleGlobalRemove(void *data, struct wl_registry *registry, uint32_t name) {
|
||||
for (auto& m : g_pHyprpaper->m_vMonitors) {
|
||||
if (m->wayland_name == name) {
|
||||
Debug::log(LOG, "Destroying output %s", m->name.c_str());
|
||||
g_pHyprpaper->clearWallpaperFromMonitor(m->name);
|
||||
std::erase_if(g_pHyprpaper->m_vMonitors, [&](const auto& other) { return other->wayland_name == name; });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Events::handlePreferredScale(void *data, wp_fractional_scale_v1* fractionalScaleInfo, uint32_t scale) {
|
||||
const double SCALE = scale / 120.0;
|
||||
|
||||
CLayerSurface *const pLS = (CLayerSurface*)data;
|
||||
|
||||
Debug::log(LOG, "handlePreferredScale: %.2lf for %lx", SCALE, pLS);
|
||||
|
||||
if (pLS->fScale != SCALE) {
|
||||
pLS->fScale = SCALE;
|
||||
std::lock_guard<std::mutex> lg(g_pHyprpaper->m_mtTickMutex);
|
||||
g_pHyprpaper->tick(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
#pragma once
|
||||
|
||||
#include "../defines.hpp"
|
||||
|
||||
namespace Events {
|
||||
void geometry(void *data, wl_output *output, 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);
|
||||
|
||||
void mode(void *data, wl_output *output, uint32_t flags, int32_t width, int32_t height, int32_t refresh);
|
||||
|
||||
void done(void *data, wl_output *wl_output);
|
||||
|
||||
void scale(void *data, wl_output *wl_output, int32_t scale);
|
||||
|
||||
void name(void *data, wl_output *wl_output, const char *name);
|
||||
|
||||
void description(void *data, wl_output *wl_output, const char *description);
|
||||
|
||||
void ls_configure(void *data, zwlr_layer_surface_v1 *surface, uint32_t serial, uint32_t width, uint32_t height);
|
||||
|
||||
void handleLSClosed(void *data, zwlr_layer_surface_v1 *zwlr_layer_surface_v1);
|
||||
|
||||
void handleGlobal(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version);
|
||||
|
||||
void handleGlobalRemove(void *data, struct wl_registry *registry, uint32_t name);
|
||||
|
||||
void handleCapabilities(void *data, wl_seat *wl_seat, uint32_t capabilities);
|
||||
|
||||
void handlePointerMotion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y);
|
||||
|
||||
void handlePointerButton(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t button_state);
|
||||
|
||||
void handlePointerAxis(void *data, wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value);
|
||||
|
||||
void handlePointerEnter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y);
|
||||
|
||||
void handlePointerLeave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface);
|
||||
|
||||
void handlePreferredScale(void *data, wp_fractional_scale_v1 *wp_fractional_scale_v1, uint32_t scale);
|
||||
|
||||
inline const wl_output_listener outputListener = {.geometry = geometry, .mode = mode, .done = done, .scale = scale, .name = name, .description = description};
|
||||
|
||||
inline const zwlr_layer_surface_v1_listener layersurfaceListener = { .configure = ls_configure, .closed = handleLSClosed };
|
||||
|
||||
inline const struct wl_registry_listener registryListener = { .global = handleGlobal, .global_remove = handleGlobalRemove };
|
||||
|
||||
inline const wl_pointer_listener pointerListener = { .enter = handlePointerEnter, .leave = handlePointerLeave, .motion = handlePointerMotion, .button = handlePointerButton, .axis = handlePointerAxis };
|
||||
|
||||
inline const wl_seat_listener seatListener = { .capabilities = handleCapabilities };
|
||||
|
||||
inline const wp_fractional_scale_v1_listener scaleListener = { .preferred_scale = handlePreferredScale };
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
#include "Jpeg.hpp"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
cairo_surface_t* JPEG::createSurfaceFromJPEG(const std::string& path) {
|
||||
|
||||
if (!std::filesystem::exists(path)) {
|
||||
Debug::log(ERR, "createSurfaceFromJPEG: file doesn't exist??");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__) {
|
||||
Debug::log(CRIT, "tried to load a jpeg on a big endian system! ping vaxry he is lazy.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void* imageRawData;
|
||||
|
||||
struct stat fileInfo = {};
|
||||
|
||||
const auto FD = open(path.c_str(), O_RDONLY);
|
||||
|
||||
fstat(FD, &fileInfo);
|
||||
|
||||
imageRawData = malloc(fileInfo.st_size);
|
||||
|
||||
read(FD, imageRawData, fileInfo.st_size);
|
||||
|
||||
close(FD);
|
||||
|
||||
// now the JPEG is in the memory
|
||||
|
||||
jpeg_decompress_struct decompressStruct = {};
|
||||
jpeg_error_mgr errorManager = {};
|
||||
|
||||
decompressStruct.err = jpeg_std_error(&errorManager);
|
||||
jpeg_create_decompress(&decompressStruct);
|
||||
jpeg_mem_src(&decompressStruct, (const unsigned char*)imageRawData, fileInfo.st_size);
|
||||
jpeg_read_header(&decompressStruct, true);
|
||||
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
decompressStruct.out_color_space = JCS_EXT_BGRA;
|
||||
#else
|
||||
decompressStruct.out_color_space = JCS_EXT_ARGB;
|
||||
#endif
|
||||
|
||||
// decompress
|
||||
jpeg_start_decompress(&decompressStruct);
|
||||
|
||||
auto cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, decompressStruct.output_width, decompressStruct.output_height);
|
||||
|
||||
if (cairo_surface_status(cairoSurface) != CAIRO_STATUS_SUCCESS) {
|
||||
Debug::log(ERR, "createSurfaceFromJPEG: Cairo Failed (?)");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
const auto CAIRODATA = cairo_image_surface_get_data(cairoSurface);
|
||||
const auto CAIROSTRIDE = cairo_image_surface_get_stride(cairoSurface);
|
||||
JSAMPROW rowRead;
|
||||
|
||||
while (decompressStruct.output_scanline < decompressStruct.output_height) {
|
||||
const auto PROW = CAIRODATA + (decompressStruct.output_scanline * CAIROSTRIDE);
|
||||
rowRead = PROW;
|
||||
jpeg_read_scanlines(&decompressStruct, &rowRead, 1);
|
||||
}
|
||||
|
||||
cairo_surface_mark_dirty(cairoSurface);
|
||||
cairo_surface_set_mime_data(cairoSurface, CAIRO_MIME_TYPE_JPEG, (const unsigned char*)imageRawData, fileInfo.st_size, free, imageRawData);
|
||||
jpeg_finish_decompress(&decompressStruct);
|
||||
jpeg_destroy_decompress(&decompressStruct);
|
||||
|
||||
return cairoSurface;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include "../defines.hpp"
|
||||
#include <jpeglib.h>
|
||||
|
||||
namespace JPEG {
|
||||
cairo_surface_t* createSurfaceFromJPEG(const std::string&);
|
||||
};
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
#include "MiscFunctions.hpp"
|
||||
#include <array>
|
||||
#include "../debug/Log.hpp"
|
||||
#include <memory>
|
||||
|
||||
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) {
|
||||
std::array<char, 128> buffer;
|
||||
std::string result;
|
||||
const std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
|
||||
if (!pipe) {
|
||||
Debug::log(ERR, "execAndGet: failed in pipe");
|
||||
return "";
|
||||
}
|
||||
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
|
||||
result += buffer.data();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include "Vector2D.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*);
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include "../defines.hpp"
|
||||
#include "PoolBuffer.hpp"
|
||||
#include "../render/LayerSurface.hpp"
|
||||
|
||||
struct SMonitor {
|
||||
std::string name = "";
|
||||
std::string description = "";
|
||||
wl_output* output = nullptr;
|
||||
uint32_t wayland_name = 0;
|
||||
Vector2D size;
|
||||
int scale;
|
||||
|
||||
bool readyForLS = false;
|
||||
bool hasATarget = 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;
|
||||
};
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include "../defines.hpp"
|
||||
|
||||
class CWallpaperTarget;
|
||||
|
||||
struct SPoolBuffer {
|
||||
wl_buffer* 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;
|
||||
};
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
#include "Vector2D.hpp"
|
||||
|
||||
Vector2D::Vector2D(double xx, double yy) {
|
||||
x = xx;
|
||||
y = yy;
|
||||
}
|
||||
|
||||
Vector2D::Vector2D() { x = 0; y = 0; }
|
||||
Vector2D::~Vector2D() = default;
|
||||
|
||||
double Vector2D::normalize() {
|
||||
// get max abs
|
||||
const auto max = abs(x) > abs(y) ? abs(x) : abs(y);
|
||||
|
||||
x /= max;
|
||||
y /= max;
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
Vector2D Vector2D::floor() const {
|
||||
return {static_cast<int>(x), static_cast<int>(y)};
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
|
||||
class Vector2D {
|
||||
public:
|
||||
Vector2D(double, double);
|
||||
Vector2D();
|
||||
~Vector2D();
|
||||
|
||||
double x = 0;
|
||||
double y = 0;
|
||||
|
||||
// returns the scale
|
||||
double normalize();
|
||||
|
||||
Vector2D operator+(const Vector2D a) const {
|
||||
return Vector2D(this->x + a.x, this->y + a.y);
|
||||
}
|
||||
Vector2D operator-(const Vector2D a) const {
|
||||
return Vector2D(this->x - a.x, this->y - a.y);
|
||||
}
|
||||
Vector2D operator*(const float a) const {
|
||||
return Vector2D(this->x * a, this->y * a);
|
||||
}
|
||||
Vector2D operator/(const float a) const {
|
||||
return Vector2D(this->x / a, this->y / a);
|
||||
}
|
||||
|
||||
bool operator==(const Vector2D& a) const {
|
||||
return a.x == x && a.y == y;
|
||||
}
|
||||
|
||||
bool operator!=(const Vector2D& a) const {
|
||||
return a.x != x || a.y != y;
|
||||
}
|
||||
|
||||
Vector2D floor() const;
|
||||
};
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
#include "Webp.hpp"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <webp/decode.h>
|
||||
|
||||
cairo_surface_t* WEBP::createSurfaceFromWEBP(const std::string& path) {
|
||||
|
||||
if (!std::filesystem::exists(path)) {
|
||||
Debug::log(ERR, "createSurfaceFromWEBP: file doesn't exist??");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void* imageRawData;
|
||||
|
||||
struct stat fileInfo = {};
|
||||
|
||||
const auto FD = open(path.c_str(), O_RDONLY);
|
||||
|
||||
fstat(FD, &fileInfo);
|
||||
|
||||
imageRawData = malloc(fileInfo.st_size);
|
||||
|
||||
read(FD, imageRawData, fileInfo.st_size);
|
||||
|
||||
close(FD);
|
||||
|
||||
// now the WebP is in the memory
|
||||
|
||||
WebPDecoderConfig config;
|
||||
if (!WebPInitDecoderConfig(&config)) {
|
||||
Debug::log(CRIT, "WebPInitDecoderConfig Failed");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (WebPGetFeatures((const unsigned char*)imageRawData, fileInfo.st_size, &config.input) != VP8_STATUS_OK) {
|
||||
Debug::log(ERR, "createSurfaceFromWEBP: file is not webp format");
|
||||
free(imageRawData);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
const auto HEIGHT = config.input.height;
|
||||
const auto WIDTH = config.input.width;
|
||||
|
||||
auto cairoSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, WIDTH, HEIGHT);
|
||||
if (cairo_surface_status(cairoSurface) != CAIRO_STATUS_SUCCESS) {
|
||||
Debug::log(CRIT, "createSurfaceFromWEBP: Cairo Failed (?)");
|
||||
cairo_surface_destroy(cairoSurface);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
config.output.colorspace = MODE_bgrA;
|
||||
#else
|
||||
config.output.colorspace = MODE_Argb;
|
||||
#endif
|
||||
|
||||
const auto CAIRODATA = cairo_image_surface_get_data(cairoSurface);
|
||||
const auto CAIROSTRIDE = cairo_image_surface_get_stride(cairoSurface);
|
||||
|
||||
config.options.no_fancy_upsampling = 1;
|
||||
config.output.u.RGBA.rgba = CAIRODATA;
|
||||
config.output.u.RGBA.stride = CAIROSTRIDE;
|
||||
config.output.u.RGBA.size = CAIROSTRIDE * HEIGHT;
|
||||
config.output.is_external_memory = 1;
|
||||
config.output.width = WIDTH;
|
||||
config.output.height = HEIGHT;
|
||||
|
||||
if (WebPDecode((const unsigned char*)imageRawData, fileInfo.st_size, &config) != VP8_STATUS_OK) {
|
||||
Debug::log(CRIT, "createSurfaceFromWEBP: WebP Decode Failed (?)");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
cairo_surface_mark_dirty(cairoSurface);
|
||||
cairo_surface_set_mime_data(cairoSurface, CAIRO_MIME_TYPE_PNG, (const unsigned char*)imageRawData, fileInfo.st_size, free, imageRawData);
|
||||
|
||||
WebPFreeDecBuffer(&config.output);
|
||||
|
||||
return cairoSurface;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "../defines.hpp"
|
||||
|
||||
namespace WEBP {
|
||||
cairo_surface_t* createSurfaceFromWEBP(const std::string&);
|
||||
};
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include <pthread.h>
|
||||
#include <cmath>
|
||||
|
||||
#define class _class
|
||||
#define namespace _namespace
|
||||
#define static
|
||||
|
||||
extern "C" {
|
||||
#include "wlr-layer-shell-unstable-v1-protocol.h"
|
||||
#include "xdg-shell-protocol.h"
|
||||
#include "fractional-scale-v1-protocol.h"
|
||||
#include "viewporter-protocol.h"
|
||||
#include <wayland-client.h>
|
||||
#include <wayland-cursor.h>
|
||||
}
|
||||
|
||||
#undef class
|
||||
#undef namespace
|
||||
#undef static
|
||||
|
||||
#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>
|
||||
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
#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>
|
||||
|
||||
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");
|
||||
|
||||
std::string socketPath = HISenv ? "/tmp/hypr/" + std::string(HISenv) + "/.hyprpaper.sock" : "/tmp/hypr/.hyprpaper.sock";
|
||||
|
||||
if (!HISenv) {
|
||||
mkdir("/tmp/hypr", S_IRWXU | S_IRWXG);
|
||||
}
|
||||
|
||||
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 %s (fd: %i)", socketPath.c_str(), 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 %i", 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;
|
||||
|
||||
// now we can work on the copy
|
||||
|
||||
if (copy == "")
|
||||
return false;
|
||||
|
||||
Debug::log(LOG, "Received a request: %s", copy.c_str());
|
||||
|
||||
// parse
|
||||
if (copy.find("wallpaper") == 0 || copy.find("preload") == 0 || copy.find("unload") == 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();
|
||||
m_bReplyReady = true;
|
||||
m_bRequestReady = false;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
m_szReply = "invalid command";
|
||||
m_bReplyReady = true;
|
||||
m_bRequestReady = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_szReply = "ok";
|
||||
m_bReplyReady = true;
|
||||
m_bRequestReady = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
#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;
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
#include <iostream>
|
||||
#include "defines.hpp"
|
||||
#include "Hyprpaper.hpp"
|
||||
|
||||
int main(int argc, char** argv, char** envp) {
|
||||
Debug::log(LOG, "Welcome to hyprpaper!\nbuilt from commit %s (%s)", GIT_COMMIT_HASH, GIT_COMMIT_MESSAGE);
|
||||
|
||||
// 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 %s.", configPath.c_str());
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
// starts
|
||||
g_pHyprpaper = std::make_unique<CHyprpaper>();
|
||||
g_pHyprpaper->m_szExplicitConfigPath = configPath;
|
||||
g_pHyprpaper->m_bNoFractionalScale = noFractional;
|
||||
g_pHyprpaper->init();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
#include "LayerSurface.hpp"
|
||||
|
||||
#include "../Hyprpaper.hpp"
|
||||
|
||||
CLayerSurface::CLayerSurface(SMonitor* pMonitor) {
|
||||
m_pMonitor = pMonitor;
|
||||
|
||||
pSurface = wl_compositor_create_surface(g_pHyprpaper->m_sCompositor);
|
||||
pCursorSurface = wl_compositor_create_surface(g_pHyprpaper->m_sCompositor);
|
||||
|
||||
if (!pSurface) {
|
||||
Debug::log(CRIT, "The compositor did not allow hyprpaper a surface!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
const auto PINPUTREGION = wl_compositor_create_region(g_pHyprpaper->m_sCompositor);
|
||||
|
||||
if (!PINPUTREGION) {
|
||||
Debug::log(CRIT, "The compositor did not allow hyprpaper a region!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
wl_surface_set_input_region(pSurface, PINPUTREGION);
|
||||
|
||||
pLayerSurface = zwlr_layer_shell_v1_get_layer_surface(g_pHyprpaper->m_sLayerShell, pSurface, pMonitor->output, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND, "hyprpaper");
|
||||
|
||||
if (!pLayerSurface) {
|
||||
Debug::log(CRIT, "The compositor did not allow hyprpaper a layersurface!");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
zwlr_layer_surface_v1_set_size(pLayerSurface, 0, 0);
|
||||
zwlr_layer_surface_v1_set_anchor(pLayerSurface, ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
|
||||
zwlr_layer_surface_v1_set_exclusive_zone(pLayerSurface, -1);
|
||||
zwlr_layer_surface_v1_add_listener(pLayerSurface, &Events::layersurfaceListener, this);
|
||||
wl_surface_commit(pSurface);
|
||||
|
||||
wl_region_destroy(PINPUTREGION);
|
||||
|
||||
// fractional scale, if supported by the compositor
|
||||
if (g_pHyprpaper->m_sFractionalScale) {
|
||||
pFractionalScaleInfo = wp_fractional_scale_manager_v1_get_fractional_scale(g_pHyprpaper->m_sFractionalScale, pSurface);
|
||||
wp_fractional_scale_v1_add_listener(pFractionalScaleInfo, &Events::scaleListener, this);
|
||||
pViewport = wp_viewporter_get_viewport(g_pHyprpaper->m_sViewporter, pSurface);
|
||||
wl_surface_commit(pSurface);
|
||||
}
|
||||
|
||||
wl_display_flush(g_pHyprpaper->m_sDisplay);
|
||||
}
|
||||
|
||||
CLayerSurface::~CLayerSurface() {
|
||||
|
||||
if (pCursorTheme)
|
||||
wl_cursor_theme_destroy(pCursorTheme);
|
||||
|
||||
if (g_pHyprpaper->m_sFractionalScale && pFractionalScaleInfo) {
|
||||
wp_fractional_scale_v1_destroy(pFractionalScaleInfo);
|
||||
|
||||
wp_viewport_destroy(pViewport);
|
||||
}
|
||||
|
||||
zwlr_layer_surface_v1_destroy(pLayerSurface);
|
||||
wl_surface_destroy(pSurface);
|
||||
|
||||
wl_display_flush(g_pHyprpaper->m_sDisplay);
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include "../defines.hpp"
|
||||
|
||||
struct SMonitor;
|
||||
|
||||
class CLayerSurface {
|
||||
public:
|
||||
explicit CLayerSurface(SMonitor*);
|
||||
~CLayerSurface();
|
||||
|
||||
SMonitor* m_pMonitor = nullptr;
|
||||
|
||||
zwlr_layer_surface_v1* pLayerSurface = nullptr;
|
||||
wl_surface* pSurface = nullptr;
|
||||
|
||||
wl_cursor_theme* pCursorTheme = nullptr;
|
||||
wl_cursor_image* pCursorImg = nullptr;
|
||||
wl_surface* pCursorSurface = nullptr;
|
||||
|
||||
wp_fractional_scale_v1* pFractionalScaleInfo = nullptr;
|
||||
wp_viewport* pViewport = nullptr;
|
||||
double fScale = 1.0;
|
||||
};
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
#include "WallpaperTarget.hpp"
|
||||
|
||||
#include <magic.h>
|
||||
|
||||
CWallpaperTarget::~CWallpaperTarget() {
|
||||
cairo_surface_destroy(m_pCairoSurface);
|
||||
}
|
||||
|
||||
void CWallpaperTarget::create(const std::string& path) {
|
||||
m_szPath = path;
|
||||
|
||||
const auto BEGINLOAD = std::chrono::system_clock::now();
|
||||
|
||||
cairo_surface_t* CAIROSURFACE = nullptr;
|
||||
const auto len = path.length();
|
||||
if (path.find(".png") == len - 4 || path.find(".PNG") == len - 4) {
|
||||
CAIROSURFACE = cairo_image_surface_create_from_png(path.c_str());
|
||||
} else if (path.find(".jpg") == len - 4 || path.find(".JPG") == len - 4 || path.find(".jpeg") == len - 5 || path.find(".JPEG") == len - 5) {
|
||||
CAIROSURFACE = JPEG::createSurfaceFromJPEG(path);
|
||||
m_bHasAlpha = false;
|
||||
} else if (path.find(".webp") == len - 5 || path.find(".WEBP") == len - 5) {
|
||||
CAIROSURFACE = WEBP::createSurfaceFromWEBP(path);
|
||||
} else {
|
||||
// magic is slow, so only use it when no recognized extension is found
|
||||
auto handle = magic_open(MAGIC_NONE|MAGIC_COMPRESS);
|
||||
magic_load(handle, nullptr);
|
||||
|
||||
const auto type_str = std::string(magic_file(handle, path.c_str()));
|
||||
const auto first_word = type_str.substr(0, type_str.find(" "));
|
||||
|
||||
if (first_word == "PNG") {
|
||||
CAIROSURFACE = cairo_image_surface_create_from_png(path.c_str());
|
||||
} else if (first_word == "JPEG") {
|
||||
CAIROSURFACE = JPEG::createSurfaceFromJPEG(path);
|
||||
m_bHasAlpha = false;
|
||||
} else {
|
||||
Debug::log(CRIT, "unrecognized image %s", path.c_str());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (cairo_surface_status(CAIROSURFACE) != CAIRO_STATUS_SUCCESS) {
|
||||
Debug::log(CRIT, "Failed to read image %s because of:\n%s", path.c_str(), cairo_status_to_string(cairo_surface_status(CAIROSURFACE)));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
m_vSize = { cairo_image_surface_get_width(CAIROSURFACE), cairo_image_surface_get_height(CAIROSURFACE) };
|
||||
|
||||
const auto MS = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - BEGINLOAD).count() / 1000.f;
|
||||
|
||||
Debug::log(LOG, "Preloaded target %s in %.2fms -> Pixel size: [%i, %i]", path.c_str(), MS, (int)m_vSize.x, (int)m_vSize.y);
|
||||
|
||||
m_pCairoSurface = CAIROSURFACE;
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include "../defines.hpp"
|
||||
#include "../helpers/Jpeg.hpp"
|
||||
#include "../helpers/Webp.hpp"
|
||||
|
||||
class CWallpaperTarget {
|
||||
public:
|
||||
|
||||
~CWallpaperTarget();
|
||||
|
||||
void create(const std::string& path);
|
||||
|
||||
std::string m_szPath;
|
||||
|
||||
Vector2D m_vSize;
|
||||
|
||||
bool m_bHasAlpha = true;
|
||||
|
||||
cairo_surface_t* m_pCairoSurface;
|
||||
};
|
||||
Loading…
Reference in New Issue