From 6d955e33db86dcb47d30f3f5a92c53d0364d050f Mon Sep 17 00:00:00 2001 From: mcgi5sr2 Date: Mon, 2 Mar 2026 16:37:11 +0000 Subject: [PATCH 1/9] cmake: add FFmpeg dependencies for video background support Add libavcodec, libavformat, libavutil, and libswscale to the pkg_check_modules dependency list to support native video decoding in the background widget. --- CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ee63ca..0d055a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,7 +88,11 @@ pkg_check_modules( gbm hyprutils>=0.11.0 sdbus-c++>=2.0.0 - hyprgraphics>=0.1.6) + hyprgraphics>=0.1.6 + libavcodec + libavformat + libavutil + libswscale) find_library(PAM_FOUND NAMES pam libpam) if(PAM_FOUND) set(PAM_LIB ${PAM_FOUND}) From 0345d4ccf8e7880b5bfec577ab9009f8e37ba730 Mon Sep 17 00:00:00 2001 From: mcgi5sr2 Date: Mon, 2 Mar 2026 16:37:41 +0000 Subject: [PATCH 2/9] background: add video playback support via FFmpeg Extend the background widget to natively decode and display video files (mp4, mkv, webm, avi, mov, gif, and more) using FFmpeg, with no external tools required. - Detection is extension-based; existing image paths are unaffected - A background decode thread paces frames to their PTS timestamps and publishes them to the render thread via an O(1) mutex-swap, avoiding any memcpy per frame - sws_getCachedContext handles codecs that only report their pixel format after the first decoded frame - Videos loop seamlessly via av_seek_frame at EOF - blur_passes works on video frames the same as on images - No new config keys: path = /path/to/video.mp4 is sufficient --- src/renderer/widgets/Background.cpp | 252 +++++++++++++++++++++++++++- src/renderer/widgets/Background.hpp | 46 +++++ 2 files changed, 295 insertions(+), 3 deletions(-) diff --git a/src/renderer/widgets/Background.cpp b/src/renderer/widgets/Background.cpp index 1754be5..d446640 100644 --- a/src/renderer/widgets/Background.cpp +++ b/src/renderer/widgets/Background.cpp @@ -7,9 +7,11 @@ #include "../../helpers/MiscFunctions.hpp" #include "../../core/AnimationManager.hpp" #include "../../config/ConfigManager.hpp" +#include #include #include #include +#include #include CBackground::CBackground() { @@ -40,6 +42,173 @@ static std::string runAndGetPath(const std::string& reloadCommand) { return path; } +// ── Video support ───────────────────────────────────────────────────────────── + +SVideoState::~SVideoState() { + running = false; + if (decodeThread.joinable()) + decodeThread.join(); + if (swsCtx) sws_freeContext(swsCtx); + if (codecCtx) avcodec_free_context(&codecCtx); + if (formatCtx) avformat_close_input(&formatCtx); +} + +bool CBackground::isVideoFile(const std::string& path) { + static const std::unordered_set VIDEO_EXT = { + ".mp4", ".mkv", ".webm", ".avi", ".mov", ".m4v", + ".flv", ".wmv", ".ts", ".m2ts", ".gif" + }; + auto ext = std::filesystem::path(path).extension().string(); + std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + return VIDEO_EXT.count(ext) > 0; +} + +bool CBackground::openVideo(const std::string& path) { + auto& v = *m_video; + + if (avformat_open_input(&v.formatCtx, path.c_str(), nullptr, nullptr) < 0) { + Debug::log(ERR, "CBackground: avformat_open_input failed for {}", path); + return false; + } + if (avformat_find_stream_info(v.formatCtx, nullptr) < 0) { + Debug::log(ERR, "CBackground: avformat_find_stream_info failed for {}", path); + return false; + } + + const AVCodec* codec = nullptr; + v.streamIdx = av_find_best_stream(v.formatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0); + if (v.streamIdx < 0 || !codec) { + Debug::log(ERR, "CBackground: no video stream found in {}", path); + return false; + } + + v.codecCtx = avcodec_alloc_context3(codec); + if (!v.codecCtx) { + Debug::log(ERR, "CBackground: avcodec_alloc_context3 failed"); + return false; + } + + avcodec_parameters_to_context(v.codecCtx, v.formatCtx->streams[v.streamIdx]->codecpar); + + if (avcodec_open2(v.codecCtx, codec, nullptr) < 0) { + Debug::log(ERR, "CBackground: avcodec_open2 failed for {}", path); + return false; + } + + v.frameW = v.codecCtx->width; + v.frameH = v.codecCtx->height; + v.timeBase = av_q2d(v.formatCtx->streams[v.streamIdx]->time_base); + + // swsCtx is created lazily per-frame via sws_getCachedContext so that + // we handle codecs where pix_fmt is only known after the first decode. + v.frameData.resize(4 * v.frameW * v.frameH); + Debug::log(LOG, "CBackground: opened video {} ({}x{}, timebase={:.6f})", + path, v.frameW, v.frameH, v.timeBase); + return true; +} + +void CBackground::startVideoThread() { + auto& v = *m_video; + v.running = true; + v.startTime = std::chrono::steady_clock::now(); + + v.decodeThread = std::thread([&v]() { + AVPacket* pkt = av_packet_alloc(); + AVFrame* frame = av_frame_alloc(); + std::vector tmpBuf(4 * v.frameW * v.frameH); + + while (v.running) { + int ret = av_read_frame(v.formatCtx, pkt); + + if (ret == AVERROR_EOF) { + // Flush decoder's internal buffer + avcodec_send_packet(v.codecCtx, nullptr); + while (avcodec_receive_frame(v.codecCtx, frame) == 0) + av_frame_unref(frame); + + // Loop: seek back to beginning + av_seek_frame(v.formatCtx, v.streamIdx, 0, AVSEEK_FLAG_BACKWARD); + avcodec_flush_buffers(v.codecCtx); + v.startTime = std::chrono::steady_clock::now(); + continue; + } + + if (ret < 0) + break; // unrecoverable error + + if (pkt->stream_index != v.streamIdx) { + av_packet_unref(pkt); + continue; + } + + if (avcodec_send_packet(v.codecCtx, pkt) < 0) { + av_packet_unref(pkt); + continue; + } + av_packet_unref(pkt); + + while (avcodec_receive_frame(v.codecCtx, frame) == 0) { + if (!v.running) + break; + + // Lazily create / update SwsContext to match the frame's actual + // pixel format (some codecs only report it after the first frame). + v.swsCtx = sws_getCachedContext(v.swsCtx, + frame->width, frame->height, (AVPixelFormat)frame->format, + v.frameW, v.frameH, AV_PIX_FMT_RGBA, + SWS_BILINEAR, nullptr, nullptr, nullptr); + if (!v.swsCtx) { + av_frame_unref(frame); + continue; + } + + // sws_scale requires 4-element pointer/stride arrays even for + // packed formats — passing a 1-element array causes UB reads. + uint8_t* dst[4] = {tmpBuf.data(), nullptr, nullptr, nullptr}; + int stride[4] = {4 * v.frameW, 0, 0, 0}; + sws_scale(v.swsCtx, + (const uint8_t* const*)frame->data, frame->linesize, + 0, frame->height, dst, stride); + + // Publish frame via O(1) swap (no memcpy) + { + std::lock_guard lock(v.frameMutex); + std::swap(v.frameData, tmpBuf); + v.hasNewFrame = true; + } + // tmpBuf now holds old frame data and will be overwritten next iteration + + // PTS-based frame pacing + if (frame->pts != AV_NOPTS_VALUE) { + double pts_sec = frame->pts * v.timeBase; + auto target = v.startTime + + std::chrono::duration_cast( + std::chrono::duration(pts_sec)); + auto now = std::chrono::steady_clock::now(); + // Safety cap: never sleep > 5 s (guards against bogus PTS values) + auto maxTarget = now + std::chrono::seconds(5); + if (target > now && target < maxTarget) + std::this_thread::sleep_until(target); + } + + av_frame_unref(frame); + } + } + + av_packet_free(&pkt); + av_frame_free(&frame); + }); +} + +void CBackground::stopVideo() { + m_video.reset(); // ~SVideoState(): sets running=false, joins thread, frees ffmpeg + m_videoTexture.destroyTexture(); + m_uploadBuffer.clear(); + m_isVideo = false; +} + +// ── End video support ───────────────────────────────────────────────────────── + void CBackground::configure(const std::unordered_map& props, const SP& pOutput) { reset(); @@ -86,10 +255,29 @@ void CBackground::configure(const std::unordered_map& pro Debug::log(ERR, "No screencopy support! path=screenshot won't work. Falling back to background color."); resourceID = 0; } - } else if (!path.empty()) - resourceID = g_asyncResourceManager->requestImage(path, m_imageRevision, nullptr); + } else if (!path.empty()) { + if (isVideoFile(path)) { + m_isVideo = true; + m_video = makeUnique(); + if (!openVideo(path)) { + Debug::log(ERR, "CBackground: failed to open '{}' as video, falling back to image", path); + m_video.reset(); + m_isVideo = false; + resourceID = g_asyncResourceManager->requestImage(path, m_imageRevision, nullptr); + } else { + // Pre-size the upload buffer so it's never empty when the main + // thread swaps it with frameData. An empty vector has data()==null + // which would propagate back to tmpBuf in the decode thread and + // cause "bad dst image pointers" on the third sws_scale call. + m_uploadBuffer.resize(4 * m_video->frameW * m_video->frameH); + startVideoThread(); + } + } else { + resourceID = g_asyncResourceManager->requestImage(path, m_imageRevision, nullptr); + } + } - if (!reloadCommand.empty() && reloadTime > -1) { + if (!reloadCommand.empty() && reloadTime > -1 && !m_isVideo) { try { if (!isScreenshot) modificationTime = std::filesystem::last_write_time(absolutePath(path, "")); @@ -100,6 +288,9 @@ void CBackground::configure(const std::unordered_map& pro } void CBackground::reset() { + if (m_isVideo) + stopVideo(); + if (reloadTimer) { reloadTimer->cancel(); reloadTimer.reset(); @@ -225,6 +416,61 @@ void CBackground::renderToFB(const CTexture& tex, CFramebuffer& fb, int passes, } bool CBackground::draw(const SRenderData& data) { + // ── Video background fast path ──────────────────────────────────────── + if (m_isVideo && m_video) { + // Grab the latest decoded frame from the decode thread (O(1) swap) + { + std::lock_guard lock(m_video->frameMutex); + if (m_video->hasNewFrame) { + std::swap(m_uploadBuffer, m_video->frameData); + m_video->hasNewFrame = false; + } + } + + // Upload frame to GL texture + if (!m_uploadBuffer.empty()) { + if (!m_videoTexture.m_bAllocated) { + // First frame: allocate the GL texture + m_videoTexture.allocate(); + glBindTexture(GL_TEXTURE_2D, m_videoTexture.m_iTexID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, + m_video->frameW, m_video->frameH, 0, + GL_RGBA, GL_UNSIGNED_BYTE, m_uploadBuffer.data()); + glBindTexture(GL_TEXTURE_2D, 0); + m_videoTexture.m_vSize = {(double)m_video->frameW, (double)m_video->frameH}; + m_videoTexture.m_iType = TEXTURE_RGBA; + m_videoTexture.m_iTarget = GL_TEXTURE_2D; + } else { + glBindTexture(GL_TEXTURE_2D, m_videoTexture.m_iTexID); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, + m_video->frameW, m_video->frameH, + GL_RGBA, GL_UNSIGNED_BYTE, m_uploadBuffer.data()); + glBindTexture(GL_TEXTURE_2D, 0); + } + + // Re-render blur FB if requested (GPU-side only, called every new frame) + if (blurPasses > 0) + renderToFB(m_videoTexture, *blurredFB, blurPasses); + } + + // Render + if (!m_videoTexture.m_bAllocated) { + renderRect(color); // solid colour until first frame is ready + return true; + } + + const CTexture& TEX = (blurPasses > 0 && blurredFB->isAllocated()) + ? blurredFB->m_cTex : m_videoTexture; + const auto TEXBOX = getScaledBoxForTextureSize(TEX.m_vSize, viewport); + g_pRenderer->renderTexture(TEXBOX, TEX, data.opacity); + return true; // always request the next compositor frame + } + // ── End video path ──────────────────────────────────────────────────── + updatePrimaryAsset(); updatePendingAsset(); updateScAsset(); diff --git a/src/renderer/widgets/Background.hpp b/src/renderer/widgets/Background.hpp index d3516c5..5f378d1 100644 --- a/src/renderer/widgets/Background.hpp +++ b/src/renderer/widgets/Background.hpp @@ -11,10 +11,44 @@ #include #include #include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +} struct SPreloadedAsset; class COutput; +struct SVideoState { + AVFormatContext* formatCtx = nullptr; + AVCodecContext* codecCtx = nullptr; + SwsContext* swsCtx = nullptr; + int streamIdx = -1; + int frameW = 0; + int frameH = 0; + double timeBase = 0.0; // seconds per PTS unit + + // Frame double-buffer (main thread swaps with its m_uploadBuffer) + std::mutex frameMutex; + std::vector frameData; // latest RGBA frame + bool hasNewFrame = false; + + std::chrono::steady_clock::time_point startTime; + + std::thread decodeThread; + std::atomic running{false}; + + ~SVideoState(); // defined in .cpp so destructor sees complete ffmpeg types +}; + class CBackground : public IWidget { public: CBackground(); @@ -46,6 +80,12 @@ class CBackground : public IWidget { private: AWP m_self; + // Video background support + static bool isVideoFile(const std::string& path); + bool openVideo(const std::string& path); + void startVideoThread(); + void stopVideo(); + // if needed UP blurredFB; UP pendingBlurredFB; @@ -82,4 +122,10 @@ class CBackground : public IWidget { ASP reloadTimer; std::filesystem::file_time_type modificationTime; size_t m_imageRevision = 0; + + // Video playback state + bool m_isVideo = false; + UP m_video; + CTexture m_videoTexture; + std::vector m_uploadBuffer; // O(1) swap target for decoded frames }; From 8fbe83b3b22eb1c9c0e03278dd09089d2783c459 Mon Sep 17 00:00:00 2001 From: mcgi5sr2 Date: Sat, 4 Apr 2026 19:56:17 +0100 Subject: [PATCH 3/9] refactor: extract video decode logic into CVideoBackend Move FFmpeg decode thread, frame buffering, and lifecycle management out of CBackground into a dedicated CVideoBackend class. Background.hpp no longer includes FFmpeg headers. CBackground interacts with video purely via open(), swapFrame(), and frameW/H(). --- src/renderer/VideoBackend.cpp | 177 ++++++++++++++++++++++ src/renderer/VideoBackend.hpp | 62 ++++++++ src/renderer/widgets/Background.cpp | 220 +++------------------------- src/renderer/widgets/Background.hpp | 47 +----- 4 files changed, 261 insertions(+), 245 deletions(-) create mode 100644 src/renderer/VideoBackend.cpp create mode 100644 src/renderer/VideoBackend.hpp diff --git a/src/renderer/VideoBackend.cpp b/src/renderer/VideoBackend.cpp new file mode 100644 index 0000000..8caac99 --- /dev/null +++ b/src/renderer/VideoBackend.cpp @@ -0,0 +1,177 @@ +#include "VideoBackend.hpp" +#include "../helpers/Log.hpp" +#include + +CVideoBackend::~CVideoBackend() { + stop(); +} + +bool CVideoBackend::isVideoFile(const std::string& path) { + static const std::unordered_set VIDEO_EXT = { + ".mp4", ".mkv", ".webm", ".avi", ".mov", ".m4v", + ".flv", ".wmv", ".ts", ".m2ts", ".gif" + }; + auto ext = std::filesystem::path(path).extension().string(); + std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + return VIDEO_EXT.count(ext) > 0; +} + +bool CVideoBackend::open(const std::string& path) { + if (avformat_open_input(&m_formatCtx, path.c_str(), nullptr, nullptr) < 0) { + Debug::log(ERR, "CVideoBackend: avformat_open_input failed for {}", path); + return false; + } + if (avformat_find_stream_info(m_formatCtx, nullptr) < 0) { + Debug::log(ERR, "CVideoBackend: avformat_find_stream_info failed for {}", path); + return false; + } + + const AVCodec* codec = nullptr; + m_streamIdx = av_find_best_stream(m_formatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0); + if (m_streamIdx < 0 || !codec) { + Debug::log(ERR, "CVideoBackend: no video stream found in {}", path); + return false; + } + + m_codecCtx = avcodec_alloc_context3(codec); + if (!m_codecCtx) { + Debug::log(ERR, "CVideoBackend: avcodec_alloc_context3 failed"); + return false; + } + + avcodec_parameters_to_context(m_codecCtx, m_formatCtx->streams[m_streamIdx]->codecpar); + + if (avcodec_open2(m_codecCtx, codec, nullptr) < 0) { + Debug::log(ERR, "CVideoBackend: avcodec_open2 failed for {}", path); + return false; + } + + m_frameW = m_codecCtx->width; + m_frameH = m_codecCtx->height; + m_timeBase = av_q2d(m_formatCtx->streams[m_streamIdx]->time_base); + + // swsCtx is created lazily per-frame via sws_getCachedContext so that + // we handle codecs where pix_fmt is only known after the first decode. + m_frameData.resize(4 * m_frameW * m_frameH); + + Debug::log(LOG, "CVideoBackend: opened {} ({}x{}, timebase={:.6f})", + path, m_frameW, m_frameH, m_timeBase); + + startDecodeThread(); + return true; +} + +void CVideoBackend::stop() { + m_running = false; + if (m_decodeThread.joinable()) + m_decodeThread.join(); + if (m_swsCtx) sws_freeContext(m_swsCtx); + if (m_codecCtx) avcodec_free_context(&m_codecCtx); + if (m_formatCtx) avformat_close_input(&m_formatCtx); + m_swsCtx = nullptr; + m_codecCtx = nullptr; + m_formatCtx = nullptr; +} + +bool CVideoBackend::swapFrame(std::vector& buf) { + std::lock_guard lock(m_frameMutex); + if (!m_hasNewFrame) + return false; + std::swap(m_frameData, buf); + m_hasNewFrame = false; + return true; +} + +void CVideoBackend::startDecodeThread() { + m_running = true; + m_startTime = std::chrono::steady_clock::now(); + + m_decodeThread = std::thread([this]() { + AVPacket* pkt = av_packet_alloc(); + AVFrame* frame = av_frame_alloc(); + // Pre-size the tmp buffer so it's never empty when swapping with m_frameData. + // An empty vector has data()==null which causes "bad dst image pointers" in sws_scale. + std::vector tmpBuf(4 * m_frameW * m_frameH); + + while (m_running) { + int ret = av_read_frame(m_formatCtx, pkt); + + if (ret == AVERROR_EOF) { + // Flush decoder's internal buffer + avcodec_send_packet(m_codecCtx, nullptr); + while (avcodec_receive_frame(m_codecCtx, frame) == 0) + av_frame_unref(frame); + + // Loop: seek back to beginning + av_seek_frame(m_formatCtx, m_streamIdx, 0, AVSEEK_FLAG_BACKWARD); + avcodec_flush_buffers(m_codecCtx); + m_startTime = std::chrono::steady_clock::now(); + continue; + } + + if (ret < 0) + break; // unrecoverable error + + if (pkt->stream_index != m_streamIdx) { + av_packet_unref(pkt); + continue; + } + + if (avcodec_send_packet(m_codecCtx, pkt) < 0) { + av_packet_unref(pkt); + continue; + } + av_packet_unref(pkt); + + while (avcodec_receive_frame(m_codecCtx, frame) == 0) { + if (!m_running) + break; + + // Lazily create/update SwsContext to match the frame's actual pixel + // format (some codecs only report it after the first frame). + m_swsCtx = sws_getCachedContext(m_swsCtx, + frame->width, frame->height, (AVPixelFormat)frame->format, + m_frameW, m_frameH, AV_PIX_FMT_RGBA, + SWS_BILINEAR, nullptr, nullptr, nullptr); + if (!m_swsCtx) { + av_frame_unref(frame); + continue; + } + + // sws_scale requires 4-element pointer/stride arrays even for + // packed formats — passing a 1-element array causes UB reads. + uint8_t* dst[4] = {tmpBuf.data(), nullptr, nullptr, nullptr}; + int stride[4] = {4 * m_frameW, 0, 0, 0}; + sws_scale(m_swsCtx, + (const uint8_t* const*)frame->data, frame->linesize, + 0, frame->height, dst, stride); + + // Publish frame via O(1) swap (no memcpy) + { + std::lock_guard lock(m_frameMutex); + std::swap(m_frameData, tmpBuf); + m_hasNewFrame = true; + } + // tmpBuf now holds old frame data — overwritten next iteration + + // PTS-based frame pacing + if (frame->pts != AV_NOPTS_VALUE) { + double pts_sec = frame->pts * m_timeBase; + auto target = m_startTime + + std::chrono::duration_cast( + std::chrono::duration(pts_sec)); + auto now = std::chrono::steady_clock::now(); + // Safety cap: never sleep > 5s (guards against bogus PTS values) + auto maxTarget = now + std::chrono::seconds(5); + if (target > now && target < maxTarget) + std::this_thread::sleep_until(target); + } + + av_frame_unref(frame); + } + } + + av_packet_free(&pkt); + av_frame_free(&frame); + }); +} diff --git a/src/renderer/VideoBackend.hpp b/src/renderer/VideoBackend.hpp new file mode 100644 index 0000000..ba90520 --- /dev/null +++ b/src/renderer/VideoBackend.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +} + +// Handles FFmpeg video decoding on a background thread. +// CBackground owns one of these when path is a video file. +// All GL upload and rendering stays in CBackground. +class CVideoBackend { + public: + ~CVideoBackend(); + + // Returns true if the file extension is a recognised video format. + static bool isVideoFile(const std::string& path); + + // Open the file and start the decode thread. Returns false on failure. + bool open(const std::string& path); + + // Stop the decode thread and release all FFmpeg resources. + void stop(); + + // Swap the latest decoded RGBA frame into buf (O(1), no memcpy). + // Returns true if a new frame was available and buf was updated. + bool swapFrame(std::vector& buf); + + int frameW() const { return m_frameW; } + int frameH() const { return m_frameH; } + bool isRunning() const { return m_running; } + + private: + void startDecodeThread(); + + AVFormatContext* m_formatCtx = nullptr; + AVCodecContext* m_codecCtx = nullptr; + SwsContext* m_swsCtx = nullptr; + int m_streamIdx = -1; + int m_frameW = 0; + int m_frameH = 0; + double m_timeBase = 0.0; + + std::mutex m_frameMutex; + std::vector m_frameData; + bool m_hasNewFrame = false; + + std::chrono::steady_clock::time_point m_startTime; + + std::thread m_decodeThread; + std::atomic m_running{false}; +}; diff --git a/src/renderer/widgets/Background.cpp b/src/renderer/widgets/Background.cpp index d446640..df1ffcd 100644 --- a/src/renderer/widgets/Background.cpp +++ b/src/renderer/widgets/Background.cpp @@ -7,11 +7,8 @@ #include "../../helpers/MiscFunctions.hpp" #include "../../core/AnimationManager.hpp" #include "../../config/ConfigManager.hpp" -#include -#include #include #include -#include #include CBackground::CBackground() { @@ -42,172 +39,6 @@ static std::string runAndGetPath(const std::string& reloadCommand) { return path; } -// ── Video support ───────────────────────────────────────────────────────────── - -SVideoState::~SVideoState() { - running = false; - if (decodeThread.joinable()) - decodeThread.join(); - if (swsCtx) sws_freeContext(swsCtx); - if (codecCtx) avcodec_free_context(&codecCtx); - if (formatCtx) avformat_close_input(&formatCtx); -} - -bool CBackground::isVideoFile(const std::string& path) { - static const std::unordered_set VIDEO_EXT = { - ".mp4", ".mkv", ".webm", ".avi", ".mov", ".m4v", - ".flv", ".wmv", ".ts", ".m2ts", ".gif" - }; - auto ext = std::filesystem::path(path).extension().string(); - std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); - return VIDEO_EXT.count(ext) > 0; -} - -bool CBackground::openVideo(const std::string& path) { - auto& v = *m_video; - - if (avformat_open_input(&v.formatCtx, path.c_str(), nullptr, nullptr) < 0) { - Debug::log(ERR, "CBackground: avformat_open_input failed for {}", path); - return false; - } - if (avformat_find_stream_info(v.formatCtx, nullptr) < 0) { - Debug::log(ERR, "CBackground: avformat_find_stream_info failed for {}", path); - return false; - } - - const AVCodec* codec = nullptr; - v.streamIdx = av_find_best_stream(v.formatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0); - if (v.streamIdx < 0 || !codec) { - Debug::log(ERR, "CBackground: no video stream found in {}", path); - return false; - } - - v.codecCtx = avcodec_alloc_context3(codec); - if (!v.codecCtx) { - Debug::log(ERR, "CBackground: avcodec_alloc_context3 failed"); - return false; - } - - avcodec_parameters_to_context(v.codecCtx, v.formatCtx->streams[v.streamIdx]->codecpar); - - if (avcodec_open2(v.codecCtx, codec, nullptr) < 0) { - Debug::log(ERR, "CBackground: avcodec_open2 failed for {}", path); - return false; - } - - v.frameW = v.codecCtx->width; - v.frameH = v.codecCtx->height; - v.timeBase = av_q2d(v.formatCtx->streams[v.streamIdx]->time_base); - - // swsCtx is created lazily per-frame via sws_getCachedContext so that - // we handle codecs where pix_fmt is only known after the first decode. - v.frameData.resize(4 * v.frameW * v.frameH); - Debug::log(LOG, "CBackground: opened video {} ({}x{}, timebase={:.6f})", - path, v.frameW, v.frameH, v.timeBase); - return true; -} - -void CBackground::startVideoThread() { - auto& v = *m_video; - v.running = true; - v.startTime = std::chrono::steady_clock::now(); - - v.decodeThread = std::thread([&v]() { - AVPacket* pkt = av_packet_alloc(); - AVFrame* frame = av_frame_alloc(); - std::vector tmpBuf(4 * v.frameW * v.frameH); - - while (v.running) { - int ret = av_read_frame(v.formatCtx, pkt); - - if (ret == AVERROR_EOF) { - // Flush decoder's internal buffer - avcodec_send_packet(v.codecCtx, nullptr); - while (avcodec_receive_frame(v.codecCtx, frame) == 0) - av_frame_unref(frame); - - // Loop: seek back to beginning - av_seek_frame(v.formatCtx, v.streamIdx, 0, AVSEEK_FLAG_BACKWARD); - avcodec_flush_buffers(v.codecCtx); - v.startTime = std::chrono::steady_clock::now(); - continue; - } - - if (ret < 0) - break; // unrecoverable error - - if (pkt->stream_index != v.streamIdx) { - av_packet_unref(pkt); - continue; - } - - if (avcodec_send_packet(v.codecCtx, pkt) < 0) { - av_packet_unref(pkt); - continue; - } - av_packet_unref(pkt); - - while (avcodec_receive_frame(v.codecCtx, frame) == 0) { - if (!v.running) - break; - - // Lazily create / update SwsContext to match the frame's actual - // pixel format (some codecs only report it after the first frame). - v.swsCtx = sws_getCachedContext(v.swsCtx, - frame->width, frame->height, (AVPixelFormat)frame->format, - v.frameW, v.frameH, AV_PIX_FMT_RGBA, - SWS_BILINEAR, nullptr, nullptr, nullptr); - if (!v.swsCtx) { - av_frame_unref(frame); - continue; - } - - // sws_scale requires 4-element pointer/stride arrays even for - // packed formats — passing a 1-element array causes UB reads. - uint8_t* dst[4] = {tmpBuf.data(), nullptr, nullptr, nullptr}; - int stride[4] = {4 * v.frameW, 0, 0, 0}; - sws_scale(v.swsCtx, - (const uint8_t* const*)frame->data, frame->linesize, - 0, frame->height, dst, stride); - - // Publish frame via O(1) swap (no memcpy) - { - std::lock_guard lock(v.frameMutex); - std::swap(v.frameData, tmpBuf); - v.hasNewFrame = true; - } - // tmpBuf now holds old frame data and will be overwritten next iteration - - // PTS-based frame pacing - if (frame->pts != AV_NOPTS_VALUE) { - double pts_sec = frame->pts * v.timeBase; - auto target = v.startTime + - std::chrono::duration_cast( - std::chrono::duration(pts_sec)); - auto now = std::chrono::steady_clock::now(); - // Safety cap: never sleep > 5 s (guards against bogus PTS values) - auto maxTarget = now + std::chrono::seconds(5); - if (target > now && target < maxTarget) - std::this_thread::sleep_until(target); - } - - av_frame_unref(frame); - } - } - - av_packet_free(&pkt); - av_frame_free(&frame); - }); -} - -void CBackground::stopVideo() { - m_video.reset(); // ~SVideoState(): sets running=false, joins thread, frees ffmpeg - m_videoTexture.destroyTexture(); - m_uploadBuffer.clear(); - m_isVideo = false; -} - -// ── End video support ───────────────────────────────────────────────────────── void CBackground::configure(const std::unordered_map& props, const SP& pOutput) { reset(); @@ -256,28 +87,21 @@ void CBackground::configure(const std::unordered_map& pro resourceID = 0; } } else if (!path.empty()) { - if (isVideoFile(path)) { - m_isVideo = true; - m_video = makeUnique(); - if (!openVideo(path)) { + if (CVideoBackend::isVideoFile(path)) { + m_videoBackend = makeUnique(); + if (!m_videoBackend->open(path)) { Debug::log(ERR, "CBackground: failed to open '{}' as video, falling back to image", path); - m_video.reset(); - m_isVideo = false; + m_videoBackend.reset(); resourceID = g_asyncResourceManager->requestImage(path, m_imageRevision, nullptr); } else { - // Pre-size the upload buffer so it's never empty when the main - // thread swaps it with frameData. An empty vector has data()==null - // which would propagate back to tmpBuf in the decode thread and - // cause "bad dst image pointers" on the third sws_scale call. - m_uploadBuffer.resize(4 * m_video->frameW * m_video->frameH); - startVideoThread(); + m_uploadBuffer.resize(4 * m_videoBackend->frameW() * m_videoBackend->frameH()); } } else { resourceID = g_asyncResourceManager->requestImage(path, m_imageRevision, nullptr); } } - if (!reloadCommand.empty() && reloadTime > -1 && !m_isVideo) { + if (!reloadCommand.empty() && reloadTime > -1 && !m_videoBackend) { try { if (!isScreenshot) modificationTime = std::filesystem::last_write_time(absolutePath(path, "")); @@ -288,8 +112,11 @@ void CBackground::configure(const std::unordered_map& pro } void CBackground::reset() { - if (m_isVideo) - stopVideo(); + if (m_videoBackend) { + m_videoBackend.reset(); + m_videoTexture.destroyTexture(); + m_uploadBuffer.clear(); + } if (reloadTimer) { reloadTimer->cancel(); @@ -417,18 +244,11 @@ void CBackground::renderToFB(const CTexture& tex, CFramebuffer& fb, int passes, bool CBackground::draw(const SRenderData& data) { // ── Video background fast path ──────────────────────────────────────── - if (m_isVideo && m_video) { - // Grab the latest decoded frame from the decode thread (O(1) swap) - { - std::lock_guard lock(m_video->frameMutex); - if (m_video->hasNewFrame) { - std::swap(m_uploadBuffer, m_video->frameData); - m_video->hasNewFrame = false; - } - } + if (m_videoBackend) { + if (m_videoBackend->swapFrame(m_uploadBuffer)) { + const int W = m_videoBackend->frameW(); + const int H = m_videoBackend->frameH(); - // Upload frame to GL texture - if (!m_uploadBuffer.empty()) { if (!m_videoTexture.m_bAllocated) { // First frame: allocate the GL texture m_videoTexture.allocate(); @@ -437,27 +257,23 @@ bool CBackground::draw(const SRenderData& data) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, - m_video->frameW, m_video->frameH, 0, + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, W, H, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_uploadBuffer.data()); glBindTexture(GL_TEXTURE_2D, 0); - m_videoTexture.m_vSize = {(double)m_video->frameW, (double)m_video->frameH}; + m_videoTexture.m_vSize = {(double)W, (double)H}; m_videoTexture.m_iType = TEXTURE_RGBA; m_videoTexture.m_iTarget = GL_TEXTURE_2D; } else { glBindTexture(GL_TEXTURE_2D, m_videoTexture.m_iTexID); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, - m_video->frameW, m_video->frameH, + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, W, H, GL_RGBA, GL_UNSIGNED_BYTE, m_uploadBuffer.data()); glBindTexture(GL_TEXTURE_2D, 0); } - // Re-render blur FB if requested (GPU-side only, called every new frame) if (blurPasses > 0) renderToFB(m_videoTexture, *blurredFB, blurPasses); } - // Render if (!m_videoTexture.m_bAllocated) { renderRect(color); // solid colour until first frame is ready return true; diff --git a/src/renderer/widgets/Background.hpp b/src/renderer/widgets/Background.hpp index 5f378d1..0d5a9c5 100644 --- a/src/renderer/widgets/Background.hpp +++ b/src/renderer/widgets/Background.hpp @@ -6,49 +6,17 @@ #include "../../helpers/Color.hpp" #include "../../core/Timer.hpp" #include "../Framebuffer.hpp" +#include "../VideoBackend.hpp" #include #include #include #include #include -#include -#include -#include #include -#include - -extern "C" { -#include -#include -#include -#include -} struct SPreloadedAsset; class COutput; -struct SVideoState { - AVFormatContext* formatCtx = nullptr; - AVCodecContext* codecCtx = nullptr; - SwsContext* swsCtx = nullptr; - int streamIdx = -1; - int frameW = 0; - int frameH = 0; - double timeBase = 0.0; // seconds per PTS unit - - // Frame double-buffer (main thread swaps with its m_uploadBuffer) - std::mutex frameMutex; - std::vector frameData; // latest RGBA frame - bool hasNewFrame = false; - - std::chrono::steady_clock::time_point startTime; - - std::thread decodeThread; - std::atomic running{false}; - - ~SVideoState(); // defined in .cpp so destructor sees complete ffmpeg types -}; - class CBackground : public IWidget { public: CBackground(); @@ -80,12 +48,6 @@ class CBackground : public IWidget { private: AWP m_self; - // Video background support - static bool isVideoFile(const std::string& path); - bool openVideo(const std::string& path); - void startVideoThread(); - void stopVideo(); - // if needed UP blurredFB; UP pendingBlurredFB; @@ -123,9 +85,8 @@ class CBackground : public IWidget { std::filesystem::file_time_type modificationTime; size_t m_imageRevision = 0; - // Video playback state - bool m_isVideo = false; - UP m_video; + // Video playback + UP m_videoBackend; CTexture m_videoTexture; - std::vector m_uploadBuffer; // O(1) swap target for decoded frames + std::vector m_uploadBuffer; }; From 92c701f76b1576acaffdcd30cc1cbfae66220bc3 Mon Sep 17 00:00:00 2001 From: mcgi5sr2 Date: Sun, 5 Apr 2026 11:07:49 +0100 Subject: [PATCH 4/9] nix: add ffmpeg to buildInputs for video background support --- nix/default.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nix/default.nix b/nix/default.nix index 58bc834..8a80871 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -4,6 +4,7 @@ cmake, pkg-config, cairo, + ffmpeg, libdrm, libGL, libxkbcommon, @@ -37,6 +38,7 @@ stdenv.mkDerivation { buildInputs = [ cairo + ffmpeg libdrm libGL libxkbcommon From cfffe9b704ce58f28dd566b6d877dda3dada8bd8 Mon Sep 17 00:00:00 2001 From: mcgi5sr2 Date: Sun, 5 Apr 2026 11:11:31 +0100 Subject: [PATCH 5/9] nix: add ffmpeg dependency for video background support ffmpeg is included conditionally via withVideoBackend (default true), allowing packagers to opt out by passing withVideoBackend = false, which also passes -DVIDEO_BACKEND=OFF to cmake. --- nix/default.nix | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nix/default.nix b/nix/default.nix index 8a80871..774da9d 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -4,7 +4,7 @@ cmake, pkg-config, cairo, - ffmpeg, + ffmpeg ? null, libdrm, libGL, libxkbcommon, @@ -20,6 +20,7 @@ wayland, wayland-protocols, wayland-scanner, + withVideoBackend ? true, version ? "git", shortRev ? "", }: @@ -38,7 +39,6 @@ stdenv.mkDerivation { buildInputs = [ cairo - ffmpeg libdrm libGL libxkbcommon @@ -52,12 +52,12 @@ stdenv.mkDerivation { systemdLibs wayland wayland-protocols - ]; + ] ++ lib.optionals withVideoBackend [ ffmpeg ]; cmakeFlags = lib.mapAttrsToList lib.cmakeFeature { HYPRLOCK_COMMIT = shortRev; HYPRLOCK_VERSION_COMMIT = ""; # Intentionally left empty (hyprlock --version will always print the commit) - }; + } ++ lib.optional (!withVideoBackend) (lib.cmakeBool "VIDEO_BACKEND" false); meta = { homepage = "https://github.com/hyprwm/hyprlock"; From af51b7c4772572e325f720f210a2ece2aa707b6e Mon Sep 17 00:00:00 2001 From: mcgi5sr2 Date: Sun, 5 Apr 2026 11:12:00 +0100 Subject: [PATCH 6/9] cmake: make video backend optional via VIDEO_BACKEND flag Add VIDEO_BACKEND cmake option (default ON) that controls whether FFmpeg is required and video playback is compiled in. When OFF, VideoBackend.cpp is excluded from the build and all video code paths in Background are gated by HYPRLOCK_HAS_VIDEO, allowing a no-FFmpeg build for packagers who want to ship without the dependency. --- CMakeLists.txt | 24 +++++++++++++++++++----- src/renderer/widgets/Background.cpp | 15 +++++++++++++-- src/renderer/widgets/Background.hpp | 4 ++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d055a8..42f45ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -88,11 +88,15 @@ pkg_check_modules( gbm hyprutils>=0.11.0 sdbus-c++>=2.0.0 - hyprgraphics>=0.1.6 - libavcodec - libavformat - libavutil - libswscale) + hyprgraphics>=0.1.6) + +option(VIDEO_BACKEND "Enable video background support via FFmpeg" ON) +if(VIDEO_BACKEND) + pkg_check_modules(ffmpeg REQUIRED IMPORTED_TARGET libavcodec libavformat libavutil libswscale) + message(STATUS "Video backend: enabled") +else() + message(STATUS "Video backend: disabled") +endif() find_library(PAM_FOUND NAMES pam libpam) if(PAM_FOUND) set(PAM_LIB ${PAM_FOUND}) @@ -108,10 +112,20 @@ endif() message(STATUS "Found pam at ${PAM_LIB}") file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp") + +if(NOT VIDEO_BACKEND) + list(FILTER SRCFILES EXCLUDE REGEX ".*/VideoBackend\\.cpp$") +endif() + add_executable(hyprlock ${SRCFILES}) target_link_libraries(hyprlock PRIVATE ${PAM_LIB} rt Threads::Threads PkgConfig::deps OpenGL::EGL OpenGL::GLES3) +if(VIDEO_BACKEND) + target_compile_definitions(hyprlock PRIVATE HYPRLOCK_HAS_VIDEO) + target_link_libraries(hyprlock PRIVATE PkgConfig::ffmpeg) +endif() + # protocols pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir) message(STATUS "Found wayland-protocols at ${WAYLAND_PROTOCOLS_DIR}") diff --git a/src/renderer/widgets/Background.cpp b/src/renderer/widgets/Background.cpp index df1ffcd..e3015c1 100644 --- a/src/renderer/widgets/Background.cpp +++ b/src/renderer/widgets/Background.cpp @@ -87,6 +87,7 @@ void CBackground::configure(const std::unordered_map& pro resourceID = 0; } } else if (!path.empty()) { +#ifdef HYPRLOCK_HAS_VIDEO if (CVideoBackend::isVideoFile(path)) { m_videoBackend = makeUnique(); if (!m_videoBackend->open(path)) { @@ -96,12 +97,18 @@ void CBackground::configure(const std::unordered_map& pro } else { m_uploadBuffer.resize(4 * m_videoBackend->frameW() * m_videoBackend->frameH()); } - } else { + } else +#endif + { resourceID = g_asyncResourceManager->requestImage(path, m_imageRevision, nullptr); } } - if (!reloadCommand.empty() && reloadTime > -1 && !m_videoBackend) { + if (!reloadCommand.empty() && reloadTime > -1 +#ifdef HYPRLOCK_HAS_VIDEO + && !m_videoBackend +#endif + ) { try { if (!isScreenshot) modificationTime = std::filesystem::last_write_time(absolutePath(path, "")); @@ -112,11 +119,13 @@ void CBackground::configure(const std::unordered_map& pro } void CBackground::reset() { +#ifdef HYPRLOCK_HAS_VIDEO if (m_videoBackend) { m_videoBackend.reset(); m_videoTexture.destroyTexture(); m_uploadBuffer.clear(); } +#endif if (reloadTimer) { reloadTimer->cancel(); @@ -243,6 +252,7 @@ void CBackground::renderToFB(const CTexture& tex, CFramebuffer& fb, int passes, } bool CBackground::draw(const SRenderData& data) { +#ifdef HYPRLOCK_HAS_VIDEO // ── Video background fast path ──────────────────────────────────────── if (m_videoBackend) { if (m_videoBackend->swapFrame(m_uploadBuffer)) { @@ -286,6 +296,7 @@ bool CBackground::draw(const SRenderData& data) { return true; // always request the next compositor frame } // ── End video path ──────────────────────────────────────────────────── +#endif updatePrimaryAsset(); updatePendingAsset(); diff --git a/src/renderer/widgets/Background.hpp b/src/renderer/widgets/Background.hpp index 0d5a9c5..f757b3e 100644 --- a/src/renderer/widgets/Background.hpp +++ b/src/renderer/widgets/Background.hpp @@ -6,7 +6,9 @@ #include "../../helpers/Color.hpp" #include "../../core/Timer.hpp" #include "../Framebuffer.hpp" +#ifdef HYPRLOCK_HAS_VIDEO #include "../VideoBackend.hpp" +#endif #include #include #include @@ -86,7 +88,9 @@ class CBackground : public IWidget { size_t m_imageRevision = 0; // Video playback +#ifdef HYPRLOCK_HAS_VIDEO UP m_videoBackend; CTexture m_videoTexture; std::vector m_uploadBuffer; +#endif }; From 623a6438ab472f06ba403e18f86cd6a1aec9e161 Mon Sep 17 00:00:00 2001 From: mcgi5sr2 Date: Sun, 5 Apr 2026 12:10:41 +0100 Subject: [PATCH 7/9] renderer: use Log::log API in VideoBackend Upstream hyprutils uses Log::log(Log::ERR, ...) rather than the older Debug::log(ERR, ...) pattern. Update VideoBackend.cpp to match. --- src/renderer/VideoBackend.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/renderer/VideoBackend.cpp b/src/renderer/VideoBackend.cpp index 8caac99..9c32069 100644 --- a/src/renderer/VideoBackend.cpp +++ b/src/renderer/VideoBackend.cpp @@ -18,31 +18,31 @@ bool CVideoBackend::isVideoFile(const std::string& path) { bool CVideoBackend::open(const std::string& path) { if (avformat_open_input(&m_formatCtx, path.c_str(), nullptr, nullptr) < 0) { - Debug::log(ERR, "CVideoBackend: avformat_open_input failed for {}", path); + Log::log(Log::ERR, "CVideoBackend: avformat_open_input failed for {}", path); return false; } if (avformat_find_stream_info(m_formatCtx, nullptr) < 0) { - Debug::log(ERR, "CVideoBackend: avformat_find_stream_info failed for {}", path); + Log::log(Log::ERR, "CVideoBackend: avformat_find_stream_info failed for {}", path); return false; } const AVCodec* codec = nullptr; m_streamIdx = av_find_best_stream(m_formatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0); if (m_streamIdx < 0 || !codec) { - Debug::log(ERR, "CVideoBackend: no video stream found in {}", path); + Log::log(Log::ERR, "CVideoBackend: no video stream found in {}", path); return false; } m_codecCtx = avcodec_alloc_context3(codec); if (!m_codecCtx) { - Debug::log(ERR, "CVideoBackend: avcodec_alloc_context3 failed"); + Log::log(Log::ERR, "CVideoBackend: avcodec_alloc_context3 failed"); return false; } avcodec_parameters_to_context(m_codecCtx, m_formatCtx->streams[m_streamIdx]->codecpar); if (avcodec_open2(m_codecCtx, codec, nullptr) < 0) { - Debug::log(ERR, "CVideoBackend: avcodec_open2 failed for {}", path); + Log::log(Log::ERR, "CVideoBackend: avcodec_open2 failed for {}", path); return false; } @@ -54,7 +54,7 @@ bool CVideoBackend::open(const std::string& path) { // we handle codecs where pix_fmt is only known after the first decode. m_frameData.resize(4 * m_frameW * m_frameH); - Debug::log(LOG, "CVideoBackend: opened {} ({}x{}, timebase={:.6f})", + Log::log(Log::LOG, "CVideoBackend: opened {} ({}x{}, timebase={:.6f})", path, m_frameW, m_frameH, m_timeBase); startDecodeThread(); From a6a7fdbe34066c2e6a7462192d235078dc5f3438 Mon Sep 17 00:00:00 2001 From: mcgi5sr2 Date: Sun, 5 Apr 2026 12:14:27 +0100 Subject: [PATCH 8/9] renderer: update video backend to use Log::logger API Follow upstream change (4e4f03c) which migrated from Debug::log to Log::logger->log via Hyprutils::CLI. --- src/renderer/VideoBackend.cpp | 12 ++++++------ src/renderer/widgets/Background.cpp | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/renderer/VideoBackend.cpp b/src/renderer/VideoBackend.cpp index 9c32069..d45af60 100644 --- a/src/renderer/VideoBackend.cpp +++ b/src/renderer/VideoBackend.cpp @@ -18,31 +18,31 @@ bool CVideoBackend::isVideoFile(const std::string& path) { bool CVideoBackend::open(const std::string& path) { if (avformat_open_input(&m_formatCtx, path.c_str(), nullptr, nullptr) < 0) { - Log::log(Log::ERR, "CVideoBackend: avformat_open_input failed for {}", path); + Log::logger->log(Log::ERR, "CVideoBackend: avformat_open_input failed for {}", path); return false; } if (avformat_find_stream_info(m_formatCtx, nullptr) < 0) { - Log::log(Log::ERR, "CVideoBackend: avformat_find_stream_info failed for {}", path); + Log::logger->log(Log::ERR, "CVideoBackend: avformat_find_stream_info failed for {}", path); return false; } const AVCodec* codec = nullptr; m_streamIdx = av_find_best_stream(m_formatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0); if (m_streamIdx < 0 || !codec) { - Log::log(Log::ERR, "CVideoBackend: no video stream found in {}", path); + Log::logger->log(Log::ERR, "CVideoBackend: no video stream found in {}", path); return false; } m_codecCtx = avcodec_alloc_context3(codec); if (!m_codecCtx) { - Log::log(Log::ERR, "CVideoBackend: avcodec_alloc_context3 failed"); + Log::logger->log(Log::ERR, "CVideoBackend: avcodec_alloc_context3 failed"); return false; } avcodec_parameters_to_context(m_codecCtx, m_formatCtx->streams[m_streamIdx]->codecpar); if (avcodec_open2(m_codecCtx, codec, nullptr) < 0) { - Log::log(Log::ERR, "CVideoBackend: avcodec_open2 failed for {}", path); + Log::logger->log(Log::ERR, "CVideoBackend: avcodec_open2 failed for {}", path); return false; } @@ -54,7 +54,7 @@ bool CVideoBackend::open(const std::string& path) { // we handle codecs where pix_fmt is only known after the first decode. m_frameData.resize(4 * m_frameW * m_frameH); - Log::log(Log::LOG, "CVideoBackend: opened {} ({}x{}, timebase={:.6f})", + Log::logger->log(Log::LOG, "CVideoBackend: opened {} ({}x{}, timebase={:.6f})", path, m_frameW, m_frameH, m_timeBase); startDecodeThread(); diff --git a/src/renderer/widgets/Background.cpp b/src/renderer/widgets/Background.cpp index 407ac90..fe693ad 100644 --- a/src/renderer/widgets/Background.cpp +++ b/src/renderer/widgets/Background.cpp @@ -91,7 +91,7 @@ void CBackground::configure(const std::unordered_map& pro if (CVideoBackend::isVideoFile(path)) { m_videoBackend = makeUnique(); if (!m_videoBackend->open(path)) { - Debug::log(ERR, "CBackground: failed to open '{}' as video, falling back to image", path); + Log::logger->log(Log::ERR, "CBackground: failed to open '{}' as video, falling back to image", path); m_videoBackend.reset(); resourceID = g_asyncResourceManager->requestImage(path, m_imageRevision, nullptr); } else { From a48820ac25db4c862090fd0cf424fc05e5f34a93 Mon Sep 17 00:00:00 2001 From: mcgi5sr2 Date: Sun, 5 Apr 2026 12:21:39 +0100 Subject: [PATCH 9/9] renderer: fix Log::LOG -> Log::INFO in VideoBackend Log::LOG does not exist in the Hyprutils::CLI log level mapping. Use Log::INFO instead. --- src/renderer/VideoBackend.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/VideoBackend.cpp b/src/renderer/VideoBackend.cpp index d45af60..511215f 100644 --- a/src/renderer/VideoBackend.cpp +++ b/src/renderer/VideoBackend.cpp @@ -54,7 +54,7 @@ bool CVideoBackend::open(const std::string& path) { // we handle codecs where pix_fmt is only known after the first decode. m_frameData.resize(4 * m_frameW * m_frameH); - Log::logger->log(Log::LOG, "CVideoBackend: opened {} ({}x{}, timebase={:.6f})", + Log::logger->log(Log::INFO, "CVideoBackend: opened {} ({}x{}, timebase={:.6f})", path, m_frameW, m_frameH, m_timeBase); startDecodeThread();