feat: Added per-monitor wallpaper rotation support

Added support for rotating wallpapers per monitor using config syntax: monitor_name,wallpaper_path,rotation_degrees
This commit is contained in:
Lucas Christiansson 2025-09-16 18:28:59 +02:00
parent bcb1ffa322
commit 6b1b348b9c
4 changed files with 97 additions and 21 deletions

View file

@ -1,4 +1,5 @@
#include "Hyprpaper.hpp"
#include "render/WallpaperTransform.hpp"
#include <filesystem>
#include <fstream>
#include <signal.h>
@ -520,6 +521,7 @@ SPoolBuffer* CHyprpaper::getPoolBuffer(SMonitor* pMonitor, CWallpaperTarget* pWa
}
void CHyprpaper::renderWallpaperForMonitor(SMonitor* pMonitor) {
#include "render/WallpaperTransform.hpp"
static auto PRENDERSPLASH = Hyprlang::CSimpleConfigValue<Hyprlang::INT>(g_pConfigManager->config.get(), "splash");
static auto PSPLASHOFFSET = Hyprlang::CSimpleConfigValue<Hyprlang::FLOAT>(g_pConfigManager->config.get(), "splash_offset");
@ -564,30 +566,21 @@ void CHyprpaper::renderWallpaperForMonitor(SMonitor* pMonitor) {
cairo_fill(PCAIRO);
cairo_surface_flush(PBUFFER->surface);
// get scale
// we always do cover
double scale;
Vector2D origin;
const bool LOWASPECTRATIO = pMonitor->size.x / pMonitor->size.y > PWALLPAPERTARGET->m_vSize.x / PWALLPAPERTARGET->m_vSize.y;
if ((CONTAIN && !LOWASPECTRATIO) || (!CONTAIN && LOWASPECTRATIO)) {
scale = DIMENSIONS.x / PWALLPAPERTARGET->m_vSize.x;
origin.y = -(PWALLPAPERTARGET->m_vSize.y * scale - DIMENSIONS.y) / 2.0 / scale;
} else {
scale = DIMENSIONS.y / PWALLPAPERTARGET->m_vSize.y;
origin.x = -(PWALLPAPERTARGET->m_vSize.x * scale - DIMENSIONS.x) / 2.0 / scale;
}
Debug::log(LOG, "Image data for {}: {} at [{:.2f}, {:.2f}], scale: {:.2f} (original image size: [{}, {}])", pMonitor->name, PWALLPAPERTARGET->m_szPath, origin.x, origin.y,
scale, (int)PWALLPAPERTARGET->m_vSize.x, (int)PWALLPAPERTARGET->m_vSize.y);
// Apply wallpaper transform (handles scaling, rotation, and centering)
applyWallpaperTransform(
PCAIRO,
Vector2D(PWALLPAPERTARGET->m_vSize.x, PWALLPAPERTARGET->m_vSize.y),
DIMENSIONS,
pMonitor->wallpaperRotation
);
if (TILE) {
cairo_pattern_t* pattern = cairo_pattern_create_for_surface(PWALLPAPERTARGET->m_pCairoSurface->cairo());
cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
cairo_set_source(PCAIRO, pattern);
} else {
cairo_scale(PCAIRO, scale, scale);
cairo_set_source_surface(PCAIRO, PWALLPAPERTARGET->m_pCairoSurface->cairo(), origin.x, origin.y);
// No additional scaling/positioning - the transform handles it all
cairo_set_source_surface(PCAIRO, PWALLPAPERTARGET->m_pCairoSurface->cairo(), 0, 0);
}
cairo_paint(PCAIRO);
@ -601,6 +594,15 @@ void CHyprpaper::renderWallpaperForMonitor(SMonitor* pMonitor) {
cairo_select_font_face(PCAIRO, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
// Calculate scale for splash text (same logic as transform function)
int rot = pMonitor->wallpaperRotation % 360;
bool swapWH = (rot == 90 || rot == 270);
double imgW = swapWH ? PWALLPAPERTARGET->m_vSize.y : PWALLPAPERTARGET->m_vSize.x;
double imgH = swapWH ? PWALLPAPERTARGET->m_vSize.x : PWALLPAPERTARGET->m_vSize.y;
double scaleX = DIMENSIONS.x / imgW;
double scaleY = DIMENSIONS.y / imgH;
double scale = std::max(scaleX, scaleY);
const auto FONTSIZE = (int)(DIMENSIONS.y / 76.0 / scale);
cairo_set_font_size(PCAIRO, FONTSIZE);

View file

@ -8,13 +8,29 @@ static Hyprlang::CParseResult handleWallpaper(const char* C, const char* V) {
const std::string VALUE = V;
Hyprlang::CParseResult result;
if (VALUE.find_first_of(',') == std::string::npos) {
// Support syntax: monitor_name,wallpaper_path[,rotation]
size_t firstComma = VALUE.find_first_of(',');
if (firstComma == 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));
size_t secondComma = VALUE.find_first_of(',', firstComma + 1);
auto MONITOR = VALUE.substr(0, firstComma);
std::string WALLPAPER;
int rotation = 0;
if (secondComma == std::string::npos) {
WALLPAPER = g_pConfigManager->trimPath(VALUE.substr(firstComma + 1));
} else {
WALLPAPER = g_pConfigManager->trimPath(VALUE.substr(firstComma + 1, secondComma - firstComma - 1));
std::string rotationStr = VALUE.substr(secondComma + 1);
try {
rotation = std::stoi(rotationStr);
} catch (...) {
rotation = 0;
}
}
bool contain = false;
@ -48,11 +64,20 @@ static Hyprlang::CParseResult handleWallpaper(const char* C, const char* V) {
return result;
}
g_pHyprpaper->clearWallpaperFromMonitor(MONITOR);
g_pHyprpaper->m_mMonitorActiveWallpapers[MONITOR] = WALLPAPER;
g_pHyprpaper->m_mMonitorWallpaperRenderData[MONITOR].contain = contain;
g_pHyprpaper->m_mMonitorWallpaperRenderData[MONITOR].tile = tile;
// Set wallpaper rotation for the monitor
for (auto& m : g_pHyprpaper->m_vMonitors) {
if (m->name == MONITOR) {
m->wallpaperRotation = rotation;
break;
}
}
if (MONITOR.empty()) {
for (auto& m : g_pHyprpaper->m_vMonitors) {
if (!m->hasATarget || m->wildcard) {

View file

@ -26,6 +26,9 @@ struct SMonitor {
bool wantsACK = false;
bool initialized = false;
// Wallpaper rotation in degrees (0, 90, 180, 270)
int wallpaperRotation = 0;
std::vector<std::unique_ptr<CLayerSurface>> layerSurfaces;
CLayerSurface* pCurrentLayerSurface = nullptr;

View file

@ -0,0 +1,46 @@
#pragma once
#include <cairo/cairo.h>
#include <hyprutils/math/Vector2D.hpp>
// Applies robust rotation, scaling, and centering for wallpaper rendering
// Always fills the monitor (cover mode), centers image, and handles all rotations correctly
inline void applyWallpaperTransform(cairo_t* cr, const Vector2D& imgSize, const Vector2D& monSize, int rotation) {
double imgW = imgSize.x;
double imgH = imgSize.y;
double monW = monSize.x;
double monH = monSize.y;
// Normalize rotation
int rot = rotation % 360;
if (rot < 0) rot += 360;
// For 90/270 degrees, the image dimensions are effectively swapped
bool isRotated90or270 = (rot == 90 || rot == 270);
double effectiveImgW = isRotated90or270 ? imgH : imgW;
double effectiveImgH = isRotated90or270 ? imgW : imgH;
// Calculate scale to cover the monitor (larger scale wins)
double scaleX = monW / effectiveImgW;
double scaleY = monH / effectiveImgH;
double scale = std::max(scaleX, scaleY);
// Debug output
printf("DEBUG Transform: rotation=%d, imgSize=[%.1f,%.1f], monSize=[%.1f,%.1f]\n",
rot, imgW, imgH, monW, monH);
printf("DEBUG effective=[%.1f,%.1f], scales=[%.3f,%.3f], final_scale=%.3f\n",
effectiveImgW, effectiveImgH, scaleX, scaleY, scale);
printf("DEBUG transforms: translate=[%.1f,%.1f], rotate=%.1f°, scale=%.3f, img_center=[%.1f,%.1f]\n",
monW/2.0, monH/2.0, (double)rot, scale, imgW/2.0, imgH/2.0);
// Move to center of monitor
cairo_translate(cr, monW / 2.0, monH / 2.0);
// Rotate around center
cairo_rotate(cr, rot * M_PI / 180.0);
// Scale the image
cairo_scale(cr, scale, scale);
// Move to center of image (so image center aligns with rotation center)
cairo_translate(cr, -imgW / 2.0, -imgH / 2.0);
}