← recipes

Commit e05a2e8

13
plików
+710
dodanych
-0
usuniętych
@@ -0,0 +1,50 @@
1 +# GENEROWANY PLIK — nie edytować ręcznie.
2 +# Źródło prawdy: paganDE/config/ i paganDE/session/.
3 +# Regeneracja: make tarball-meta && make recipe
4 +
5 +pkgname: pagande
6 +pkgver: '0.1.0'
7 +pkgrel: 2
8 +pkgdesc: 'PaganDE — nowoczesne, modułowe środowisko Wayland z pływającymi oknami (własny shell, bez GTK/Qt)'
9 +url: 'https://paganlinux.eu'
10 +arch: x86_64
11 +license:
12 +- MIT
13 +depends:
14 +- labwc-pagan
15 +- pagande-shell
16 +- pipewire
17 +- polkit-gnome
18 +- network-manager-applet
19 +- xdg-desktop-portal
20 +- xdg-desktop-portal-gtk
21 +- papirus-icon-theme
22 +- cantarell-fonts
23 +- xcursor-dmz
24 +- alacritty
25 +- thunar
26 +makedepends: []
27 +source:
28 +- pagande-${pkgver}.tar.gz
29 +sha256sums:
30 +- SKIP
31 +build: |
32 + cd ${pkgname}
33 +
34 + # ── Pliki sesji ─────────────────────────────────────────────
35 + install -dm755 "${PKGDIR}/usr/bin"
36 + install -m755 session/pagande-session "${PKGDIR}/usr/bin/pagande-session"
37 +
38 + install -dm755 "${PKGDIR}/usr/share/wayland-sessions"
39 + install -m644 session/pagande.desktop "${PKGDIR}/usr/share/wayland-sessions/pagande.desktop"
40 +
41 + # ── Motyw okien (fork labwc) ────────────────────────────────
42 + install -dm755 "${PKGDIR}/usr/share/themes"
43 + cp -a config/theme/pagan "${PKGDIR}/usr/share/themes/pagan"
44 +
45 + # ── Domyślne konfiguracje (sesja rozkłada je do ~/.config) ─
46 + install -dm755 "${PKGDIR}/etc/xdg/pagande"
47 + cp -a config/labwc "${PKGDIR}/etc/xdg/pagande/labwc"
48 + cp -a config/shell "${PKGDIR}/etc/xdg/pagande/shell"
49 +package: |
50 + true
0 51 new file mode 100644
1 52 Binary files /dev/null and b/de/pagande/pagande-0.1.0.tar.gz differ
2 53 new file mode 100644
@@ -0,0 +1,20 @@
1 +pkgname: hwdata
2 +pkgver: '0.410'
3 +pkgrel: 1
4 +pkgdesc: Hardware identification and configuration data (pci.ids, usb.ids, ...)
5 +url: https://github.com/vcrhonek/hwdata
6 +arch: x86_64
7 +license:
8 +- GPL-2.0-or-later
9 +depends: []
10 +makedepends: []
11 +source:
12 +- https://github.com/vcrhonek/hwdata/archive/refs/tags/v${pkgver}.tar.gz
13 +sha256sums:
14 +- SKIP
15 +build: |
16 + cd ${pkgname}
17 +
18 + make install PREFIX=/usr DESTDIR=${PKGDIR}
19 +package: |
20 + true
0 21 new file mode 100644
@@ -0,0 +1,45 @@
1 +pkgname: labwc-pagan
2 +pkgver: '0.20.2'
3 +pkgrel: 1
4 +pkgdesc: Labwc PaganDE (fork) - lekki kompozytor Wayland z pływającymi oknami, branding PaganDE
5 +url: https://paganlinux.eu
6 +arch: x86_64
7 +license:
8 +- GPL-2.0-only
9 +depends:
10 +- wlroots
11 +- libxkbcommon
12 +- xorg-libdrm
13 +- libxml2
14 +- glib
15 +- cairo
16 +- pango
17 +- libinput
18 +- xorg-pixman
19 +- libpng
20 +- systemd
21 +makedepends:
22 +- meson
23 +- ninja
24 +- wayland
25 +- wayland-protocols
26 +- glib
27 +source:
28 +- labwc-pagan-${pkgver}.tar.gz
29 +sha256sums:
30 +- SKIP
31 +build: |
32 + cd ${pkgname}
33 +
34 + # Nasz fork: Xwayland, SVG (librsvg) i ikony z libsfdo wyłączone w v1.
35 + # Rozjazdy względem upstream: pagan/FORK.md (wersja 0.20.2-pagan,
36 + # XDG_CURRENT_DESKTOP=PaganDE:wlroots).
37 + meson setup build --prefix=/usr \
38 + -Dxwayland=disabled \
39 + -Dsvg=disabled \
40 + -Dicon=disabled
41 +
42 + ninja -C build
43 + DESTDIR=${PKGDIR} ninja -C build install
44 +package: |
45 + true
0 46 new file mode 100644
1 47 Binary files /dev/null and b/gui/labwc-pagan/labwc-pagan-0.20.2.tar.gz differ
2 48 new file mode 100644
@@ -0,0 +1,25 @@
1 +pkgname: libdisplay-info
2 +pkgver: '0.4.0'
3 +pkgrel: 1
4 +pkgdesc: EDID and DisplayID parser library (wymagana przez wlroots)
5 +url: https://gitlab.freedesktop.org/emersion/libdisplay-info
6 +arch: x86_64
7 +license:
8 +- MIT
9 +depends:
10 +- xorg-libdrm
11 +makedepends:
12 +- meson
13 +- ninja
14 +source:
15 +- https://gitlab.freedesktop.org/emersion/libdisplay-info/-/archive/${pkgver}/${pkgname}-${pkgver}.tar.gz
16 +sha256sums:
17 +- SKIP
18 +build: |
19 + cd ${pkgname}
20 +
21 + meson setup build --prefix=/usr
22 + ninja -C build
23 + DESTDIR=${PKGDIR} ninja -C build install
24 +package: |
25 + true
0 26 new file mode 100644
@@ -0,0 +1,27 @@
1 +pkgname: libliftoff
2 +pkgver: '0.5.0'
3 +pkgrel: 1
4 +pkgdesc: Lightweight KMS plane library (wymagana przez wlroots)
5 +url: https://gitlab.freedesktop.org/emersion/libliftoff
6 +arch: x86_64
7 +license:
8 +- MIT
9 +depends:
10 +- xorg-libdrm
11 +- systemd
12 +makedepends:
13 +- meson
14 +- ninja
15 +- systemd
16 +source:
17 +- https://gitlab.freedesktop.org/emersion/libliftoff/-/archive/v${pkgver}/${pkgname}-v${pkgver}.tar.gz
18 +sha256sums:
19 +- SKIP
20 +build: |
21 + cd ${pkgname}
22 +
23 + meson setup build --prefix=/usr
24 + ninja -C build
25 + DESTDIR=${PKGDIR} ninja -C build install
26 +package: |
27 + true
0 28 new file mode 100644
@@ -0,0 +1,33 @@
1 +pkgname: pagande-shell
2 +pkgver: '0.1.1'
3 +pkgrel: 1
4 +pkgdesc: PaganDE shell - własne moduły środowiska Wayland (bar, dock, launcher, wallpaper, idle) - bez GTK/Qt
5 +url: https://paganlinux.eu
6 +arch: x86_64
7 +license:
8 +- MIT
9 +depends:
10 +- wayland
11 +- cairo
12 +- pango
13 +- libxkbcommon
14 +- glib
15 +makedepends:
16 +- meson
17 +- ninja
18 +- wayland
19 +- wayland-protocols
20 +- pango
21 +- gettext
22 +source:
23 +- pagande-shell-${pkgver}.tar.gz
24 +sha256sums:
25 +- SKIP
26 +build: |
27 + cd ${pkgname}
28 +
29 + meson setup build --prefix=/usr
30 + ninja -C build
31 + DESTDIR=${PKGDIR} ninja -C build install
32 +package: |
33 + true
0 34 new file mode 100644
1 35 Binary files /dev/null and b/gui/pagande-shell/pagande-shell-0.1.1.tar.gz differ
2 36 new file mode 100644
@@ -0,0 +1,32 @@
1 +pkgname: seatd
2 +pkgver: '0.9.3'
3 +pkgrel: 1
4 +pkgdesc: Minimal seat management daemon and libseat (sesja DRM dla wlroots, backend logind)
5 +url: https://github.com/kennylevinsen/seatd
6 +arch: x86_64
7 +license:
8 +- MIT
9 +depends:
10 +- systemd
11 +makedepends:
12 +- meson
13 +- ninja
14 +- systemd
15 +source:
16 +- https://github.com/kennylevinsen/seatd/archive/refs/tags/${pkgver}.tar.gz
17 +sha256sums:
18 +- SKIP
19 +build: |
20 + cd ${pkgname}
21 +
22 + # Budujemy samą bibliotekę libseat (daemon seatd jest zbędny — PaganOS ma
23 + # systemd-logind, którego libseat używa jako backendu w runtime).
24 + meson setup build --prefix=/usr \
25 + -Dserver=disabled \
26 + -Dexamples=false \
27 + -Dlibseat-logind=enabled
28 +
29 + ninja -C build
30 + DESTDIR=${PKGDIR} ninja -C build install
31 +package: |
32 + true
0 33 new file mode 100644
@@ -0,0 +1,60 @@
1 +pkgname: wlroots
2 +pkgver: '0.20.2'
3 +pkgrel: 3
4 +pkgdesc: 'Modular Wayland compositor library (fundament labwc; patch PaganDE: zaokrąglone rogi okien + poprawka kanałów BGR888)'
5 +url: https://gitlab.freedesktop.org/wlroots/wlroots
6 +arch: x86_64
7 +license:
8 +- MIT
9 +depends:
10 +- wayland
11 +- xorg-libdrm
12 +- libinput
13 +- libxkbcommon
14 +- xorg-pixman
15 +- mesa
16 +- libdisplay-info
17 +- libliftoff
18 +- seatd
19 +- hwdata
20 +- systemd
21 +makedepends:
22 +- meson
23 +- ninja
24 +- wayland-protocols
25 +- systemd
26 +source:
27 +- https://gitlab.freedesktop.org/wlroots/wlroots/-/archive/${pkgver}/${pkgname}-${pkgver}.tar.gz
28 +- wlroots-0.20.2-rounded-clip.patch
29 +- wlroots-0.20.2-bgr888-read-pixels.patch
30 +sha256sums:
31 +- SKIP
32 +- SKIP
33 +- SKIP
34 +build: |
35 + cd ${pkgname}
36 +
37 + # PaganDE: wlr_scene_buffer_set_rounded_clip() — renderer gles2 przycina
38 + # tekstury do prostokąta z zaokrąglonymi rogami (używane przez labwc-pagan
39 + # do zaokrąglania treści okien; patrz paganDE/compositor/labwc/pagan/FORK.md).
40 + patch -p1 -i ../wlroots-0.20.2-rounded-clip.patch
41 +
42 + # PaganDE: glReadPixels(GL_RGB) dla DRM_FORMAT_BGR888 zwraca bajty R,G,B,
43 + # a układ BGR888 oczekuje B,G,R — bez tego screencopy/zrzuty miały zamienione
44 + # kanały czerwony i niebieski (widoczne na llvmpipe, gdzie preferred read
45 + # format to BGR888).
46 + patch -p1 -i ../wlroots-0.20.2-bgr888-read-pixels.patch
47 +
48 + # Renderer gles2 (mesa), backendy DRM + libinput; Xwayland i zarządzanie
49 + # kolorem wyłączone w v1 PaganDE (patrz paganDE/docs/ARCHITECTURE.md).
50 + meson setup build --prefix=/usr \
51 + -Dexamples=false \
52 + -Drenderers=gles2 \
53 + -Dbackends=drm,libinput \
54 + -Dxwayland=disabled \
55 + -Dcolor-management=disabled
56 +
57 + ninja -C build
58 + DESTDIR=${PKGDIR} ninja -C build install
59 +package: |
60 + true
0 61 new file mode 100644
@@ -0,0 +1,50 @@
1 +From: PaganDE <dev@paganlinux.org>
2 +Subject: [PATCH] gles2: fix R/B swap when reading DRM_FORMAT_BGR888
3 +
4 +GLES2 has no GL_BGR, so wlroots maps DRM_FORMAT_BGR888 to
5 +glReadPixels(GL_RGB, GL_UNSIGNED_BYTE). GL_RGB returns bytes in
6 +R,G,B order while the BGR888 layout expects B,G,R - the resulting
7 +buffer ends up with red and blue swapped.
8 +
9 +On llvmpipe the implementation read format is GL_RGB, so
10 +wlr_output_preferred_read_format() reports BGR888 and screencopy
11 +clients (e.g. our pg-shot) receive a buffer with inverted channels.
12 +
13 +Fix: after reading, swap R and B in place for BGR888 destinations.
14 +
15 +Origin: PaganDE fork of wlroots 0.20.2
16 +---
17 + render/gles2/texture.c | 16 ++++++++++++++++
18 + 1 file changed, 16 insertions(+)
19 +
20 +diff --git a/render/gles2/texture.c b/render/gles2/texture.c
21 +index 0000000..0000000 100644
22 +--- a/render/gles2/texture.c
23 +@@ -216,6 +216,22 @@ static bool gles2_texture_read_pixels(struct wlr_texture *wlr_texture,
24 + }
25 + }
26 +
27 ++ /*
28 ++ * DRM_FORMAT_BGR888 has no GLES2 equivalent: we read it as GL_RGB,
29 ++ * which returns bytes in R,G,B order, while the BGR888 layout
30 ++ * expects B,G,R. Swap the channels so the buffer actually contains
31 ++ * BGR data. Otherwise screencopy/screenshot clients see red and
32 ++ * blue swapped (visible on llvmpipe, where the preferred read
33 ++ * format is BGR888).
34 ++ */
35 ++ if (drm_fmt->drm_format == DRM_FORMAT_BGR888) {
36 ++ for (int32_t i = 0; i < src.height; ++i) {
37 ++ unsigned char *row = p + (size_t)i * options->stride;
38 ++ for (int32_t x = 0; x < src.width; ++x) {
39 ++ unsigned char tmp = row[x * 3];
40 ++ row[x * 3] = row[x * 3 + 2];
41 ++ row[x * 3 + 2] = tmp;
42 ++ }
43 ++ }
44 ++ }
45 ++
46 + wlr_egl_restore_context(&prev_ctx);
47 + pop_gles2_debug(texture->renderer);
48 +
49 + return glGetError() == GL_NO_ERROR;
0 50 new file mode 100644
@@ -0,0 +1,368 @@
1 +diff -ruN a/include/render/gles2.h b/include/render/gles2.h
2 +--- a/include/render/gles2.h 2026-07-07 23:46:09.000000000 +0200
3 +@@ -35,6 +35,12 @@
4 + GLint tex;
5 + GLint alpha;
6 + GLint pos_attrib;
7 ++ /* PaganDE: rounded clip */
8 ++ GLint rounded;
9 ++ GLint box_pos;
10 ++ GLint box_size;
11 ++ GLint radius;
12 ++ GLint corners;
13 + };
14 +
15 + struct wlr_gles2_renderer {
16 +diff -ruN a/include/wlr/render/pass.h b/include/wlr/render/pass.h
17 +--- a/include/wlr/render/pass.h 2026-07-07 23:46:09.000000000 +0200
18 +@@ -84,6 +84,14 @@
19 + WLR_SCALE_FILTER_NEAREST,
20 + };
21 +
22 ++/* PaganDE: rounded corners bitmask (see corner_radius below) */
23 ++enum wlr_rounded_corner {
24 ++ WLR_ROUNDED_CORNER_TOP_LEFT = 1 << 0,
25 ++ WLR_ROUNDED_CORNER_TOP_RIGHT = 1 << 1,
26 ++ WLR_ROUNDED_CORNER_BOTTOM_RIGHT = 1 << 2,
27 ++ WLR_ROUNDED_CORNER_BOTTOM_LEFT = 1 << 3,
28 ++};
29 ++
30 + struct wlr_render_texture_options {
31 + /* Source texture */
32 + struct wlr_texture *texture;
33 +@@ -95,6 +103,12 @@
34 + const float *alpha;
35 + /* Clip region, leave NULL to disable clipping */
36 + const pixman_region32_t *clip;
37 ++ /* PaganDE: rounded corners. When corner_radius > 0 and rounded_corners != 0,
38 ++ * the texture is clipped to a rectangle with rounded corners (bitmask of
39 ++ * enum wlr_rounded_corner). Renderers that do not support this feature
40 ++ * must ignore these fields. */
41 ++ float corner_radius;
42 ++ uint32_t rounded_corners;
43 + /* Transform applied to the source texture */
44 + enum wl_output_transform transform;
45 + /* Filtering */
46 +diff -ruN a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h
47 +--- a/include/wlr/types/wlr_scene.h 2026-07-07 23:46:09.000000000 +0200
48 +@@ -215,6 +215,11 @@
49 +
50 + // True if the underlying buffer is a wlr_single_pixel_buffer_v1
51 + bool is_single_pixel_buffer;
52 ++
53 ++ // PaganDE: rounded clip applied when rendering this buffer
54 ++ // (see wlr_scene_buffer_set_rounded_clip)
55 ++ float clip_radius;
56 ++ uint32_t clip_corners;
57 + // If is_single_pixel_buffer is set, contains the color of the buffer
58 + // as {R, G, B, A} where the max value of each component is UINT32_MAX
59 + uint32_t single_pixel_buffer_color[4];
60 +@@ -549,6 +554,16 @@
61 + enum wl_output_transform transform);
62 +
63 + /**
64 ++ * PaganDE: clip the buffer to a rectangle with rounded corners.
65 ++ *
66 ++ * radius is the corner radius in output pixels; corners is a bitmask of
67 ++ * enum wlr_rounded_corner. Passing radius 0 (or corners 0) disables the clip.
68 ++ * Renderers without support (e.g. pixman) ignore this.
69 ++ */
70 ++void wlr_scene_buffer_set_rounded_clip(struct wlr_scene_buffer *scene_buffer,
71 ++ float radius, uint32_t corners);
72 ++
73 ++/**
74 + * Sets the opacity of this buffer
75 + */
76 + void wlr_scene_buffer_set_opacity(struct wlr_scene_buffer *scene_buffer,
77 +diff -ruN a/render/gles2/pass.c b/render/gles2/pass.c
78 +--- a/render/gles2/pass.c 2026-07-07 23:46:09.000000000 +0200
79 +@@ -247,6 +247,19 @@
80 + set_proj_matrix(shader->proj, pass->projection_matrix, &dst_box);
81 + set_tex_matrix(shader->tex_proj, options->transform, &src_fbox);
82 +
83 ++ bool rounded = options->corner_radius > 0 && options->rounded_corners != 0;
84 ++ glUniform1f(shader->rounded, rounded ? 1.0f : 0.0f);
85 ++ if (rounded) {
86 ++ glUniform2f(shader->box_pos, dst_box.x, dst_box.y);
87 ++ glUniform2f(shader->box_size, dst_box.width, dst_box.height);
88 ++ glUniform1f(shader->radius, options->corner_radius);
89 ++ glUniform4f(shader->corners,
90 ++ (options->rounded_corners & WLR_ROUNDED_CORNER_TOP_LEFT) ? 1.0f : 0.0f,
91 ++ (options->rounded_corners & WLR_ROUNDED_CORNER_TOP_RIGHT) ? 1.0f : 0.0f,
92 ++ (options->rounded_corners & WLR_ROUNDED_CORNER_BOTTOM_RIGHT) ? 1.0f : 0.0f,
93 ++ (options->rounded_corners & WLR_ROUNDED_CORNER_BOTTOM_LEFT) ? 1.0f : 0.0f);
94 ++ }
95 ++
96 + render(&dst_box, options->clip, shader->pos_attrib);
97 +
98 + glBindTexture(texture->target, 0);
99 +diff -ruN a/render/gles2/renderer.c b/render/gles2/renderer.c
100 +--- a/render/gles2/renderer.c 2026-07-07 23:46:09.000000000 +0200
101 +@@ -652,6 +652,11 @@
102 + renderer->shaders.tex_rgba.tex = glGetUniformLocation(prog, "tex");
103 + renderer->shaders.tex_rgba.alpha = glGetUniformLocation(prog, "alpha");
104 + renderer->shaders.tex_rgba.pos_attrib = glGetAttribLocation(prog, "pos");
105 ++ renderer->shaders.tex_rgba.rounded = glGetUniformLocation(prog, "u_rounded");
106 ++ renderer->shaders.tex_rgba.box_pos = glGetUniformLocation(prog, "u_box_pos");
107 ++ renderer->shaders.tex_rgba.box_size = glGetUniformLocation(prog, "u_box_size");
108 ++ renderer->shaders.tex_rgba.radius = glGetUniformLocation(prog, "u_radius");
109 ++ renderer->shaders.tex_rgba.corners = glGetUniformLocation(prog, "u_corners");
110 +
111 + renderer->shaders.tex_rgbx.program = prog =
112 + link_program(renderer, common_vert_src, tex_rgbx_frag_src);
113 +@@ -663,6 +668,11 @@
114 + renderer->shaders.tex_rgbx.tex = glGetUniformLocation(prog, "tex");
115 + renderer->shaders.tex_rgbx.alpha = glGetUniformLocation(prog, "alpha");
116 + renderer->shaders.tex_rgbx.pos_attrib = glGetAttribLocation(prog, "pos");
117 ++ renderer->shaders.tex_rgbx.rounded = glGetUniformLocation(prog, "u_rounded");
118 ++ renderer->shaders.tex_rgbx.box_pos = glGetUniformLocation(prog, "u_box_pos");
119 ++ renderer->shaders.tex_rgbx.box_size = glGetUniformLocation(prog, "u_box_size");
120 ++ renderer->shaders.tex_rgbx.radius = glGetUniformLocation(prog, "u_radius");
121 ++ renderer->shaders.tex_rgbx.corners = glGetUniformLocation(prog, "u_corners");
122 +
123 + if (renderer->exts.OES_egl_image_external) {
124 + renderer->shaders.tex_ext.program = prog =
125 +@@ -675,6 +685,11 @@
126 + renderer->shaders.tex_ext.tex = glGetUniformLocation(prog, "tex");
127 + renderer->shaders.tex_ext.alpha = glGetUniformLocation(prog, "alpha");
128 + renderer->shaders.tex_ext.pos_attrib = glGetAttribLocation(prog, "pos");
129 ++ renderer->shaders.tex_ext.rounded = glGetUniformLocation(prog, "u_rounded");
130 ++ renderer->shaders.tex_ext.box_pos = glGetUniformLocation(prog, "u_box_pos");
131 ++ renderer->shaders.tex_ext.box_size = glGetUniformLocation(prog, "u_box_size");
132 ++ renderer->shaders.tex_ext.radius = glGetUniformLocation(prog, "u_radius");
133 ++ renderer->shaders.tex_ext.corners = glGetUniformLocation(prog, "u_corners");
134 + }
135 +
136 + pop_gles2_debug(renderer);
137 +diff -ruN a/render/gles2/shaders/tex_external.frag b/render/gles2/shaders/tex_external.frag
138 +--- a/render/gles2/shaders/tex_external.frag 2026-07-07 23:46:09.000000000 +0200
139 +@@ -10,6 +10,51 @@
140 + uniform samplerExternalOES texture0;
141 + uniform float alpha;
142 +
143 ++
144 ++// PaganDE: rounded clip (discard fragments outside the rounded rect)
145 ++// GLSL ES 1.00: tylko floaty (bez bitowych operacji); maska rogów w vec4.
146 ++uniform float u_rounded;
147 ++uniform vec2 u_box_pos;
148 ++uniform vec2 u_box_size;
149 ++uniform float u_radius;
150 ++uniform vec4 u_corners; /* x=TL y=TR z=BR w=BL, 1.0 = zaokrąglony */
151 ++
152 ++void rounded_clip() {
153 ++ if (u_rounded == 0.0) {
154 ++ return;
155 ++ }
156 ++ vec2 p = gl_FragCoord.xy - u_box_pos;
157 ++ float r = u_radius;
158 ++ /* top-left */
159 ++ if (u_corners.x > 0.5) {
160 ++ if (p.x <= r && p.y <= r && distance(p, vec2(r, r)) > r) {
161 ++ discard;
162 ++ }
163 ++ }
164 ++ /* top-right */
165 ++ if (u_corners.y > 0.5) {
166 ++ if (p.x >= u_box_size.x - r && p.y <= r
167 ++ && distance(p, vec2(u_box_size.x - r, r)) > r) {
168 ++ discard;
169 ++ }
170 ++ }
171 ++ /* bottom-right */
172 ++ if (u_corners.z > 0.5) {
173 ++ if (p.x >= u_box_size.x - r && p.y >= u_box_size.y - r
174 ++ && distance(p, vec2(u_box_size.x - r, u_box_size.y - r)) > r) {
175 ++ discard;
176 ++ }
177 ++ }
178 ++ /* bottom-left */
179 ++ if (u_corners.w > 0.5) {
180 ++ if (p.x <= r && p.y >= u_box_size.y - r
181 ++ && distance(p, vec2(r, u_box_size.y - r)) > r) {
182 ++ discard;
183 ++ }
184 ++ }
185 ++}
186 ++
187 + void main() {
188 ++ rounded_clip();
189 + gl_FragColor = texture2D(texture0, v_texcoord) * alpha;
190 + }
191 +diff -ruN a/render/gles2/shaders/tex_rgba.frag b/render/gles2/shaders/tex_rgba.frag
192 +--- a/render/gles2/shaders/tex_rgba.frag 2026-07-07 23:46:09.000000000 +0200
193 +@@ -8,6 +8,51 @@
194 + uniform sampler2D tex;
195 + uniform float alpha;
196 +
197 ++
198 ++// PaganDE: rounded clip (discard fragments outside the rounded rect)
199 ++// GLSL ES 1.00: tylko floaty (bez bitowych operacji); maska rogów w vec4.
200 ++uniform float u_rounded;
201 ++uniform vec2 u_box_pos;
202 ++uniform vec2 u_box_size;
203 ++uniform float u_radius;
204 ++uniform vec4 u_corners; /* x=TL y=TR z=BR w=BL, 1.0 = zaokrąglony */
205 ++
206 ++void rounded_clip() {
207 ++ if (u_rounded == 0.0) {
208 ++ return;
209 ++ }
210 ++ vec2 p = gl_FragCoord.xy - u_box_pos;
211 ++ float r = u_radius;
212 ++ /* top-left */
213 ++ if (u_corners.x > 0.5) {
214 ++ if (p.x <= r && p.y <= r && distance(p, vec2(r, r)) > r) {
215 ++ discard;
216 ++ }
217 ++ }
218 ++ /* top-right */
219 ++ if (u_corners.y > 0.5) {
220 ++ if (p.x >= u_box_size.x - r && p.y <= r
221 ++ && distance(p, vec2(u_box_size.x - r, r)) > r) {
222 ++ discard;
223 ++ }
224 ++ }
225 ++ /* bottom-right */
226 ++ if (u_corners.z > 0.5) {
227 ++ if (p.x >= u_box_size.x - r && p.y >= u_box_size.y - r
228 ++ && distance(p, vec2(u_box_size.x - r, u_box_size.y - r)) > r) {
229 ++ discard;
230 ++ }
231 ++ }
232 ++ /* bottom-left */
233 ++ if (u_corners.w > 0.5) {
234 ++ if (p.x <= r && p.y >= u_box_size.y - r
235 ++ && distance(p, vec2(r, u_box_size.y - r)) > r) {
236 ++ discard;
237 ++ }
238 ++ }
239 ++}
240 ++
241 + void main() {
242 ++ rounded_clip();
243 + gl_FragColor = texture2D(tex, v_texcoord) * alpha;
244 + }
245 +diff -ruN a/render/gles2/shaders/tex_rgbx.frag b/render/gles2/shaders/tex_rgbx.frag
246 +--- a/render/gles2/shaders/tex_rgbx.frag 2026-07-07 23:46:09.000000000 +0200
247 +@@ -8,6 +8,51 @@
248 + uniform sampler2D tex;
249 + uniform float alpha;
250 +
251 ++
252 ++// PaganDE: rounded clip (discard fragments outside the rounded rect)
253 ++// GLSL ES 1.00: tylko floaty (bez bitowych operacji); maska rogów w vec4.
254 ++uniform float u_rounded;
255 ++uniform vec2 u_box_pos;
256 ++uniform vec2 u_box_size;
257 ++uniform float u_radius;
258 ++uniform vec4 u_corners; /* x=TL y=TR z=BR w=BL, 1.0 = zaokrąglony */
259 ++
260 ++void rounded_clip() {
261 ++ if (u_rounded == 0.0) {
262 ++ return;
263 ++ }
264 ++ vec2 p = gl_FragCoord.xy - u_box_pos;
265 ++ float r = u_radius;
266 ++ /* top-left */
267 ++ if (u_corners.x > 0.5) {
268 ++ if (p.x <= r && p.y <= r && distance(p, vec2(r, r)) > r) {
269 ++ discard;
270 ++ }
271 ++ }
272 ++ /* top-right */
273 ++ if (u_corners.y > 0.5) {
274 ++ if (p.x >= u_box_size.x - r && p.y <= r
275 ++ && distance(p, vec2(u_box_size.x - r, r)) > r) {
276 ++ discard;
277 ++ }
278 ++ }
279 ++ /* bottom-right */
280 ++ if (u_corners.z > 0.5) {
281 ++ if (p.x >= u_box_size.x - r && p.y >= u_box_size.y - r
282 ++ && distance(p, vec2(u_box_size.x - r, u_box_size.y - r)) > r) {
283 ++ discard;
284 ++ }
285 ++ }
286 ++ /* bottom-left */
287 ++ if (u_corners.w > 0.5) {
288 ++ if (p.x <= r && p.y >= u_box_size.y - r
289 ++ && distance(p, vec2(r, u_box_size.y - r)) > r) {
290 ++ discard;
291 ++ }
292 ++ }
293 ++}
294 ++
295 + void main() {
296 ++ rounded_clip();
297 + gl_FragColor = vec4(texture2D(tex, v_texcoord).rgb, 1.0) * alpha;
298 + }
299 +diff -ruN a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c
300 +--- a/types/scene/wlr_scene.c 2026-07-07 23:46:09.000000000 +0200
301 +@@ -1124,6 +1124,18 @@
302 + }
303 + }
304 +
305 ++void wlr_scene_buffer_set_rounded_clip(struct wlr_scene_buffer *scene_buffer,
306 ++ float radius, uint32_t corners) {
307 ++ if (scene_buffer->clip_radius == radius
308 ++ && scene_buffer->clip_corners == corners) {
309 ++ return;
310 ++ }
311 ++ assert(radius >= 0);
312 ++ scene_buffer->clip_radius = radius;
313 ++ scene_buffer->clip_corners = corners;
314 ++ scene_node_update(&scene_buffer->node, NULL);
315 ++}
316 ++
317 + void wlr_scene_buffer_set_opacity(struct wlr_scene_buffer *scene_buffer,
318 + float opacity) {
319 + if (scene_buffer->opacity == opacity) {
320 +@@ -1499,6 +1511,26 @@
321 + case WLR_SCENE_NODE_BUFFER:;
322 + struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
323 +
324 ++ bool rounded = scene_buffer->clip_radius > 0
325 ++ && scene_buffer->clip_corners != 0;
326 ++ if (rounded) {
327 ++ /* Corner areas are no longer opaque: exclude them from the
328 ++ * opaque region so occlusion and blending stay correct. */
329 ++ int r = (int)scene_buffer->clip_radius;
330 ++ pixman_region32_t corners;
331 ++ pixman_region32_init(&corners);
332 ++ pixman_region32_union_rect(&corners, &corners,
333 ++ x, y, r, r);
334 ++ pixman_region32_union_rect(&corners, &corners,
335 ++ x + dst_box.width - r, y, r, r);
336 ++ pixman_region32_union_rect(&corners, &corners,
337 ++ x + dst_box.width - r, y + dst_box.height - r, r, r);
338 ++ pixman_region32_union_rect(&corners, &corners,
339 ++ x, y + dst_box.height - r, r, r);
340 ++ pixman_region32_subtract(&opaque, &opaque, &corners);
341 ++ pixman_region32_fini(&corners);
342 ++ }
343 ++
344 + if (scene_buffer->is_single_pixel_buffer) {
345 + // Render the buffer as a rect, this is likely to be more efficient
346 + wlr_render_pass_add_rect(data->render_pass, &(struct wlr_render_rect_options){
347 +@@ -1544,9 +1576,11 @@
348 + .dst_box = dst_box,
349 + .transform = transform,
350 + .clip = &render_region,
351 ++ .corner_radius = scene_buffer->clip_radius,
352 ++ .rounded_corners = scene_buffer->clip_corners,
353 + .alpha = &scene_buffer->opacity,
354 + .filter_mode = scene_buffer->filter_mode,
355 +- .blend_mode = !data->output->scene->calculate_visibility ||
356 ++ .blend_mode = rounded || !data->output->scene->calculate_visibility ||
357 + !pixman_region32_empty(&opaque) ?
358 + WLR_RENDER_BLEND_MODE_PREMULTIPLIED : WLR_RENDER_BLEND_MODE_NONE,
359 + .transfer_function = scene_buffer->transfer_function,