feat: add vaapi path for video wallpapers on generic gpus
C++ / fmt (pull_request) Failing after 6s
C++ / build (pull_request) Failing after 10s
C++ / clang-tidy (pull_request) Failing after 11s
JS/TS / fmt (pull_request) Successful in 10s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Failing after 32s
Rust / build (pull_request) Successful in 52s
Python / verify (pull_request) Failing after 1m23s
Rust / fmt (pull_request) Successful in 51s
Rust / clippy (pull_request) Successful in 1m2s

This commit is contained in:
2026-10-27 02:15:16 +01:00
parent e655ec5bc7
commit b0cb43f75c
39 changed files with 1670 additions and 599 deletions
-41
View File
@@ -1,8 +1,3 @@
wl_proto(wlp-layer-shell wlr-layer-shell-unstable-v1 "${CMAKE_CURRENT_SOURCE_DIR}/protocols")
wl_proto(wlp-viewporter viewporter "${WAYLAND_PROTOCOLS}/stable/viewporter")
wl_proto(wlp-fractional-scale fractional-scale-v1 "${WAYLAND_PROTOCOLS}/staging/fractional-scale")
wl_proto(wlp-xdg-shell xdg-shell "${WAYLAND_PROTOCOLS}/stable/xdg-shell")
qml_module(ZShell-internal
URI ZShell.Internal
INCLUDE_PREFIX internal
@@ -13,16 +8,11 @@ qml_module(ZShell-internal
circularindicatormanager.hpp circularindicatormanager.cpp
circularbuffer.hpp circularbuffer.cpp
arcgauge.hpp arcgauge.cpp
wallpaperimage.hpp wallpaperimage.cpp
wallpaperservice.hpp wallpaperservice.cpp
wallpapersurface.hpp wallpapersurface.cpp
wallpaperobject.hpp wallpaperobject.cpp
lidwatcher.hpp lidwatcher.cpp
visualizerbars.hpp visualizerbars.cpp
linearindicatormanager.hpp linearindicatormanager.cpp
strokecanvasitem.hpp strokecanvasitem.cpp
strokecanvasrenderer.hpp strokecanvasrenderer.cpp
videoplayer.hpp videoplayer.cpp
cursor.hpp
LIBRARIES
Qt::Gui
@@ -34,39 +24,8 @@ qml_module(ZShell-internal
Qt::DBus
Qt::CanvasPainter
Qt::GuiPrivate
wlp-layer-shell
wlp-xdg-shell
wlp-viewporter
wlp-fractional-scale
PkgConfig::EGL
PkgConfig::WAYLAND_EGL
PkgConfig::GL
PkgConfig::MPV
PkgConfig::AVCODEC
PkgConfig::AVFORMAT
PkgConfig::AVUTIL
ZShell-config
)
qt_add_resources(ZShell-internal "wallpaper_shaders"
PREFIX "/"
FILES
shaders/wallpaper.vert
shaders/wallpaper.frag
shaders/wallpaper_video.frag
shaders/wallpaper_trans_bowtie.frag
shaders/wallpaper_trans_fade.frag
shaders/wallpaper_trans_linear_blur.frag
shaders/wallpaper_trans_window_slices.frag
shaders/wallpaper_trans_wipe_right.frag
shaders/wallpaper_trans_wipe_down.frag
shaders/wallpaper_trans_tiles_wave.frag
shaders/wallpaper_trans_split_slide_in_horizontal.frag
shaders/wallpaper_trans_hexagonalize.frag
shaders/wallpaper_trans_doom_melt.frag
)
if(CUDAToolkit_FOUND)
target_link_libraries(ZShell-internal PRIVATE CUDA::cuda_driver)
target_compile_definitions(ZShell-internal PRIVATE ZHAVE_VIDEO=1)
endif()
@@ -1,407 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="wlr_layer_shell_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="5">
<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.
After creating a layer_surface object and setting it up, the client
must perform an initial commit without any buffer attached.
The compositor will reply with a layer_surface.configure event.
The client must acknowledge it and is then allowed to attach a buffer
to map the surface.
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>
<!-- Version 3 additions -->
<request name="destroy" type="destructor" since="3">
<description summary="destroy the layer_shell object">
This request indicates that the client will not use the layer_shell
object any more. Objects that have been created through this instance
are not affected.
</description>
</request>
</interface>
<interface name="zwlr_layer_surface_v1" version="5">
<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 (layer, 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.
Attaching a null buffer to a layer surface unmaps it.
Unmapping a layer_surface means that the surface cannot be shown by the
compositor until it is explicitly mapped again. The layer_surface
returns to the state it had right after layer_shell.get_layer_surface.
The client can re-map the surface by performing a commit without any
buffer attached, waiting for a configure event and handling it as usual.
</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 orthogonal 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 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 one
edge or an edge and both perpendicular edges. If the surface is not
anchored, anchored to only two perpendicular edges (a corner), anchored
to only two parallel edges or anchored to all edges, a positive value
will be treated the same as zero.
A positive zone is the distance from the edge in surface-local
coordinates to consider 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 exclusive 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>
<enum name="keyboard_interactivity">
<description summary="types of keyboard interaction possible for a layer shell surface">
Types of keyboard interaction possible for layer shell surfaces. The
rationale for this is twofold: (1) some applications are not interested
in keyboard events and not allowing them to be focused can improve the
desktop experience; (2) some applications will want to take exclusive
keyboard focus.
</description>
<entry name="none" value="0">
<description summary="no keyboard focus is possible">
This value indicates that this surface is not interested in keyboard
events and the compositor should never assign it the keyboard focus.
This is the default value, set for newly created layer shell surfaces.
This is useful for e.g. desktop widgets that display information or
only have interaction with non-keyboard input devices.
</description>
</entry>
<entry name="exclusive" value="1">
<description summary="request exclusive keyboard focus">
Request exclusive keyboard focus if this surface is above the shell surface layer.
For the top and overlay layers, the seat will always give
exclusive keyboard focus to the top-most layer which has keyboard
interactivity set to exclusive. If this layer contains multiple
surfaces with keyboard interactivity set to exclusive, the compositor
determines the one receiving keyboard events in an implementation-
defined manner. In this case, no guarantee is made when this surface
will receive keyboard focus (if ever).
For the bottom and background layers, the compositor is allowed to use
normal focus semantics.
This setting is mainly intended for applications that need to ensure
they receive all keyboard events, such as a lock screen or a password
prompt.
</description>
</entry>
<entry name="on_demand" value="2" since="4">
<description summary="request regular keyboard focus semantics">
This requests the compositor to allow this surface to be focused and
unfocused by the user in an implementation-defined manner. The user
should be able to unfocus this surface even regardless of the layer
it is on.
Typically, the compositor will want to use its normal mechanism to
manage keyboard focus between layer shell surfaces with this setting
and regular toplevels on the desktop layer (e.g. click to focus).
Nevertheless, it is possible for a compositor to require a special
interaction to focus or unfocus layer shell surfaces (e.g. requiring
a click even if focus follows the mouse normally, or providing a
keybinding to switch focus between layers).
This setting is mainly intended for desktop shell components (e.g.
panels) that allow keyboard interaction. Using this option can allow
implementing a desktop shell that can be fully usable without the
mouse.
</description>
</entry>
</enum>
<request name="set_keyboard_interactivity">
<description summary="requests keyboard events">
Set how keyboard events are delivered to this surface. By default,
layer shell surfaces do not receive keyboard events; this request can
be used to change this.
This setting is inherited by child surfaces set by the get_popup
request.
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.
Keyboard interactivity is double-buffered, see wl_surface.commit.
</description>
<arg name="keyboard_interactivity" type="uint" enum="keyboard_interactivity"/>
</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"/>
<entry name="invalid_keyboard_interactivity" value="3" summary="keyboard interactivity is invalid"/>
<entry name="invalid_exclusive_edge" value="4" summary="exclusive edge is invalid given the surface anchors"/>
</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>
<!-- Version 2 additions -->
<request name="set_layer" since="2">
<description summary="change the layer of the surface">
Change the layer that the surface is rendered on.
Layer is double-buffered, see wl_surface.commit.
</description>
<arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/>
</request>
<!-- Version 5 additions -->
<request name="set_exclusive_edge" since="5">
<description summary="set the edge the exclusive zone will be applied to">
Requests an edge for the exclusive zone to apply. The exclusive
edge will be automatically deduced from anchor points when possible,
but when the surface is anchored to a corner, it will be necessary
to set it explicitly to disambiguate, as it is not possible to deduce
which one of the two corner edges should be used.
The edge must be one the surface is anchored to, otherwise the
invalid_exclusive_edge protocol error will be raised.
</description>
<arg name="edge" type="uint" enum="anchor"/>
</request>
</interface>
</protocol>
@@ -1,11 +0,0 @@
uniform sampler2D source;
uniform vec2 size;
uniform vec4 crop;
uniform float alpha;
out vec4 outColor;
void main() {
vec2 p = vec2(gl_FragCoord.x, size.y - gl_FragCoord.y) / size;
vec4 c = texture(source, crop.xy + p * crop.zw);
outColor = vec4(c.rgb, c.a * alpha);
}
@@ -1,4 +0,0 @@
void main() {
vec2 corner = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
gl_Position = vec4(corner * 2.0 - 1.0, 0.0, 1.0);
}
@@ -1,106 +0,0 @@
// Bow-tie wipe transition adapted from gl-transitions.com
// Author: huynx (MIT)
// Original: two triangles grow from the top and bottom edges into a bow tie
// that reveals the incoming image.
uniform sampler2D from;
uniform sampler2D to;
uniform vec2 size;
uniform vec4 fromCrop;
uniform vec4 toCrop;
uniform float progress;
uniform float fromFlip;
uniform float toFlip;
out vec4 outColor;
vec4 getFromColor(vec2 uv) {
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
return texture(from, fromCrop.xy + s * fromCrop.zw);
}
vec4 getToColor(vec2 uv) {
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
return texture(to, toCrop.xy + s * toCrop.zw);
}
float check(vec2 p1, vec2 p2, vec2 p3) {
return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
}
bool pointInTriangle(vec2 pt, vec2 p1, vec2 p2, vec2 p3) {
bool b1 = check(pt, p1, p2) < 0.0;
bool b2 = check(pt, p2, p3) < 0.0;
bool b3 = check(pt, p3, p1) < 0.0;
return (b1 == b2) && (b2 == b3);
}
bool in_top_triangle(vec2 p) {
vec2 vertex1 = vec2(0.5, progress);
vec2 vertex2 = vec2(0.5 - progress, 0.0);
vec2 vertex3 = vec2(0.5 + progress, 0.0);
return pointInTriangle(p, vertex1, vertex2, vertex3);
}
bool in_bottom_triangle(vec2 p) {
vec2 vertex1 = vec2(0.5, 1.0 - progress);
vec2 vertex2 = vec2(0.5 - progress, 1.0);
vec2 vertex3 = vec2(0.5 + progress, 1.0);
return pointInTriangle(p, vertex1, vertex2, vertex3);
}
float blur_edge(vec2 bot1, vec2 bot2, vec2 top, vec2 testPt) {
vec2 lineDir = bot1 - top;
vec2 perpDir = vec2(lineDir.y, -lineDir.x);
vec2 dirToPt1 = bot1 - testPt;
float dist1 = abs(dot(normalize(perpDir), dirToPt1));
lineDir = bot2 - top;
perpDir = vec2(lineDir.y, -lineDir.x);
dirToPt1 = bot2 - testPt;
float min_dist = min(abs(dot(normalize(perpDir), dirToPt1)), dist1);
if (min_dist < 0.005)
return min_dist / 0.005;
return 1.0;
}
vec4 transition(vec2 uv) {
if (in_top_triangle(uv)) {
if (progress < 0.1)
return getFromColor(uv);
if (uv.y < 0.5) {
vec2 vertex1 = vec2(0.5, progress);
vec2 vertex2 = vec2(0.5 - progress, 0.0);
vec2 vertex3 = vec2(0.5 + progress, 0.0);
return mix(
getFromColor(uv),
getToColor(uv),
blur_edge(vertex2, vertex3, vertex1, uv));
}
if (progress > 0.0)
return getToColor(uv);
return getFromColor(uv);
}
if (in_bottom_triangle(uv)) {
if (uv.y >= 0.5) {
vec2 vertex1 = vec2(0.5, 1.0 - progress);
vec2 vertex2 = vec2(0.5 - progress, 1.0);
vec2 vertex3 = vec2(0.5 + progress, 1.0);
return mix(
getFromColor(uv),
getToColor(uv),
blur_edge(vertex2, vertex3, vertex1, uv));
}
return getFromColor(uv);
}
return getFromColor(uv);
}
void main() {
vec2 uv = gl_FragCoord.xy / size;
outColor = transition(uv);
}
@@ -1,72 +0,0 @@
// Doom-style melt wipe, from gl-transitions.com
// Author: Zeh Fernando (MIT)
// Description: the outgoing image melts down in uneven columns, like the Doom
// screen wipe. Fully procedural (no displacement texture).
//
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag). Optional
// gl-transitions params are fixed here: bars = 30, amplitude = 2.0,
// noise = 0.1, frequency = 0.5, dripScale = 0.5.
uniform sampler2D from;
uniform sampler2D to;
uniform vec2 size;
uniform vec4 fromCrop;
uniform vec4 toCrop;
uniform float progress;
uniform float fromFlip;
uniform float toFlip;
out vec4 outColor;
const int bars = 30;
const float amplitude = 2.0;
const float noise = 0.1;
const float frequency = 0.5;
const float dripScale = 0.5;
vec4 getFromColor(vec2 uv) {
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
return texture(from, fromCrop.xy + s * fromCrop.zw);
}
vec4 getToColor(vec2 uv) {
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
return texture(to, toCrop.xy + s * toCrop.zw);
}
float rand(int num) {
return fract(mod(float(num) * 67123.313, 12.0) * sin(float(num) * 10.3) *
cos(float(num)));
}
float wave(int num) {
float fn = float(num) * frequency * 0.1 * float(bars);
return cos(fn * 0.5) * cos(fn * 0.13) * sin((fn + 10.0) * 0.3) / 2.0 + 0.5;
}
float drip(int num) {
return sin(float(num) / float(bars - 1) * 3.141592) * dripScale;
}
float pos(int num) {
return (noise == 0.0 ? wave(num)
: mix(wave(num), rand(num), noise)) +
(dripScale == 0.0 ? 0.0 : drip(num));
}
vec4 transition(vec2 uv) {
int bar = int(uv.x * float(bars));
float scale = 1.0 + pos(bar) * amplitude;
float phase = progress * scale;
float posY = uv.y;
if (phase + posY < 1.0) {
return getFromColor(vec2(uv.x, uv.y + phase));
}
return getToColor(uv);
}
void main() {
vec2 uv = gl_FragCoord.xy / size;
outColor = transition(uv);
}
@@ -1,39 +0,0 @@
// Fade (crossfade) transition, from gl-transitions.com.
//
// Ported to ZShell's own GLSL ABI (same contract as wallpaper_trans_bowtie.frag):
// version/precision header is prepended by WallpaperService::shaderSource, the
// fullscreen triangle comes from shaders/wallpaper.vert, and getFromColor /
// getToColor sample with the crop + per-layer flip convention.
//
// uniforms: samplers `from`/`to`, vec2 size, vec4 fromCrop, vec4 toCrop,
// float fromFlip, float toFlip, float progress (0..1).
uniform sampler2D from;
uniform sampler2D to;
uniform vec2 size;
uniform vec4 fromCrop;
uniform vec4 toCrop;
uniform float fromFlip;
uniform float toFlip;
uniform float progress;
out vec4 outColor;
vec4 getFromColor(vec2 uv) {
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
return texture(from, fromCrop.xy + s * fromCrop.zw);
}
vec4 getToColor(vec2 uv) {
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
return texture(to, toCrop.xy + s * toCrop.zw);
}
vec4 transition(vec2 uv) {
return mix(getFromColor(uv), getToColor(uv), progress);
}
void main() {
vec2 uv = gl_FragCoord.xy / size;
outColor = transition(uv);
}
@@ -1,101 +0,0 @@
// Hexagonalize crossfade, from gl-transitions.com
// Author: Fernando Kuteken (MIT)
// Description: both images turn into hexagons, crossfade, then sharpen again.
// Hexagonal math from http://www.redblobgames.com/grids/hexagons/
//
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag). The
// gl-transitions `ratio` contextual value is derived from `size`. Optional
// params are fixed here: steps = 50, horizontalHexagons = 20.
uniform sampler2D from;
uniform sampler2D to;
uniform vec2 size;
uniform vec4 fromCrop;
uniform vec4 toCrop;
uniform float progress;
uniform float fromFlip;
uniform float toFlip;
out vec4 outColor;
const int steps = 50;
const float horizontalHexagons = 20.0;
float ratio;
vec4 getFromColor(vec2 uv) {
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
return texture(from, fromCrop.xy + s * fromCrop.zw);
}
vec4 getToColor(vec2 uv) {
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
return texture(to, toCrop.xy + s * toCrop.zw);
}
struct Hexagon {
float q;
float r;
float s;
};
Hexagon createHexagon(float q, float r) {
Hexagon hex;
hex.q = q;
hex.r = r;
hex.s = -q - r;
return hex;
}
Hexagon roundHexagon(Hexagon hex) {
float q = floor(hex.q + 0.5);
float r = floor(hex.r + 0.5);
float s = floor(hex.s + 0.5);
float deltaQ = abs(q - hex.q);
float deltaR = abs(r - hex.r);
float deltaS = abs(s - hex.s);
if (deltaQ > deltaR && deltaQ > deltaS)
q = -r - s;
else if (deltaR > deltaS)
r = -q - s;
else
s = -q - r;
return createHexagon(q, r);
}
Hexagon hexagonFromPoint(vec2 point, float size) {
point.y /= ratio;
point = (point - 0.5) / size;
float q = (sqrt(3.0) / 3.0) * point.x + (-1.0 / 3.0) * point.y;
float r = 0.0 * point.x + 2.0 / 3.0 * point.y;
Hexagon hex = createHexagon(q, r);
return roundHexagon(hex);
}
vec2 pointFromHexagon(Hexagon hex, float size) {
float x = (sqrt(3.0) * hex.q + (sqrt(3.0) / 2.0) * hex.r) * size + 0.5;
float y = (0.0 * hex.q + (3.0 / 2.0) * hex.r) * size + 0.5;
return vec2(x, y * ratio);
}
vec4 transition(vec2 uv) {
float dist = 2.0 * min(progress, 1.0 - progress);
dist = steps > 0 ? ceil(dist * float(steps)) / float(steps) : dist;
float size = (sqrt(3.0) / 3.0) * dist / horizontalHexagons;
vec2 point = dist > 0.0 ? pointFromHexagon(hexagonFromPoint(uv, size), size)
: uv;
return mix(getFromColor(point), getToColor(point), progress);
}
void main() {
vec2 uv = gl_FragCoord.xy / size;
ratio = size.x / size.y;
outColor = transition(uv);
}
@@ -1,58 +0,0 @@
// Linear blur crossfade, from gl-transitions.com
// Author: gre (MIT)
// Description: a crossfade that passes through a directional blur.
//
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag): the version
// header is prepended by WallpaperService::shaderSource, the fullscreen
// triangle comes from wallpaper.vert, and getFromColor/getToColor apply the
// crop + flip convention. `uv` is bottom-left origin per gl-transitions.
//
// Optional gl-transitions params are fixed here: intensity = 0.1.
uniform sampler2D from;
uniform sampler2D to;
uniform vec2 size;
uniform vec4 fromCrop;
uniform vec4 toCrop;
uniform float progress;
uniform float fromFlip;
uniform float toFlip;
out vec4 outColor;
const float intensity = 0.1;
const int passes = 6;
vec4 getFromColor(vec2 uv) {
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
return texture(from, fromCrop.xy + s * fromCrop.zw);
}
vec4 getToColor(vec2 uv) {
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
return texture(to, toCrop.xy + s * toCrop.zw);
}
vec4 transition(vec2 uv) {
vec4 c1 = vec4(0.0);
vec4 c2 = vec4(0.0);
float disp = intensity * (0.5 - distance(0.5, progress));
for (int xi = 0; xi < passes; xi++) {
float x = float(xi) / float(passes) - 0.5;
for (int yi = 0; yi < passes; yi++) {
float y = float(yi) / float(passes) - 0.5;
vec2 v = vec2(x, y);
c1 += getFromColor(uv + disp * v);
c2 += getToColor(uv + disp * v);
}
}
c1 /= float(passes * passes);
c2 /= float(passes * passes);
return mix(c1, c2, progress);
}
void main() {
vec2 uv = gl_FragCoord.xy / size;
outColor = transition(uv);
}
@@ -1,49 +0,0 @@
// Split slide-in (horizontal), from gl-transitions.com
// Author: OllyOllyOlly (MIT)
// Description: the incoming image slides in as two halves moving horizontally
// in opposite directions.
//
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag).
// Optional gl-transitions params are fixed here: reverse = false.
uniform sampler2D from;
uniform sampler2D to;
uniform vec2 size;
uniform vec4 fromCrop;
uniform vec4 toCrop;
uniform float progress;
uniform float fromFlip;
uniform float toFlip;
out vec4 outColor;
const vec2 boundMin = vec2(0.0, 0.0);
const vec2 boundMax = vec2(1.0, 1.0);
bool inBounds(vec2 p) {
return all(lessThan(boundMin, p)) && all(lessThan(p, boundMax));
}
vec4 getFromColor(vec2 uv) {
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
return texture(from, fromCrop.xy + s * fromCrop.zw);
}
vec4 getToColor(vec2 uv) {
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
return texture(to, toCrop.xy + s * toCrop.zw);
}
vec4 transition(vec2 uv) {
float modifier = 1.0;
vec2 toP = (uv.y > 0.5)
? vec2((uv.x - (progress * modifier)) + modifier, uv.y)
: vec2((uv.x + (progress * modifier)) - modifier, uv.y);
return inBounds(toP) ? getToColor(toP) : getFromColor(uv);
}
void main() {
vec2 uv = gl_FragCoord.xy / size;
outColor = transition(uv);
}
@@ -1,89 +0,0 @@
// Tiles wave reveal, from gl-transitions.com
// Author: numb3r23 (MIT)
// Description: the incoming image is revealed tile by tile in a diagonal wave.
//
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag).
// Optional gl-transitions params are fixed here: tileCount = (8, 8),
// flipX = true, flipY = false.
uniform sampler2D from;
uniform sampler2D to;
uniform vec2 size;
uniform vec4 fromCrop;
uniform vec4 toCrop;
uniform float progress;
uniform float fromFlip;
uniform float toFlip;
out vec4 outColor;
const ivec2 tileCount = ivec2(8, 8);
const bool flipX = true;
const bool flipY = false;
vec4 getFromColor(vec2 uv) {
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
return texture(from, fromCrop.xy + s * fromCrop.zw);
}
vec4 getToColor(vec2 uv) {
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
return texture(to, toCrop.xy + s * toCrop.zw);
}
vec4 transition(vec2 uv) {
vec2 tileSize = 1.0 / vec2(tileCount);
vec2 posInTile = fract(uv * vec2(tileCount));
vec2 tileNum = floor(uv * vec2(tileCount));
float countTiles = float(tileCount.x * tileCount.y);
// Diagonal wave from bottom-left to top-right.
float offset = (tileNum.y + tileNum.x * float(tileCount.y)) / countTiles;
float timeOffset = clamp((progress - offset) * countTiles, 0.0, 0.5);
float sinTime = 1.0 - abs(cos(fract(timeOffset) * 3.1415926));
vec2 texC = posInTile;
if (sinTime <= 0.5) {
if (flipX) {
if (texC.x < sinTime || texC.x > 1.0 - sinTime)
return getFromColor(uv);
texC.x = texC.x < 0.5
? (texC.x - sinTime) * 0.5 / (0.5 - sinTime)
: (texC.x - 0.5) * 0.5 / (0.5 - sinTime) + 0.5;
}
if (flipY) {
if (texC.y < sinTime || texC.y > 1.0 - sinTime)
return getFromColor(uv);
texC.y = texC.y < 0.5
? (texC.y - sinTime) * 0.5 / (0.5 - sinTime)
: (texC.y - 0.5) * 0.5 / (0.5 - sinTime) + 0.5;
}
vec2 globalUV = tileNum * tileSize + texC * tileSize;
return getFromColor(globalUV);
} else {
if (flipX) {
if (texC.x > sinTime || texC.x < 1.0 - sinTime)
return getToColor(uv);
texC.x = texC.x < 0.5
? (texC.x - sinTime) * 0.5 / (0.5 - sinTime)
: (texC.x - 0.5) * 0.5 / (0.5 - sinTime) + 0.5;
texC.x = 1.0 - texC.x;
}
if (flipY) {
if (texC.y > sinTime || texC.y < 1.0 - sinTime)
return getToColor(uv);
texC.y = texC.y < 0.5
? (texC.y - sinTime) * 0.5 / (0.5 - sinTime)
: (texC.y - 0.5) * 0.5 / (0.5 - sinTime) + 0.5;
texC.y = 1.0 - texC.y;
}
vec2 globalUV = tileNum * tileSize + texC * tileSize;
return getToColor(globalUV);
}
}
void main() {
vec2 uv = gl_FragCoord.xy / size;
outColor = transition(uv);
}
@@ -1,42 +0,0 @@
// Window slices wipe, from gl-transitions.com
// Author: gre (MIT)
// Description: the incoming image appears through vertical slices that widen.
//
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag).
// Optional gl-transitions params are fixed here: count = 10.0,
// smoothness = 0.5.
uniform sampler2D from;
uniform sampler2D to;
uniform vec2 size;
uniform vec4 fromCrop;
uniform vec4 toCrop;
uniform float progress;
uniform float fromFlip;
uniform float toFlip;
out vec4 outColor;
const float count = 10.0;
const float smoothness = 0.5;
vec4 getFromColor(vec2 uv) {
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
return texture(from, fromCrop.xy + s * fromCrop.zw);
}
vec4 getToColor(vec2 uv) {
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
return texture(to, toCrop.xy + s * toCrop.zw);
}
vec4 transition(vec2 uv) {
float pr = smoothstep(-smoothness, 0.0, uv.x - progress * (1.0 + smoothness));
float s = step(pr, fract(count * uv.x));
return mix(getFromColor(uv), getToColor(uv), s);
}
void main() {
vec2 uv = gl_FragCoord.xy / size;
outColor = transition(uv);
}
@@ -1,34 +0,0 @@
// Hard-edged wipe from top to bottom, from gl-transitions.com
// Author: Jake Nelson (MIT)
//
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag).
uniform sampler2D from;
uniform sampler2D to;
uniform vec2 size;
uniform vec4 fromCrop;
uniform vec4 toCrop;
uniform float progress;
uniform float fromFlip;
uniform float toFlip;
out vec4 outColor;
vec4 getFromColor(vec2 uv) {
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
return texture(from, fromCrop.xy + s * fromCrop.zw);
}
vec4 getToColor(vec2 uv) {
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
return texture(to, toCrop.xy + s * toCrop.zw);
}
vec4 transition(vec2 uv) {
return mix(getFromColor(uv), getToColor(uv), step(1.0 - uv.y, progress));
}
void main() {
vec2 uv = gl_FragCoord.xy / size;
outColor = transition(uv);
}
@@ -1,34 +0,0 @@
// Hard-edged wipe from left to right, from gl-transitions.com
// Author: Jake Nelson (MIT)
//
// Ported to ZShell's GLSL ABI (see wallpaper_trans_bowtie.frag).
uniform sampler2D from;
uniform sampler2D to;
uniform vec2 size;
uniform vec4 fromCrop;
uniform vec4 toCrop;
uniform float progress;
uniform float fromFlip;
uniform float toFlip;
out vec4 outColor;
vec4 getFromColor(vec2 uv) {
vec2 s = vec2(uv.x, fromFlip > 0.5 ? 1.0 - uv.y : uv.y);
return texture(from, fromCrop.xy + s * fromCrop.zw);
}
vec4 getToColor(vec2 uv) {
vec2 s = vec2(uv.x, toFlip > 0.5 ? 1.0 - uv.y : uv.y);
return texture(to, toCrop.xy + s * toCrop.zw);
}
vec4 transition(vec2 uv) {
return mix(getFromColor(uv), getToColor(uv), step(0.0 + uv.x, progress));
}
void main() {
vec2 uv = gl_FragCoord.xy / size;
outColor = transition(uv);
}
@@ -1,105 +0,0 @@
uniform sampler2D luma;
uniform sampler2D chroma;
uniform vec2 size;
uniform vec2 srcSize;
uniform vec4 crop;
uniform float alpha;
uniform int matrix;
uniform int range;
uniform vec2 chromaOff;
out vec4 outColor;
float noise(vec2 p) {
return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
}
const float PI = 3.1415926535893;
float sinc(float x) {
x *= PI;
return abs(x) < 1e-6 ? 1.0 : sin(x) / x;
}
float lanczos(float x) {
if (abs(x) >= 3.0)
return 0.0;
return sinc(x) * sinc(x / 3.0);
}
float hermite(float x) {
float a = abs(x);
if (a >= 1.0)
return 0.0;
return 1.0 - 3.0 * a * a + 2.0 * a * a * a;
}
vec4 resample(sampler2D tex, vec2 plane, vec2 dest, vec2 uv) {
vec2 pt = 1.0 / plane;
float wx[6];
float wy[6];
vec2 base;
int upx = dest.x >= plane.x ? 1 : 0;
int tx = upx == 1 ? 6 : 2;
float fsx = max(1.0, plane.x / dest.x);
float f = fract(uv.x * plane.x - 0.5);
base.x = uv.x - f * pt.x - pt.x * (float(tx) * 0.5 - 1.0);
float sx = 0.0;
for (int i = 0; i < 6; ++i) {
if (i >= tx) { wx[i] = 0.0; continue; }
float x = f - (float(i) - float(tx) * 0.5 + 1.0);
wx[i] = upx == 1 ? lanczos(x / fsx) : hermite(x / fsx);
sx += wx[i];
}
if (sx != 0.0)
for (int i = 0; i < 6; ++i) wx[i] /= sx;
int upy = dest.y >= plane.y ? 1 : 0;
int ty = upy == 1 ? 6 : 2;
float fsy = max(1.0, plane.y / dest.y);
f = fract(uv.y * plane.y - 0.5);
base.y = uv.y - f * pt.y - pt.y * (float(ty) * 0.5 - 1.0);
float sy = 0.0;
for (int i = 0; i < 6; ++i) {
if (i >= ty) { wy[i] = 0.0; continue; }
float y = f - (float(i) - float(ty) * 0.5 + 1.0);
wy[i] = upy == 1 ? lanczos(y / fsy) : hermite(y / fsy);
sy += wy[i];
}
if (sy != 0.0)
for (int i = 0; i < 6; ++i) wy[i] /= sy;
vec4 col = vec4(0.0);
for (int i = 0; i < 6; ++i) {
if (wx[i] == 0.0)
continue;
for (int j = 0; j < 6; ++j) {
if (wy[j] == 0.0)
continue;
vec2 c = base + vec2(float(i) * pt.x, float(j) * pt.y);
col += (wx[i] * wy[j]) * texture(tex, c);
}
}
return col;
}
void main() {
vec2 p = vec2(gl_FragCoord.x, size.y - gl_FragCoord.y) / size;
vec2 uv = crop.xy + p * crop.zw;
float Yv = resample(luma, srcSize, size, uv).r;
vec2 ch = resample(chroma, srcSize * 0.5, size, uv + chromaOff).rg;
vec3 yuv;
if (range == 0) {
yuv = vec3((Yv - 16.0 / 255.0) / (219.0 / 255.0),
(ch.r - 128.0 / 255.0) / (224.0 / 255.0),
(ch.g - 128.0 / 255.0) / (224.0 / 255.0));
} else {
yuv = vec3(Yv, ch.r - 0.5, ch.g - 0.5);
}
mat3 m = matrix == 1
? mat3(1.0, 1.0, 1.0, 0.0, -0.187324, 1.855600, 1.574800, -0.468124, 0.0)
: mat3(1.0, 1.0, 1.0, 0.0, -0.344136, 1.772000, 1.402000, -0.714136, 0.0);
vec3 rgb = m * yuv;
rgb += (noise(gl_FragCoord.xy) - 0.5) / 255.0;
outColor = vec4(clamp(rgb, 0.0, 1.0), alpha);
}
-549
View File
@@ -1,549 +0,0 @@
#include "videoplayer.hpp"
#ifdef ZHAVE_VIDEO
#define EGL_EGLEXT_PROTOTYPES
#define GL_GLEXT_PROTOTYPES
#include <EGL/egl.h>
#include <GL/gl.h>
#include <GL/glext.h>
#include <cuda.h>
#include <algorithm>
#include <chrono>
#include <climits>
#include <QtLogging>
extern "C" CUresult cuGraphicsGLRegisterImage(
CUgraphicsResource* resource,
GLuint image,
GLenum target,
unsigned int flags);
Q_LOGGING_CATEGORY(videoLog, "ZShell.Internal.wallpaper")
namespace {
enum AVPixelFormat chooseFormat(
AVCodecContext* ctx, const AVPixelFormat* pix_fmts) {
(void)ctx;
for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p)
if (*p == AV_PIX_FMT_CUDA) return AV_PIX_FMT_CUDA;
return pix_fmts[0];
}
} // namespace
VideoPlayer::VideoPlayer(EGLDisplay display) : m_eglDisplay(display) {}
VideoPlayer::~VideoPlayer() {
stop();
std::lock_guard<std::mutex> lock(m_mtx);
if (m_pending) {
av_frame_unref(m_pending);
av_frame_free(&m_pending);
}
if (m_displayed) {
av_frame_unref(m_displayed);
av_frame_free(&m_displayed);
}
if (m_cuCtx) {
CUcontext prev = nullptr;
cuCtxPushCurrent(m_cuCtx);
if (m_resY) {
cuGraphicsUnregisterResource(m_resY);
m_resY = nullptr;
}
if (m_resUV) {
cuGraphicsUnregisterResource(m_resUV);
m_resUV = nullptr;
}
if (m_texY) {
glDeleteTextures(1, &m_texY);
m_texY = 0;
}
if (m_texUV) {
glDeleteTextures(1, &m_texUV);
m_texUV = 0;
}
cuCtxPopCurrent(&prev);
cuCtxDestroy(m_cuCtx);
m_cuCtx = nullptr;
}
m_texSize = QSize();
}
void VideoPlayer::setReadyCallback(std::function<void()> callback) {
m_readyCb = std::move(callback);
}
void VideoPlayer::setFrameCallback(std::function<void()> callback) {
m_frameCb = std::move(callback);
}
bool VideoPlayer::openDecoder(const QString& path) {
if (avformat_open_input(
&m_fmt, path.toUtf8().constData(), nullptr, nullptr) < 0) {
qCWarning(
videoLog,
"VideoPlayer: could not open %s",
path.toUtf8().constData());
return false;
}
if (avformat_find_stream_info(m_fmt, nullptr) < 0) {
qCWarning(videoLog, "VideoPlayer: could not read stream info");
return false;
}
m_streamIndex = -1;
for (unsigned i = 0; i < m_fmt->nb_streams; ++i)
if (m_fmt->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
m_streamIndex = int(i);
break;
}
if (m_streamIndex < 0) {
qCWarning(videoLog, "VideoPlayer: no video stream");
return false;
}
AVCodecParameters* par = m_fmt->streams[m_streamIndex]->codecpar;
const AVCodec* codec = avcodec_find_decoder(par->codec_id);
const AVCodec* hwcodec = nullptr;
void* it = nullptr;
while (const AVCodec* c = av_codec_iterate(&it)) {
if (c->id != par->codec_id || !av_codec_is_decoder(c)) continue;
for (int i = 0;
const AVCodecHWConfig* cfg = avcodec_get_hw_config(c, i);
++i)
if (cfg->device_type == AV_HWDEVICE_TYPE_CUDA &&
(cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)) {
hwcodec = c;
break;
}
if (hwcodec) break;
}
if (hwcodec) codec = hwcodec;
if (!codec) {
qCWarning(
videoLog,
"VideoPlayer: no decoder for codec id %d",
int(par->codec_id));
return false;
}
m_codec = avcodec_alloc_context3(codec);
if (!m_codec) return false;
if (avcodec_parameters_to_context(m_codec, par) < 0) {
qCWarning(videoLog, "VideoPlayer: could not copy codec params");
return false;
}
m_hwDeviceCtx = av_hwdevice_ctx_alloc(AV_HWDEVICE_TYPE_CUDA);
if (!m_hwDeviceCtx) {
qCWarning(
videoLog, "VideoPlayer: could not allocate cuda hwdevice context");
return false;
}
AVCUDADeviceContext* cudaHw = reinterpret_cast<AVCUDADeviceContext*>(
reinterpret_cast<AVHWDeviceContext*>(m_hwDeviceCtx->data)->hwctx);
if (m_cuCtx) cudaHw->cuda_ctx = m_cuCtx;
if (av_hwdevice_ctx_init(m_hwDeviceCtx) < 0) {
qCWarning(
videoLog, "VideoPlayer: could not create cuda hwdevice context");
av_buffer_unref(&m_hwDeviceCtx);
return false;
}
m_codec->hw_device_ctx = av_buffer_ref(m_hwDeviceCtx);
m_codec->get_format = chooseFormat;
if (avcodec_open2(m_codec, codec, nullptr) < 0) {
qCWarning(videoLog, "VideoPlayer: could not open codec");
return false;
}
return true;
}
void VideoPlayer::closeDecoder() {
if (m_codec) {
avcodec_free_context(&m_codec);
m_codec = nullptr;
}
if (m_hwDeviceCtx) {
av_buffer_unref(&m_hwDeviceCtx);
m_hwDeviceCtx = nullptr;
}
if (m_fmt) {
avformat_close_input(&m_fmt);
m_fmt = nullptr;
}
m_streamIndex = -1;
}
void VideoPlayer::decodeLoop(const QString& path, uint32_t generation) {
if (!m_threadRunning.load(std::memory_order_acquire) ||
m_generation.load(std::memory_order_acquire) != generation)
return;
if (!openDecoder(path)) {
m_threadRunning.store(false);
return;
}
AVRational fr = m_fmt->streams[m_streamIndex]->avg_frame_rate;
if (fr.num <= 0 || fr.den <= 0) fr = m_codec->framerate;
if (fr.num <= 0 || fr.den <= 0) fr = AVRational{30, 1};
const int64_t intervalUs =
av_rescale_q(1, AVRational{fr.den, fr.num}, AVRational{1, 1000000});
AVFrame* frame = av_frame_alloc();
if (!frame) {
m_threadRunning.store(false);
return;
}
AVPacket pkt{};
while (m_threadRunning.load(std::memory_order_acquire) &&
m_generation.load(std::memory_order_acquire) == generation) {
int ret = av_read_frame(m_fmt, &pkt);
if (ret == AVERROR_EOF) {
if (avformat_seek_file(
m_fmt, m_streamIndex, INT64_MIN, 0, INT64_MAX, 0) < 0) {
qCWarning(videoLog, "VideoPlayer: loop seek failed; stopping");
break;
}
avcodec_flush_buffers(m_codec);
continue;
}
if (ret < 0) {
qCWarning(videoLog, "VideoPlayer: read error; stopping");
break;
}
if (pkt.stream_index != m_streamIndex) {
av_packet_unref(&pkt);
continue;
}
if (avcodec_send_packet(m_codec, &pkt) < 0) {
av_packet_unref(&pkt);
continue;
}
av_packet_unref(&pkt);
while (m_threadRunning.load(std::memory_order_acquire) &&
m_generation.load(std::memory_order_acquire) == generation &&
avcodec_receive_frame(m_codec, frame) == 0) {
bool wasEmpty = false;
{
std::lock_guard<std::mutex> lock(m_mtx);
if (!m_pending) m_pending = av_frame_alloc();
av_frame_unref(m_pending);
if (av_frame_ref(m_pending, frame) >= 0) {
wasEmpty = !m_haveFrame;
m_haveFrame = true;
}
}
if (wasEmpty && m_readyCb) m_readyCb();
if (m_frameCb) m_frameCb();
av_frame_unref(frame);
}
std::this_thread::sleep_for(std::chrono::microseconds(intervalUs));
}
av_frame_free(&frame);
closeDecoder();
m_threadRunning.store(false);
}
bool VideoPlayer::start(const QString& path) {
const uint32_t generation =
m_generation.fetch_add(1, std::memory_order_acq_rel) + 1;
m_threadRunning.store(true);
std::thread previous = std::move(m_thread);
m_thread = std::thread(
[this, path, generation, previous = std::move(previous)]() mutable {
if (previous.joinable()) previous.join();
closeDecoder();
if (!ensureCudaContext()) {
m_threadRunning.store(false);
return;
}
decodeLoop(path, generation);
});
return true;
}
void VideoPlayer::stop() {
m_generation.fetch_add(1, std::memory_order_acq_rel);
m_threadRunning.store(false);
if (m_thread.joinable()) m_thread.join();
closeDecoder();
std::lock_guard<std::mutex> lock(m_mtx);
if (m_pending) {
av_frame_unref(m_pending);
m_pending = nullptr;
}
if (m_displayed) {
av_frame_unref(m_displayed);
m_displayed = nullptr;
}
m_haveFrame = false;
m_size = QSize();
}
QSize VideoPlayer::frameSize() const {
std::lock_guard<std::mutex> lock(m_mtx);
return m_size;
}
void VideoPlayer::deleteTexture() {
std::lock_guard<std::mutex> lock(m_mtx);
CUcontext prev = nullptr;
if (m_cuCtx) cuCtxPushCurrent(m_cuCtx);
if (m_resY) {
cuGraphicsUnregisterResource(m_resY);
m_resY = nullptr;
}
if (m_resUV) {
cuGraphicsUnregisterResource(m_resUV);
m_resUV = nullptr;
}
if (m_texY) {
glDeleteTextures(1, &m_texY);
m_texY = 0;
}
if (m_texUV) {
glDeleteTextures(1, &m_texUV);
m_texUV = 0;
}
if (m_cuCtx) cuCtxPopCurrent(&prev);
m_texSize = QSize();
}
bool VideoPlayer::ensureCudaContext() {
if (m_cuCtx) return true;
if (cuInit(0) != CUDA_SUCCESS) {
qCWarning(videoLog, "VideoPlayer: cuInit failed");
return false;
}
CUdevice dev = 0;
if (cuDeviceGet(&dev, 0) != CUDA_SUCCESS) {
qCWarning(videoLog, "VideoPlayer: cuDeviceGet failed");
return false;
}
if (cuCtxCreate_v4(&m_cuCtx, nullptr, 0, dev) != CUDA_SUCCESS) {
m_cuCtx = nullptr;
qCWarning(videoLog, "VideoPlayer: cuCtxCreate failed");
return false;
}
CUcontext popped = nullptr;
cuCtxPopCurrent(&popped);
return true;
}
bool VideoPlayer::ensureTextures(unsigned w, unsigned h, int compBytes) {
if (!ensureCudaContext()) return false;
CUcontext prev = nullptr;
cuCtxPushCurrent(m_cuCtx);
const QSize sz = QSize(int(w), int(h));
if (m_texSize == sz && m_texComp == compBytes && m_texY != 0) {
cuCtxPopCurrent(&prev);
return true;
}
if (m_resY) cuGraphicsUnregisterResource(m_resY);
if (m_resUV) cuGraphicsUnregisterResource(m_resUV);
m_resY = m_resUV = nullptr;
if (m_texY) glDeleteTextures(1, &m_texY);
if (m_texUV) glDeleteTextures(1, &m_texUV);
m_texY = m_texUV = 0;
const bool wide = compBytes == 2;
auto makeTex =
[&](GLuint& tex, GLint internal, GLenum fmt, unsigned tw, unsigned th) {
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(
GL_TEXTURE_2D,
0,
internal,
GLint(tw),
GLint(th),
0,
fmt,
wide ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE,
nullptr);
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);
};
makeTex(m_texY, wide ? GL_R16 : GL_R8, GL_RED, w, h);
makeTex(m_texUV, wide ? GL_RG16 : GL_RG8, GL_RG, w / 2, h / 2);
CUresult r = cuGraphicsGLRegisterImage(
&m_resY,
m_texY,
GL_TEXTURE_2D,
CU_GRAPHICS_REGISTER_FLAGS_WRITE_DISCARD);
if (r != CUDA_SUCCESS) {
const char* es = nullptr;
cuGetErrorString(r, &es);
qCWarning(
videoLog,
"VideoPlayer: register luma tex failed (%s)",
es ? es : "?");
m_texSize = QSize();
cuCtxPopCurrent(&prev);
return false;
}
r = cuGraphicsGLRegisterImage(
&m_resUV,
m_texUV,
GL_TEXTURE_2D,
CU_GRAPHICS_REGISTER_FLAGS_WRITE_DISCARD);
if (r != CUDA_SUCCESS) {
qCWarning(videoLog, "VideoPlayer: register chroma tex failed");
cuGraphicsUnregisterResource(m_resY);
m_resY = nullptr;
m_texSize = QSize();
cuCtxPopCurrent(&prev);
return false;
}
m_texSize = sz;
m_texComp = compBytes;
cuCtxPopCurrent(&prev);
return true;
}
bool VideoPlayer::importCurrentFrame() {
AVFrame* frame = nullptr;
{
std::lock_guard<std::mutex> lock(m_mtx);
if (!m_pending || m_pending->format == AV_PIX_FMT_NONE) return false;
frame = m_pending;
m_pending = nullptr;
}
if (frame->format != AV_PIX_FMT_CUDA || !frame->hw_frames_ctx) {
static int logged = 0;
if (logged++ < 3)
qCWarning(
videoLog,
"VideoPlayer: frame not on GPU (fmt=%s hw_frames=%d)",
av_get_pix_fmt_name(AVPixelFormat(frame->format)),
frame->hw_frames_ctx != nullptr);
av_frame_unref(frame);
return false;
}
const AVHWFramesContext* hf =
reinterpret_cast<const AVHWFramesContext*>(frame->hw_frames_ctx->data);
const AVPixelFormat sw = hf->sw_format;
const bool tenBit = sw == AV_PIX_FMT_P010LE || sw == AV_PIX_FMT_P010BE ||
sw == AV_PIX_FMT_P012LE || sw == AV_PIX_FMT_P016LE;
const int compBytes = tenBit ? 2 : 1;
const size_t compSize = size_t(compBytes);
const unsigned w = unsigned(frame->width);
const unsigned h = unsigned(frame->height);
if (w == 0 || h == 0) {
av_frame_unref(frame);
return false;
}
size_t pitchY = size_t(frame->linesize[0]);
size_t pitchUV = frame->linesize[1] > 0 ? size_t(frame->linesize[1])
: pitchY;
if (pitchY == 0) pitchY = size_t(w) * compSize;
switch (frame->colorspace) {
case AVCOL_SPC_BT470BG:
case AVCOL_SPC_SMPTE170M:
m_matrix = 0;
break;
case AVCOL_SPC_BT709:
m_matrix = 1;
break;
default:
m_matrix = h >= 720 ? 1 : 0;
break;
}
m_range = frame->color_range == AVCOL_RANGE_JPEG ? 1 : 0;
m_size = QSize(int(w), int(h));
if (!ensureTextures(w, h, compBytes)) {
av_frame_unref(frame);
return false;
}
CUcontext prev = nullptr;
cuCtxPushCurrent(m_cuCtx);
CUgraphicsResource res[2] = {m_resY, m_resUV};
CUresult r = cuGraphicsMapResources(2, res, nullptr);
if (r != CUDA_SUCCESS) {
cuCtxPopCurrent(&prev);
av_frame_unref(frame);
qCWarning(videoLog, "VideoPlayer: cuGraphicsMapResources failed");
return false;
}
CUarray arrY = nullptr, arrUV = nullptr;
cuGraphicsSubResourceGetMappedArray(&arrY, m_resY, 0, 0);
cuGraphicsSubResourceGetMappedArray(&arrUV, m_resUV, 0, 0);
const size_t dstPitch = size_t(w) * compSize;
CUDA_MEMCPY2D mc{};
mc.srcMemoryType = CU_MEMORYTYPE_DEVICE;
mc.dstMemoryType = CU_MEMORYTYPE_ARRAY;
mc.srcDevice = CUdeviceptr(frame->data[0]);
mc.srcPitch = pitchY;
mc.dstArray = arrY;
mc.dstPitch = dstPitch;
mc.WidthInBytes = std::min(pitchY, dstPitch);
mc.Height = h;
r = cuMemcpy2D(&mc);
if (r == CUDA_SUCCESS) {
mc.srcDevice = CUdeviceptr(frame->data[1]);
mc.srcPitch = pitchUV;
mc.dstArray = arrUV;
mc.dstPitch = dstPitch;
mc.WidthInBytes = std::min(pitchUV, dstPitch);
mc.Height = h / 2;
r = cuMemcpy2D(&mc);
}
cuGraphicsUnmapResources(2, res, nullptr);
cuCtxPopCurrent(&prev);
if (r != CUDA_SUCCESS) {
av_frame_unref(frame);
qCWarning(videoLog, "VideoPlayer: cuMemcpy2D failed");
return false;
}
{
std::lock_guard<std::mutex> lock(m_mtx);
if (m_displayed) av_frame_unref(m_displayed);
m_displayed = frame;
}
return true;
}
#endif
-88
View File
@@ -1,88 +0,0 @@
#pragma once
#include <EGL/egl.h>
#include <cuda.h>
#include <atomic>
#include <functional>
#include <mutex>
#include <thread>
#include <QSize>
#include <QString>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/buffer.h>
#include <libavutil/frame.h>
#include <libavutil/hwcontext.h>
#include <libavutil/hwcontext_cuda.h>
#include <libavutil/pixfmt.h>
#include <libavutil/pixdesc.h>
}
class VideoPlayer {
public:
explicit VideoPlayer(EGLDisplay display);
~VideoPlayer();
void setReadyCallback(std::function<void()> callback);
void setFrameCallback(std::function<void()> callback);
bool start(const QString& path);
void stop();
bool importCurrentFrame();
void deleteTexture();
[[nodiscard]] uint32_t textureId() const { return m_texY; }
[[nodiscard]] uint32_t chromaTextureId() const { return m_texUV; }
[[nodiscard]] int colorMatrix() const { return m_matrix; }
[[nodiscard]] int colorRange() const { return m_range; }
[[nodiscard]] QSize frameSize() const;
private:
void decodeLoop(const QString& path, uint32_t generation);
bool openDecoder(const QString& path);
void closeDecoder();
bool ensureTextures(unsigned w, unsigned h, int compBytes);
bool ensureCudaContext();
EGLDisplay m_eglDisplay;
std::function<void()> m_readyCb;
std::function<void()> m_frameCb;
std::thread m_thread;
std::atomic<bool> m_threadRunning{false};
std::atomic<uint32_t> m_generation{0};
mutable std::mutex m_mtx;
AVFrame* m_pending = nullptr;
bool m_haveFrame = false;
AVFrame* m_displayed = nullptr;
AVFormatContext* m_fmt = nullptr;
AVCodecContext* m_codec = nullptr;
AVBufferRef* m_hwDeviceCtx = nullptr;
int m_streamIndex = -1;
CUcontext m_cuCtx = nullptr;
CUgraphicsResource m_resY = nullptr;
CUgraphicsResource m_resUV = nullptr;
QSize m_texSize;
uint32_t m_texY = 0;
uint32_t m_texUV = 0;
int m_matrix = 1;
int m_range = 0;
int m_texComp = 1;
QSize m_size;
};
-355
View File
@@ -1,355 +0,0 @@
#include "wallpaperimage.hpp"
#include <QStandardPaths>
#include <QCryptographicHash>
#include <QDir>
#include <QFileInfo>
#include <QImageReader>
#include <QtConcurrent>
#include <QQuickWindow>
namespace ZShell::internal {
QThreadPool* WallpaperImage::loadPool() {
static QThreadPool* pool = [] {
auto* p = new QThreadPool();
p->setMaxThreadCount(4);
return p;
}();
return pool;
}
WallpaperImage::WallpaperImage(QQuickItem* parent) : QQuickItem(parent) {
setFlag(ItemHasContents, true);
m_loadDebounceTimer.setSingleShot(true);
connect(
&m_loadDebounceTimer,
&QTimer::timeout,
this,
&WallpaperImage::beginLoad);
connect(
&m_imageWatcher,
&QFutureWatcher<QImage>::finished,
this,
&WallpaperImage::handleImageLoaded);
}
WallpaperImage::~WallpaperImage() {
m_requestGeneration->fetch_add(1, std::memory_order_relaxed);
m_imageWatcher.disconnect();
if (m_texture) delete m_texture;
if (m_previousTexture) delete m_previousTexture;
}
void WallpaperImage::setStatus(const Status s) {
if (m_status == s) return;
m_status = s;
emit statusChanged();
}
void WallpaperImage::setSource(const QUrl& source) {
if (m_source == source) return;
m_source = source;
emit sourceChanged();
setStatus(Loading);
m_loadDebounceTimer.start(80);
}
void WallpaperImage::setScreenResolution(const QSize& screenResolution) {
if (m_screenResolution == screenResolution) return;
m_screenResolution = screenResolution;
emit screenResolutionChanged();
beginLoad();
}
void WallpaperImage::setZoom(qreal zoom) {
if (qFuzzyCompare(m_zoom, zoom)) return;
m_zoom = zoom;
emit zoomChanged();
update();
}
void WallpaperImage::setFadeDuration(int ms) {
if (ms < 0) ms = 0;
if (m_fadeDuration == ms) return;
m_fadeDuration = ms;
emit fadeDurationChanged();
}
void WallpaperImage::setCropX(qreal x) {
if (qFuzzyCompare(m_cropX, x)) return;
m_cropX = x;
emit cropXChanged();
update();
}
void WallpaperImage::setCropY(qreal y) {
if (qFuzzyCompare(m_cropY, y)) return;
m_cropY = y;
emit cropYChanged();
update();
}
void WallpaperImage::setCropWidth(qreal w) {
if (w <= 0.0) w = 1.0;
if (qFuzzyCompare(m_cropWidth, w)) return;
m_cropWidth = w;
emit cropWidthChanged();
update();
}
void WallpaperImage::setCropHeight(qreal h) {
if (h <= 0.0) h = 1.0;
if (qFuzzyCompare(m_cropHeight, h)) return;
m_cropHeight = h;
emit cropHeightChanged();
update();
}
QString WallpaperImage::getCacheFilePath() const {
if (m_source.isEmpty() || m_screenResolution.isEmpty()) return QString();
QString cachePath =
QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) +
"/zshell/imagecache";
QDir().mkpath(cachePath);
QString id = m_source.toString() + "_" +
QString::number(m_screenResolution.width()) + "x" +
QString::number(m_screenResolution.height());
QByteArray hash =
QCryptographicHash::hash(id.toUtf8(), QCryptographicHash::Md5).toHex();
return cachePath + "/" + hash + ".png";
}
void WallpaperImage::beginLoad() {
if (m_source.isEmpty()) {
setStatus(Null);
return;
}
const quint64 requestId =
m_requestGeneration->fetch_add(1, std::memory_order_relaxed) + 1;
QString cacheFile = getCacheFilePath();
QString sourceFile = m_source.isLocalFile() ? m_source.toLocalFile()
: m_source.toString();
if (sourceFile.startsWith("qrc:/")) sourceFile = sourceFile.mid(3);
QSize targetRes = m_screenResolution;
auto generation = m_requestGeneration;
QFuture<QImage> future = QtConcurrent::run(
loadPool(),
[sourceFile, cacheFile, targetRes, requestId, generation]() -> QImage {
auto stale = [&] {
return generation->load(std::memory_order_relaxed) != requestId;
};
if (stale()) return QImage();
if (!targetRes.isEmpty() && !cacheFile.isEmpty() &&
QFileInfo::exists(cacheFile)) {
QImage cached(cacheFile);
if (!cached.isNull()) return cached;
}
if (stale()) return QImage();
QImage original;
QImageReader reader(sourceFile);
if (!targetRes.isEmpty() &&
reader.supportsOption(QImageIOHandler::Size)) {
QSize actual = reader.size();
if (actual.isValid() &&
(actual.width() > targetRes.width() ||
actual.height() > targetRes.height())) {
QSize scaledSize =
actual.scaled(targetRes, Qt::KeepAspectRatioByExpanding);
reader.setScaledSize(scaledSize);
}
original = reader.read();
} else {
original = QImage(sourceFile);
}
if (original.isNull()) return QImage();
if (stale()) return QImage();
if (targetRes.isEmpty()) return original;
QImage scaled = (original.width() > targetRes.width() ||
original.height() > targetRes.height())
? original.scaled(
targetRes,
Qt::KeepAspectRatioByExpanding,
Qt::SmoothTransformation)
: original;
if (stale()) return scaled;
if (!cacheFile.isEmpty()) scaled.save(cacheFile, "PNG");
return scaled;
});
m_imageWatcher.setFuture(future);
}
void WallpaperImage::handleImageLoaded() {
QImage result = m_imageWatcher.result();
if (result.isNull()) {
setStatus(Error);
return;
}
m_pendingImage = result;
m_hasPending = true;
setStatus(Ready);
update();
}
QRectF WallpaperImage::computeSourceRect(QSGTexture* texture) const {
QSize texSize = texture->textureSize();
qreal cW = m_cropWidth / m_zoom;
qreal cH = m_cropHeight / m_zoom;
QRectF reqRect(
m_cropX * texSize.width(),
m_cropY * texSize.height(),
cW * texSize.width(),
cH * texSize.height());
QRectF bounds = boundingRect();
if (bounds.isEmpty() || reqRect.isEmpty()) return reqRect;
qreal targetRatio = bounds.width() / bounds.height();
qreal reqRatio = reqRect.width() / reqRect.height();
QRectF sourceRect = reqRect;
if (reqRatio > targetRatio) {
qreal newWidth = reqRect.height() * targetRatio;
qreal xOffset = (reqRect.width() - newWidth) / 2.0;
sourceRect.setX(reqRect.x() + xOffset);
sourceRect.setWidth(newWidth);
} else if (reqRatio < targetRatio) {
qreal newHeight = reqRect.width() / targetRatio;
qreal yOffset = (reqRect.height() - newHeight) / 2.0;
sourceRect.setY(reqRect.y() + yOffset);
sourceRect.setHeight(newHeight);
}
return sourceRect;
}
void WallpaperImage::configureImageNode(
QSGImageNode* node, QSGTexture* texture) {
node->setTexture(texture);
node->setRect(boundingRect());
node->setFiltering(QSGTexture::Linear);
QRectF sourceRect = computeSourceRect(texture);
node->setSourceRect(sourceRect);
if (texture == m_texture) {
QSize texSize = texture->textureSize();
QRectF normalizedActual(
sourceRect.x() / texSize.width(),
sourceRect.y() / texSize.height(),
sourceRect.width() / texSize.width(),
sourceRect.height() / texSize.height());
bool changed = false;
auto updateIfChanged = [&](qreal& dst, qreal value) {
if (!qFuzzyCompare(dst, value)) {
dst = value;
changed = true;
}
};
updateIfChanged(m_actualCropX, normalizedActual.x());
updateIfChanged(m_actualCropY, normalizedActual.y());
updateIfChanged(m_actualCropWidth, normalizedActual.width());
updateIfChanged(m_actualCropHeight, normalizedActual.height());
if (changed) emit actualCropChanged();
}
}
QSGNode* WallpaperImage::updatePaintNode(
QSGNode* oldNode, UpdatePaintNodeData*) {
Q_UNUSED(oldNode);
if (m_hasPending) {
qDebug() << "pending format" << m_pendingImage.format()
<< double(m_pendingImage.sizeInBytes()) / 1048576.0 << "MB";
if (m_previousTexture) {
delete m_previousTexture;
m_previousTexture = nullptr;
}
m_previousTexture = m_texture;
m_texture = window()->createTextureFromImage(
m_pendingImage, QQuickWindow::TextureIsOpaque);
m_pendingImage = QImage();
m_hasPending = false;
if (m_previousTexture && m_fadeDuration > 0) {
m_fading = true;
m_fadeTimer.restart();
} else {
if (m_previousTexture) {
delete m_previousTexture;
m_previousTexture = nullptr;
}
m_fading = false;
}
}
if (!m_texture) {
return m_rootNode;
}
if (!m_rootNode) {
m_rootNode = new QSGNode();
m_newOpacityNode = new QSGOpacityNode();
m_newImageNode = window()->createImageNode();
m_newOpacityNode->appendChildNode(m_newImageNode);
m_rootNode->appendChildNode(m_newOpacityNode);
m_oldOpacityNode = new QSGOpacityNode();
m_oldImageNode = window()->createImageNode();
m_oldOpacityNode->appendChildNode(m_oldImageNode);
m_rootNode->appendChildNode(m_oldOpacityNode);
}
configureImageNode(m_newImageNode, m_texture);
m_newOpacityNode->setOpacity(1.0);
if (m_fading && m_previousTexture) {
qreal progress =
qBound(0.0, qreal(m_fadeTimer.elapsed()) / m_fadeDuration, 1.0);
configureImageNode(m_oldImageNode, m_previousTexture);
m_oldOpacityNode->setOpacity(1.0 - progress);
if (progress >= 1.0) {
m_fading = false;
delete m_previousTexture;
m_previousTexture = nullptr;
m_oldOpacityNode->setOpacity(0.0);
} else {
update();
}
} else {
m_oldOpacityNode->setOpacity(0.0);
}
return m_rootNode;
}
} // namespace ZShell::internal
-147
View File
@@ -1,147 +0,0 @@
#pragma once
#include <QQuickItem>
#include <QImage>
#include <QUrl>
#include <QSGTexture>
#include <QSGOpacityNode>
#include <QSGImageNode>
#include <QFutureWatcher>
#include <QElapsedTimer>
#include <QThreadPool>
#include <QTimer>
#include <QtQml/qqml.h>
#include <qtmetamacros.h>
#include <atomic>
namespace ZShell::internal {
class WallpaperImage : public QQuickItem {
Q_OBJECT
QML_NAMED_ELEMENT(WallpaperImage)
Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged)
Q_PROPERTY(
QSize screenResolution READ screenResolution WRITE setScreenResolution
NOTIFY screenResolutionChanged)
Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged)
Q_PROPERTY(Status status READ status NOTIFY statusChanged)
Q_PROPERTY(
int fadeDuration READ fadeDuration WRITE setFadeDuration NOTIFY
fadeDurationChanged)
Q_PROPERTY(qreal actualCropX READ actualCropX NOTIFY actualCropChanged)
Q_PROPERTY(qreal actualCropY READ actualCropY NOTIFY actualCropChanged)
Q_PROPERTY(
qreal actualCropWidth READ actualCropWidth NOTIFY actualCropChanged)
Q_PROPERTY(
qreal actualCropHeight READ actualCropHeight NOTIFY actualCropChanged)
Q_PROPERTY(qreal cropX READ cropX WRITE setCropX NOTIFY cropXChanged)
Q_PROPERTY(qreal cropY READ cropY WRITE setCropY NOTIFY cropYChanged)
Q_PROPERTY(
qreal cropWidth READ cropWidth WRITE setCropWidth NOTIFY
cropWidthChanged)
Q_PROPERTY(
qreal cropHeight READ cropHeight WRITE setCropHeight NOTIFY
cropHeightChanged)
public:
explicit WallpaperImage(QQuickItem* parent = nullptr);
~WallpaperImage() override;
enum Status { Null, Ready, Loading, Error };
Q_ENUM(Status)
Status status() const { return m_status; }
QUrl source() const { return m_source; }
void setSource(const QUrl& source);
QSize screenResolution() const { return m_screenResolution; }
void setScreenResolution(const QSize& screenResolution);
qreal zoom() const { return m_zoom; }
void setZoom(qreal zoom);
int fadeDuration() const { return m_fadeDuration; }
void setFadeDuration(int ms);
qreal actualCropX() const { return m_actualCropX; }
qreal actualCropY() const { return m_actualCropY; }
qreal actualCropWidth() const { return m_actualCropWidth; }
qreal actualCropHeight() const { return m_actualCropHeight; }
qreal cropX() const { return m_cropX; }
void setCropX(qreal x);
qreal cropY() const { return m_cropY; }
void setCropY(qreal y);
qreal cropWidth() const { return m_cropWidth; }
void setCropWidth(qreal w);
qreal cropHeight() const { return m_cropHeight; }
void setCropHeight(qreal h);
protected:
QSGNode* updatePaintNode(
QSGNode* oldNode, UpdatePaintNodeData* updatePaintNodeData) override;
signals:
void sourceChanged();
void screenResolutionChanged();
void zoomChanged();
void fadeDurationChanged();
void actualCropChanged();
void cropXChanged();
void cropYChanged();
void cropWidthChanged();
void cropHeightChanged();
void statusChanged();
private:
void beginLoad();
void handleImageLoaded();
QString getCacheFilePath() const;
void setStatus(const Status s);
QRectF computeSourceRect(QSGTexture* texture) const;
void configureImageNode(QSGImageNode* node, QSGTexture* texture);
Status m_status = Null;
QUrl m_source;
QSize m_screenResolution;
qreal m_zoom = 1.0;
int m_fadeDuration = 400;
qreal m_actualCropX = 0.0;
qreal m_actualCropY = 0.0;
qreal m_actualCropWidth = 1.0;
qreal m_actualCropHeight = 1.0;
qreal m_cropX = 0.0;
qreal m_cropY = 0.0;
qreal m_cropWidth = 1.0;
qreal m_cropHeight = 1.0;
QTimer m_loadDebounceTimer;
QFutureWatcher<QImage> m_imageWatcher;
std::shared_ptr<std::atomic<quint64>> m_requestGeneration =
std::make_shared<std::atomic<quint64>>(0);
static QThreadPool* loadPool();
QImage m_pendingImage;
bool m_hasPending = false;
QSGTexture* m_texture = nullptr;
QSGTexture* m_previousTexture = nullptr;
bool m_fading = false;
QElapsedTimer m_fadeTimer;
QSGNode* m_rootNode = nullptr;
QSGOpacityNode* m_newOpacityNode = nullptr;
QSGOpacityNode* m_oldOpacityNode = nullptr;
QSGImageNode* m_newImageNode = nullptr;
QSGImageNode* m_oldImageNode = nullptr;
};
} // namespace ZShell::internal
-121
View File
@@ -1,121 +0,0 @@
#include "wallpaperobject.hpp"
namespace ZShell::internal {
WallpaperObject::WallpaperObject(QObject* parent) : QObject(parent) {}
WallpaperObject::~WallpaperObject() {
if (m_service && !m_screen.isEmpty()) m_service->removeScreen(m_screen);
}
void WallpaperObject::registerScreen() {
if (!m_service || m_screen.isEmpty()) return;
m_service->addScreen(m_screen);
}
void WallpaperObject::setService(WallpaperService* service) {
if (m_service == service) return;
m_service = service;
emit serviceChanged();
registerScreen();
push();
}
void WallpaperObject::setScreen(const QString& screen) {
if (m_screen == screen) return;
m_screen = screen;
emit screenChanged();
registerScreen();
push();
}
void WallpaperObject::setCropX(qreal x) {
if (qFuzzyCompare(m_cropX, x)) return;
m_cropX = x;
emit cropXChanged();
push();
}
void WallpaperObject::setCropY(qreal y) {
if (qFuzzyCompare(m_cropY, y)) return;
m_cropY = y;
emit cropYChanged();
push();
}
void WallpaperObject::setCropWidth(qreal w) {
if (qFuzzyCompare(m_cropWidth, w)) return;
m_cropWidth = w;
emit cropWidthChanged();
push();
}
void WallpaperObject::setCropHeight(qreal h) {
if (qFuzzyCompare(m_cropHeight, h)) return;
m_cropHeight = h;
emit cropHeightChanged();
push();
}
void WallpaperObject::setZoom(qreal zoom) {
if (qFuzzyCompare(m_zoom, zoom)) return;
m_zoom = zoom;
emit zoomChanged();
push();
}
void WallpaperObject::syncCrop(const QVariantMap& crop) {
const qreal x = crop.value("x").toDouble();
const qreal y = crop.value("y").toDouble();
const qreal width = crop.contains("width") ? crop.value("width").toDouble()
: 1.0;
const qreal height = crop.contains("height")
? crop.value("height").toDouble()
: 1.0;
const qreal z =
crop.contains("zoom") ? crop.value("zoom").toDouble() : 1.0;
const bool prev = m_pushEnabled;
m_pushEnabled = false;
if (!qFuzzyCompare(m_cropX, x)) {
m_cropX = x;
emit cropXChanged();
}
if (!qFuzzyCompare(m_cropY, y)) {
m_cropY = y;
emit cropYChanged();
}
if (!qFuzzyCompare(m_cropWidth, width)) {
m_cropWidth = width;
emit cropWidthChanged();
}
if (!qFuzzyCompare(m_cropHeight, height)) {
m_cropHeight = height;
emit cropHeightChanged();
}
if (!qFuzzyCompare(m_zoom, z)) {
m_zoom = z;
emit zoomChanged();
}
m_pushEnabled = prev;
}
void WallpaperObject::push() {
if (!m_pushEnabled || !m_service || m_screen.isEmpty()) return;
QVariantMap crop;
crop.insert("x", m_cropX);
crop.insert("y", m_cropY);
crop.insert("width", m_cropWidth);
crop.insert("height", m_cropHeight);
crop.insert("zoom", m_zoom);
m_service->setCrop(m_screen, crop);
}
} // namespace ZShell::internal
@@ -1,86 +0,0 @@
#pragma once
#include <QObject>
#include <QPointer>
#include <QString>
#include <QVariantMap>
#include <QtQml/qqml.h>
#include <qtmetamacros.h>
#include "wallpaperservice.hpp"
namespace ZShell::internal {
class WallpaperObject : public QObject {
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(
WallpaperService* service READ service WRITE setService NOTIFY
serviceChanged)
Q_PROPERTY(QString screen READ screen WRITE setScreen NOTIFY screenChanged)
Q_PROPERTY(qreal cropX READ cropX WRITE setCropX NOTIFY cropXChanged)
Q_PROPERTY(qreal cropY READ cropY WRITE setCropY NOTIFY cropYChanged)
Q_PROPERTY(
qreal cropWidth READ cropWidth WRITE setCropWidth NOTIFY
cropWidthChanged)
Q_PROPERTY(
qreal cropHeight READ cropHeight WRITE setCropHeight NOTIFY
cropHeightChanged)
Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged)
public:
explicit WallpaperObject(QObject* parent = nullptr);
~WallpaperObject() override;
WallpaperService* service() const { return m_service; }
void setService(WallpaperService* service);
QString screen() const { return m_screen; }
void setScreen(const QString& screen);
qreal cropX() const { return m_cropX; }
void setCropX(qreal x);
qreal cropY() const { return m_cropY; }
void setCropY(qreal y);
qreal cropWidth() const { return m_cropWidth; }
void setCropWidth(qreal w);
qreal cropHeight() const { return m_cropHeight; }
void setCropHeight(qreal h);
qreal zoom() const { return m_zoom; }
void setZoom(qreal zoom);
Q_INVOKABLE void syncCrop(const QVariantMap& crop);
signals:
void serviceChanged();
void screenChanged();
void cropXChanged();
void cropYChanged();
void cropWidthChanged();
void cropHeightChanged();
void zoomChanged();
private:
void registerScreen();
void push();
QPointer<WallpaperService> m_service;
QString m_screen;
qreal m_cropX = 0.0;
qreal m_cropY = 0.0;
qreal m_cropWidth = 1.0;
qreal m_cropHeight = 1.0;
qreal m_zoom = 1.0;
bool m_pushEnabled = true;
};
} // namespace ZShell::internal
File diff suppressed because it is too large Load Diff
@@ -1,384 +0,0 @@
#pragma once
#include <EGL/egl.h>
#include <cstdint>
#include <mutex>
#include <thread>
#include <vector>
#include <QImage>
#include <QHash>
#include <QList>
#include <QElapsedTimer>
#include <QRectF>
#include <QSize>
#include <QString>
#include <QStringList>
#include <QVariantMap>
#include <QVector>
#include <QtQml/qqml.h>
#include <qtmetamacros.h>
#include <atomic>
#include <memory>
#ifdef ZHAVE_VIDEO
#include "videoplayer.hpp"
#endif
#include <qwayland-wlr-layer-shell-unstable-v1.h>
#include <wayland-client-protocol.h>
#include "wallpapersurface.hpp"
#include "config/enums.hpp"
struct mpv_handle;
struct mpv_render_context;
class QProcess;
namespace ZShell::internal {
class WallpaperSurface;
class WallpaperService : public QObject {
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged REQUIRED)
Q_PROPERTY(QVariantMap crops READ crops WRITE setCrops NOTIFY cropsChanged)
Q_PROPERTY(QString dbPath READ dbPath WRITE setDbPath NOTIFY dbPathChanged)
Q_PROPERTY(
QSize largestBufferSize READ largestBufferSize NOTIFY
largestBufferSizeChanged)
public:
explicit WallpaperService(QObject* parent = nullptr);
~WallpaperService() override;
[[nodiscard]] QString path() const;
void setPath(const QString& path);
[[nodiscard]] QSize largestBufferSize() const;
[[nodiscard]] QString dbPath() const;
void setDbPath(const QString& path);
void addScreen(const QString& screen);
void removeScreen(const QString& screen);
[[nodiscard]] const QVariantMap& crops() const;
void setCrops(const QVariantMap& crops);
Q_INVOKABLE void redraw();
Q_INVOKABLE void setCrop(const QString& screen, const QVariantMap& crop);
Q_INVOKABLE void invalidateCache();
signals:
void pathChanged();
void cropsChanged();
void dbPathChanged();
void largestBufferSizeChanged();
private:
friend class WallpaperSurface;
static bool isVideoPath(const QString& path);
static bool isHardwareCodec(const QString& codec);
[[nodiscard]] QString codecName(const QString& path) const;
[[nodiscard]] QString cacheDir() const;
[[nodiscard]] QString transcodeTarget(const QString& path) const;
[[nodiscard]] bool needsTranscode(const QString& path) const;
void transcodeVideo(const QString& src, const QString& dst);
void applyPath(const QString& path);
void startVideo(const QString& path);
void teardownMpv();
void initMpvRender();
void renderMpvToSurface(const QSize& size);
void pumpMpvEvents();
static void* getMpvProcAddress(void* ctx, const char* name);
static void mpvUpdate(void* ctx);
#ifdef ZHAVE_VIDEO
bool startPlayerVideo(const QString& path);
void stopPlayerVideo();
[[nodiscard]] uint32_t createVideoProgram();
void drawVideoTexture(
uint32_t lumaTex,
uint32_t chromaTex,
int matrix,
int range,
const QRectF& rect,
float sizeW,
float sizeH,
float srcW,
float srcH,
float alpha);
#endif
void bindGlobals();
static void registryGlobal(
void* data,
struct wl_registry* registry,
uint32_t name,
const char* interface,
uint32_t version);
void storeGlobal(const char* interface, void* proxy);
struct TransProgram {
uint32_t id = 0;
int size = -1;
int progress = -1;
int from = -1;
int to = -1;
int fromCrop = -1;
int toCrop = -1;
int fromFlip = -1;
int toFlip = -1;
};
[[nodiscard]] bool setupGl();
[[nodiscard]] uint32_t createProgram();
[[nodiscard]] TransProgram createTransitionProgram(const QString& file);
[[nodiscard]] TransProgram programFor(const QString& file);
bool ensureTransitionProgram();
QString transitionFileFor(ZShell::config::WallpaperTransition::Enum value);
[[nodiscard]] QString randomTransitionFile();
void ensureCapture(const QSize& size);
uint32_t renderVideoIntoTexture(const QSize& size);
void drawSurfaceTransition(WallpaperSurface* surface);
struct TransitionLayer {
uint32_t tex = 0; // frozen image target (older layers)
QImage image; // CPU copy for crops/sizing (image layers)
QVariantMap crops; // crop map captured when the layer was pushed
QString transitionFile; // this layer's own transition shader
bool isVideo = false; // only the newest layer may be video
bool ready = false; // target is sampleable
bool started = false; // its own clock has begun
QElapsedTimer timer;
qreal progress = 0.0;
};
void ensureBaseFbo();
void ensureAccum(const QSize& size);
void bindRtt(uint32_t fbo, uint32_t tex, const QSize& size);
void drawTransitionPair(
const TransProgram& program,
uint32_t fromTex,
float fromFlip,
const QRectF& fromCrop,
uint32_t toTex,
float toFlip,
const QRectF& toCrop,
qreal progress,
const QSize& size);
uint32_t targetTexFor(const TransitionLayer& layer, int index) const;
QRectF cropForLayer(
const TransitionLayer& layer,
const QString& screen,
const QSize& size) const;
void captureBase(
const QSize& size,
const QString& screen,
uint32_t oldTex,
float oldFlip,
const QRectF& oldCrop);
void renderComposite(
const QSize& size,
const QString& screen,
uint32_t outFbo,
uint32_t outTex,
int first,
int last,
uint32_t videoTex,
uint32_t posterTex);
void uploadTexture(uint32_t& texture, const QImage& image);
void uploadImage();
void createBlackTexture(uint32_t& texture);
void drawTexture(
uint32_t texture,
const QRectF& rect,
float sizeW,
float sizeH,
float alpha);
[[nodiscard]] QImage decodeImage(
const QString& path, const QSize& decodeSize) const;
[[nodiscard]] QByteArray cachedBlob(const QString& path) const;
[[nodiscard]] QByteArray posterBlob(const QString& path) const;
[[nodiscard]] QVariantMap cropsForPath(const QString& path) const;
void ensureImage();
void ensurePoster();
void notifyBufferSizes();
void queue(WallpaperSurface* surface);
void queueAll();
void loop();
void present();
void drawSurface(WallpaperSurface* surface);
void wake();
[[nodiscard]] wl_event_queue* eventQueue() const { return m_queue; }
[[nodiscard]] QRectF sourceRect(
const QVariantMap& crops,
const QString& screen,
const QSize& size,
const QSize& image) const;
void start();
void addSurfaces();
void addSurface(const QString& screen);
QString m_path;
QString m_dbPath;
QStringList m_screens;
QVariantMap m_crops;
QImage m_image;
bool m_isVideo = false;
bool m_videoReload = false;
mpv_handle* m_mpv = nullptr;
mpv_render_context* m_mpvRender = nullptr;
uint32_t m_blackTex = 0;
std::atomic<bool> m_mpvFrameReady{false};
std::atomic<bool> m_videoReady{false};
std::atomic<bool> m_videoLoaded{false};
QProcess* m_transcode = nullptr;
QSize m_decodeSize;
QImage m_stagedImage;
QString m_stagedPath;
bool m_stagedReady = false;
bool m_decoding = false;
QString m_decodePath;
bool m_replacementReady = false;
wl_display* m_display = nullptr;
wl_registry* m_registry = nullptr;
wl_compositor* m_compositor = nullptr;
QtWayland::zwlr_layer_shell_v1* m_layerShell = nullptr;
QtWayland::wp_viewporter* m_viewporter = nullptr;
QtWayland::wp_fractional_scale_manager_v1* m_scaleManager = nullptr;
EGLDisplay m_eglDisplay = EGL_NO_DISPLAY;
EGLConfig m_config = nullptr;
EGLContext m_context = EGL_NO_CONTEXT;
uint32_t m_texture = 0;
uint32_t m_program = 0;
int m_sizeUniform = -1;
int m_cropUniform = -1;
int m_alphaUniform = -1;
int m_sourceUniform = -1;
bool m_randomTransition = false; // wallTransition == Random
QString m_transitionFile; // the transition stamped onto new layers
bool m_transitionDirty = false;
QHash<QString, TransProgram>
m_transPrograms; // compiled programs, keyed by shader
TransProgram
m_neutralProgram; // fade program for identity copies (seed/base/final)
QVector<int>
m_transitionUses; // usage counts, parallel to the transition list
QString m_lastTransition; // last random pick, so it is never repeated
uint32_t m_captureFbo = 0;
uint32_t m_captureTex = 0;
QSize m_captureSize;
uint32_t m_baseFbo = 0;
uint32_t m_baseTex = 0;
QSize m_baseSize;
bool m_baseReady = false;
uint32_t m_accumFbo = 0;
uint32_t m_accumA = 0;
uint32_t m_accumB = 0;
QSize m_accumSize;
std::vector<TransitionLayer> m_transitions; // oldest .. newest
bool m_needCaptureBase = false;
QImage m_baseImage; // old settled image, for the base capture crop
QVariantMap m_baseCrops; // old settled crop map, for the base capture crop
bool m_baseIsVideo = false; // old settled frame was a live video
bool m_baseIsBlack = false; // shell start / first load: fade in from black
bool m_collapseVideo =
false; // video pushed mid-stack: bake older into base
bool m_promoteOldest = false; // stack over the cap: promote oldest to base
bool m_newestDone = false; // newest layer finished: settle into base
static constexpr int kMaxTransitions = 4;
uint32_t m_videoProgram = 0;
uint32_t m_vao = 0;
int m_videoSizeUniform = -1;
int m_videoSrcSizeUniform = -1;
int m_videoCropUniform = -1;
int m_videoAlphaUniform = -1;
int m_videoLumaUniform = -1;
int m_videoChromaUniform = -1;
int m_videoMatrixUniform = -1;
int m_videoRangeUniform = -1;
int m_videoChromaOffUniform = -1;
#ifdef ZHAVE_VIDEO
std::unique_ptr<VideoPlayer> m_player;
bool m_usePlayer = true;
#endif
std::vector<uint32_t> m_orphanTextures;
QList<WallpaperSurface*> m_surfaces;
std::thread m_thread;
mutable std::recursive_mutex m_mutex;
wl_event_queue* m_queue = nullptr;
int m_wake[2] = {-1, -1};
bool m_stop = false;
bool m_drawing = false;
bool started = false;
bool m_forceDecode = false;
bool m_imageChanged = false;
QImage m_posterImage;
uint32_t m_posterTex = 0;
QString m_posterPath;
bool m_posterReady = false;
bool m_posterLoading = false;
bool m_posterChanged = false;
};
} // namespace ZShell::internal
@@ -1,191 +0,0 @@
#include "wallpapersurface.hpp"
#include <QDebug>
#include <cmath>
#include "wallpaperservice.hpp"
namespace ZShell::internal {
const QString kNamespace = "ZShell-Wallpaper";
void setQueue(void* proxy, struct wl_event_queue* queue) {
wl_proxy_set_queue(static_cast<struct wl_proxy*>(proxy), queue);
}
WallpaperSurface::WallpaperSurface(
WallpaperService* service,
wl_surface* surface,
wl_output* output,
const QString& screen)
: m_service(service), m_screen(screen), m_surface(surface) {
if (!surface) {
qWarning() << "WallpaperSurface: no surface for" << screen;
return;
}
setQueue(m_surface, m_service->eventQueue());
auto* layer = m_service->m_layerShell->get_layer_surface(
m_surface,
output,
QtWayland::zwlr_layer_shell_v1::layer_background,
kNamespace);
setQueue(layer, m_service->eventQueue());
QtWayland::zwlr_layer_surface_v1::init(layer);
set_size(0, 0);
set_anchor(anchor_top | anchor_bottom | anchor_left | anchor_right);
set_exclusive_zone(-1);
set_keyboard_interactivity(keyboard_interactivity_none);
set_margin(0, 0, 0, 0);
auto* viewport = m_service->m_viewporter->get_viewport(m_surface);
setQueue(viewport, m_service->eventQueue());
QtWayland::wp_viewport::init(viewport);
auto* scale = m_service->m_scaleManager->get_fractional_scale(m_surface);
setQueue(scale, m_service->eventQueue());
QtWayland::wp_fractional_scale_v1::init(scale);
wl_surface_commit(m_surface);
}
WallpaperSurface::~WallpaperSurface() {
if (m_frameCallback) {
wl_callback_destroy(m_frameCallback);
m_frameCallback = nullptr;
}
if (m_eglSurface != EGL_NO_SURFACE)
eglDestroySurface(m_service->m_eglDisplay, m_eglSurface);
if (m_window) wl_egl_window_destroy(m_window);
if (QtWayland::wp_viewport::isInitialized())
QtWayland::wp_viewport::destroy();
if (QtWayland::wp_fractional_scale_v1::isInitialized())
QtWayland::wp_fractional_scale_v1::destroy();
if (QtWayland::zwlr_layer_surface_v1::isInitialized())
QtWayland::zwlr_layer_surface_v1::destroy();
if (m_surface) wl_surface_destroy(m_surface);
}
void WallpaperSurface::markDirty() {
m_dirty.store(true, std::memory_order_relaxed);
}
bool WallpaperSurface::needsDraw() const {
return m_dirty.load(std::memory_order_relaxed);
}
bool WallpaperSurface::frameReady() const {
return m_frameReady.load(std::memory_order_relaxed);
}
void WallpaperSurface::requestFrame() {
if (!m_surface || m_frameCallback) return;
m_frameCallback = wl_surface_frame(m_surface);
static const struct wl_callback_listener listener = {
[](void* data, struct wl_callback*, uint32_t) {
static_cast<WallpaperSurface*>(data)->onFrameDone();
},
};
wl_callback_add_listener(m_frameCallback, &listener, this);
}
void WallpaperSurface::onFrameDone() {
if (m_frameCallback) {
wl_callback_destroy(m_frameCallback);
m_frameCallback = nullptr;
}
m_frameReady.store(true, std::memory_order_relaxed);
m_service->wake();
}
void WallpaperSurface::endDraw() {
m_dirty.store(false, std::memory_order_relaxed);
m_frameReady.store(false, std::memory_order_relaxed);
}
void WallpaperSurface::zwlr_layer_surface_v1_configure(
uint32_t serial, uint32_t width, uint32_t height) {
ack_configure(serial);
if (width == 0 || height == 0) return;
m_size = QSize(int(width), int(height));
if (!configured) configured = true;
updateBuffer();
}
void WallpaperSurface::wp_fractional_scale_v1_preferred_scale(uint32_t scale) {
if (scale == 0) return;
qreal newScale = qreal(scale) / 120.0;
if (qFuzzyCompare(newScale, m_scale)) return;
m_scale = newScale;
updateBuffer();
}
void WallpaperSurface::zwlr_layer_surface_v1_closed() {}
void WallpaperSurface::updateBuffer() {
if (m_size.isEmpty()) return;
QSize buffer = QSize(
int(std::llround(m_size.width() * m_scale)),
int(std::llround(m_size.height() * m_scale)));
if (buffer.isEmpty()) return;
if (m_bufferSize != buffer) {
m_bufferSize = buffer;
if (m_window) {
wl_egl_window_resize(
m_window, buffer.width(), buffer.height(), 0, 0);
} else {
m_window =
wl_egl_window_create(m_surface, buffer.width(), buffer.height());
if (!m_window) {
qWarning()
<< "WallpaperSurface: could not make a window surface for"
<< m_screen;
return;
}
}
if (m_eglSurface == EGL_NO_SURFACE) {
m_eglSurface = eglCreateWindowSurface(
m_service->m_eglDisplay,
m_service->m_config,
reinterpret_cast<EGLNativeWindowType>(m_window),
nullptr);
if (m_eglSurface == EGL_NO_SURFACE) {
qWarning()
<< "WallpaperSurface: eglCreateWindowSurface failed for"
<< m_screen;
return;
}
}
}
if (QtWayland::wp_viewport::isInitialized())
set_destination(m_size.width(), m_size.height());
requestFrame();
m_service->queue(this);
m_service->notifyBufferSizes();
}
} // namespace ZShell::internal
@@ -1,73 +0,0 @@
#pragma once
#include <EGL/egl.h>
#include <wayland-egl.h>
#include <atomic>
#include <QSize>
#include <QString>
#include <qwayland-fractional-scale-v1.h>
#include <qwayland-viewporter.h>
#include <qwayland-wlr-layer-shell-unstable-v1.h>
#include <wayland-client-protocol.h>
namespace ZShell::internal {
class WallpaperService;
class WallpaperSurface : public QtWayland::zwlr_layer_surface_v1,
public QtWayland::wp_viewport,
public QtWayland::wp_fractional_scale_v1 {
public:
WallpaperSurface(
WallpaperService* service,
wl_surface* surface,
wl_output* output,
const QString& screen);
~WallpaperSurface() override;
[[nodiscard]] const QString& screen() const { return m_screen; }
[[nodiscard]] const QSize& size() const { return m_size; }
[[nodiscard]] const QSize& bufferSize() const { return m_bufferSize; }
[[nodiscard]] EGLSurface eglSurface() const { return m_eglSurface; }
void markDirty();
[[nodiscard]] bool needsDraw() const;
[[nodiscard]] bool frameReady() const;
void requestFrame();
void endDraw();
private:
void zwlr_layer_surface_v1_configure(
uint32_t serial, uint32_t width, uint32_t height) override;
void zwlr_layer_surface_v1_closed() override;
void wp_fractional_scale_v1_preferred_scale(uint32_t scale) override;
void updateBuffer();
void onFrameDone();
WallpaperService* m_service;
QString m_screen;
wl_surface* m_surface;
wl_egl_window* m_window = nullptr;
EGLSurface m_eglSurface = EGL_NO_SURFACE;
QSize m_size;
QSize m_bufferSize;
qreal m_scale = 1.0;
bool configured = false;
std::atomic<bool> m_dirty{false};
std::atomic<bool> m_frameReady{true};
struct wl_callback* m_frameCallback = nullptr;
};
} // namespace ZShell::internal