diff options
149 files changed, 0 insertions, 30148 deletions
diff --git a/gui-apps/ambient-background/ambient-background-1.0.0-r15.ebuild b/gui-apps/ambient-background/ambient-background-1.0.0-r15.ebuild deleted file mode 100644 index 6d6ee09217e8..000000000000 --- a/gui-apps/ambient-background/ambient-background-1.0.0-r15.ebuild +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright 2026 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -EAPI=8 -PYTHON_COMPAT=( python3_{13..14} ) -inherit toolchain-funcs python-single-r1 - -DESCRIPTION="Modular Wayfire live wallpapers, per-monitor idle fading and native session integration" -HOMEPAGE="" -LICENSE="MIT" -SLOT="0" -KEYWORDS="~amd64" -IUSE="+mcp +pipewire" -REQUIRED_USE="${PYTHON_REQUIRED_USE}" - -BDEPEND="dev-libs/wayland dev-libs/wayland-protocols virtual/pkgconfig" -DEPEND=" - media-libs/glm - dev-libs/json-c:= - dev-libs/wayland - >=gui-wm/wayfire-0.11.0-r59:0= - media-libs/freetype:2 - media-libs/mesa[egl(+),gles2(+)] - pipewire? ( media-video/pipewire:= ) - sys-libs/pam - x11-libs/libxkbcommon -" -RDEPEND=" - ${DEPEND} - ${PYTHON_DEPS} - media-libs/fontconfig - !gui-apps/digitalrain - >=gui-wm/wayfire-0.11.0-r60:0= -" - -src_unpack() { - mkdir -p "${S}" || die - cp -a "${FILESDIR}/${PF}/." "${S}/" || die -} - -src_compile() { - emake PIPEWIRE="$(usex pipewire 1 0)" CC="$(tc-getCC)" CXX="$(tc-getCXX)" \ - CFLAGS="${CPPFLAGS} ${CFLAGS} -std=c11" \ - CXXFLAGS="${CPPFLAGS} ${CXXFLAGS} -std=c++17" LDFLAGS="${LDFLAGS}" \ - PREFIX="${EPREFIX}/usr" -} - -src_install() { - emake PIPEWIRE="$(usex pipewire 1 0)" CC="$(tc-getCC)" CXX="$(tc-getCXX)" \ - CFLAGS="${CPPFLAGS} ${CFLAGS} -std=c11" \ - CXXFLAGS="${CPPFLAGS} ${CXXFLAGS} -std=c++17" LDFLAGS="${LDFLAGS}" \ - PREFIX="${EPREFIX}/usr" DESTDIR="${D}" install - python_fix_shebang "${ED}/usr/bin/ambient-background" - if use mcp; then - python_fix_shebang "${ED}/usr/bin/ambient-background-mcp" - else - rm "${ED}/usr/bin/ambient-background-mcp" "${ED}/usr/bin/digitalrain-mcp" || die - fi - insinto /etc - newins effects/digital-rain/digitalrain.toml.default ambient-background.toml - dosym ambient-background.toml /etc/digitalrain.toml - dodoc docs/README.md effects/digital-rain/{configuration,interfaces-and-locking,alerts-and-audio,adaptive-rendering,hide-desktop,stitched-outputs,monitor-power,video-inhibition,live-config-alerts-audio-plan}.md - dodoc effects/digital-rain/tomlc17.{LICENSE,UPSTREAM} - dodoc effects/nebula/UPSTREAM effects/nebula/LICENSE - newdoc effects/digital-rain/digitalrain.toml.default ambient-background.toml.example -} - -pkg_postinst() { - elog "WCM → Desktop → Ambient Background owns live wallpaper and idle settings." - elog "Hyperspace, Nebula and Digital Rain have independent appearance and background controls." - elog "Background defaults to None for all effects." - elog "Idle/locking/audio configuration: ~/.config/ambient-background.toml or /etc/ambient-background.toml." - elog "Documentation: ${EROOT}/usr/share/doc/${PF}/" - elog "Legacy digitalrain commands remain compatibility aliases for existing scripts." -} diff --git a/gui-apps/ambient-background/ambient-background-1.0.0-r16.ebuild b/gui-apps/ambient-background/ambient-background-1.0.0-r16.ebuild deleted file mode 100644 index 6d6ee09217e8..000000000000 --- a/gui-apps/ambient-background/ambient-background-1.0.0-r16.ebuild +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright 2026 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -EAPI=8 -PYTHON_COMPAT=( python3_{13..14} ) -inherit toolchain-funcs python-single-r1 - -DESCRIPTION="Modular Wayfire live wallpapers, per-monitor idle fading and native session integration" -HOMEPAGE="" -LICENSE="MIT" -SLOT="0" -KEYWORDS="~amd64" -IUSE="+mcp +pipewire" -REQUIRED_USE="${PYTHON_REQUIRED_USE}" - -BDEPEND="dev-libs/wayland dev-libs/wayland-protocols virtual/pkgconfig" -DEPEND=" - media-libs/glm - dev-libs/json-c:= - dev-libs/wayland - >=gui-wm/wayfire-0.11.0-r59:0= - media-libs/freetype:2 - media-libs/mesa[egl(+),gles2(+)] - pipewire? ( media-video/pipewire:= ) - sys-libs/pam - x11-libs/libxkbcommon -" -RDEPEND=" - ${DEPEND} - ${PYTHON_DEPS} - media-libs/fontconfig - !gui-apps/digitalrain - >=gui-wm/wayfire-0.11.0-r60:0= -" - -src_unpack() { - mkdir -p "${S}" || die - cp -a "${FILESDIR}/${PF}/." "${S}/" || die -} - -src_compile() { - emake PIPEWIRE="$(usex pipewire 1 0)" CC="$(tc-getCC)" CXX="$(tc-getCXX)" \ - CFLAGS="${CPPFLAGS} ${CFLAGS} -std=c11" \ - CXXFLAGS="${CPPFLAGS} ${CXXFLAGS} -std=c++17" LDFLAGS="${LDFLAGS}" \ - PREFIX="${EPREFIX}/usr" -} - -src_install() { - emake PIPEWIRE="$(usex pipewire 1 0)" CC="$(tc-getCC)" CXX="$(tc-getCXX)" \ - CFLAGS="${CPPFLAGS} ${CFLAGS} -std=c11" \ - CXXFLAGS="${CPPFLAGS} ${CXXFLAGS} -std=c++17" LDFLAGS="${LDFLAGS}" \ - PREFIX="${EPREFIX}/usr" DESTDIR="${D}" install - python_fix_shebang "${ED}/usr/bin/ambient-background" - if use mcp; then - python_fix_shebang "${ED}/usr/bin/ambient-background-mcp" - else - rm "${ED}/usr/bin/ambient-background-mcp" "${ED}/usr/bin/digitalrain-mcp" || die - fi - insinto /etc - newins effects/digital-rain/digitalrain.toml.default ambient-background.toml - dosym ambient-background.toml /etc/digitalrain.toml - dodoc docs/README.md effects/digital-rain/{configuration,interfaces-and-locking,alerts-and-audio,adaptive-rendering,hide-desktop,stitched-outputs,monitor-power,video-inhibition,live-config-alerts-audio-plan}.md - dodoc effects/digital-rain/tomlc17.{LICENSE,UPSTREAM} - dodoc effects/nebula/UPSTREAM effects/nebula/LICENSE - newdoc effects/digital-rain/digitalrain.toml.default ambient-background.toml.example -} - -pkg_postinst() { - elog "WCM → Desktop → Ambient Background owns live wallpaper and idle settings." - elog "Hyperspace, Nebula and Digital Rain have independent appearance and background controls." - elog "Background defaults to None for all effects." - elog "Idle/locking/audio configuration: ~/.config/ambient-background.toml or /etc/ambient-background.toml." - elog "Documentation: ${EROOT}/usr/share/doc/${PF}/" - elog "Legacy digitalrain commands remain compatibility aliases for existing scripts." -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/Makefile b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/Makefile deleted file mode 100644 index 2797f97a22cc..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/Makefile +++ /dev/null @@ -1,37 +0,0 @@ -CXX ?= c++ -CXXFLAGS ?= -O2 -std=c++17 -CPPFLAGS ?= -LDFLAGS ?= -PREFIX ?= /usr -DESTDIR ?= -PIPEWIRE ?= 1 -NEBULA_FILES := effects/nebula/nebula-background.cpp effects/nebula/nebula-background.hpp effects/nebula/nebula-shaders.tpp effects/nebula/nebula-field.tpp -WF_CFLAGS := $(shell pkg-config --cflags wayfire wf-utils) -WF_LIBS := $(shell pkg-config --libs wayfire wf-utils) -WF_PLUGINDIR := $(shell pkg-config --variable=plugindir wayfire) -WF_METADATADIR := $(shell pkg-config --variable=metadatadir wayfire) - -.PHONY: all rain install -all: rain libambient-background.so -rain: - $(MAKE) -C effects/digital-rain PIPEWIRE=$(PIPEWIRE) all -libambient-background.so: $(NEBULA_FILES) core/ambient-background.cpp include/ambient-api.hpp include/effect-settings.inc effects/hyperspace/hyperspace-background.cpp effects/hyperspace/hyperspace-background.hpp effects/hyperspace/hyperspace-shaders.tpp effects/hyperspace/hyperspace-anomalies.tpp effects/hyperspace/hyperspace-asteroids.tpp effects/hyperspace/hyperspace-asteroid-shaders.tpp effects/hyperspace/hyperspace-galaxies.tpp effects/hyperspace/hyperspace-galaxy-shaders.tpp - $(CXX) $(CPPFLAGS) $(CXXFLAGS) -fPIC -shared -DWAYFIRE_PLUGIN -DWLR_USE_UNSTABLE $(WF_CFLAGS) -Iinclude -Ieffects/hyperspace -Ieffects/nebula -o $@ core/ambient-background.cpp effects/hyperspace/hyperspace-background.cpp effects/nebula/nebula-background.cpp $(LDFLAGS) $(WF_LIBS) -install: all - $(MAKE) -C effects/digital-rain PIPEWIRE=$(PIPEWIRE) PREFIX=$(PREFIX) DESTDIR=$(DESTDIR) install - mv $(DESTDIR)$(PREFIX)/bin/digitalrain $(DESTDIR)$(PREFIX)/bin/ambient-idle - mv $(DESTDIR)$(PREFIX)/bin/digitalrain-lock $(DESTDIR)$(PREFIX)/bin/ambient-lock - mv $(DESTDIR)$(PREFIX)/bin/digitalrain-auth $(DESTDIR)$(PREFIX)/bin/ambient-auth - mv $(DESTDIR)$(PREFIX)/bin/digitalrain-mcp $(DESTDIR)$(PREFIX)/bin/ambient-background-mcp - ln -s ambient-idle $(DESTDIR)$(PREFIX)/bin/digitalrain - ln -s ambient-lock $(DESTDIR)$(PREFIX)/bin/digitalrain-lock - ln -s ambient-auth $(DESTDIR)$(PREFIX)/bin/digitalrain-auth - ln -s ambient-background-mcp $(DESTDIR)$(PREFIX)/bin/digitalrain-mcp -ifeq ($(PIPEWIRE),1) - mv $(DESTDIR)$(PREFIX)/bin/digitalrain-audio $(DESTDIR)$(PREFIX)/bin/ambient-audio - ln -s ambient-audio $(DESTDIR)$(PREFIX)/bin/digitalrain-audio -endif - install -Dm755 libambient-background.so $(DESTDIR)$(WF_PLUGINDIR)/libambient-background.so - install -Dm755 libambient-background.so $(DESTDIR)$(WF_PLUGINDIR)/ambient-background-r17/libambient-background.so - install -Dm755 controller/ambient-background.py $(DESTDIR)$(PREFIX)/bin/ambient-background - install -Dm644 metadata/ambient-background.xml $(DESTDIR)$(WF_METADATADIR)/ambient-background.xml diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/controller/ambient-background.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/controller/ambient-background.py deleted file mode 100755 index 44a4588800b4..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/controller/ambient-background.py +++ /dev/null @@ -1,309 +0,0 @@ -#!/usr/bin/env python3 -"""Per-physical-monitor ambient controller; no credentials or desktop capture. - -The compositor owns rendering, bindings and reversible per-output composition. This -ordinary-user process reads Digital Rain's proven activity/inhibition clocks. -A connection-bound ownership lease leaves those clocks running and restores -legacy saver behavior automatically if this process disappears. -""" -import argparse -import fcntl -import json -import os -from pathlib import Path -import signal -import socket -import struct -import subprocess -import sys -import time -import tomllib - -MAX_MESSAGE = 1024 * 1024 -STOP = False - - -def peer(stream): - pid, uid, _ = struct.unpack('3i', stream.getsockopt(socket.SOL_SOCKET, socket.SO_PEERCRED, 12)) - if uid != os.geteuid(): - raise PermissionError('Ambient IPC requires the same desktop user') - return pid - - -def connect(path, kind=socket.SOCK_STREAM): - stream = socket.socket(socket.AF_UNIX, kind) - stream.settimeout(2) - try: - stream.connect(str(path)) - peer(stream) - return stream - except BaseException: - stream.close() - raise - - -def read(stream, count): - result = bytearray() - while len(result) < count: - part = stream.recv(count - len(result)) - if not part: - raise ConnectionError('Ambient IPC disconnected') - result.extend(part) - return result - - -def ipc(method, data=None): - with connect(os.environ['WAYFIRE_SOCKET']) as stream: - payload = json.dumps({'method': method, 'data': data or {}}).encode() - stream.sendall(struct.pack('=I', len(payload)) + payload) - count, = struct.unpack('=I', read(stream, 4)) - if not 0 < count <= MAX_MESSAGE: - raise ValueError('Oversized compositor response') - result = json.loads(read(stream, count)) - if isinstance(result, dict) and result.get('error'): - raise RuntimeError(result['error']) - return result - - -def dr_request(stream, method, **data): - stream.send(json.dumps({'version': 1, 'method': method, **data}).encode()) - payload, _, flags, _ = stream.recvmsg(MAX_MESSAGE) - if flags & socket.MSG_TRUNC or not payload: - raise ConnectionError('Invalid Digital Rain response') - result = json.loads(payload) - if result.get('error'): - raise RuntimeError(result['error']) - return result - - -def clamp(value): - return min(1., max(0., value)) - - -def phase(options, output, config): - """Pure timeline: UI fades, backdrop blackens, then the animation fades. - - Legacy black delays begin at idle activation, never at daemon startup. - Zero delays retain DR's meaning of a disabled stage. - """ - if output.get('application_inhibited') or output.get('timed_inhibited'): - return {'opacity': 1., 'black': 0., 'effect': 1.} - delay = options['focused_delay'] if output['focused'] else options['inactive_delay'] - override = config.get('output_timeouts', {}).get(output['name'], 0) - if not output['focused'] and override > 0: - delay = override - age = max(0., output['idle_seconds'] - delay) - fade = max(.05, options['fade_seconds']) - alpha = 1. - clamp(age / fade) * (1. - clamp(options['min_opacity'])) - matrix = config.get('matrix', {}) - transparent = True if 'matrix' in options else matrix.get('TRANSPARENT', True) - black = 0. if transparent else 1. - effect = 1. - if options['black_enabled'] and output['idle_seconds'] >= delay: - seconds = options.get('black_fade_duration', max(1, matrix.get('FADE_FRAMES', 20)) / 30.) - black_delay = options.get('background_black_delay', matrix.get('TRANSPARENT_TIMEOUT', 600)) - if black_delay > 0: - black = clamp((age - black_delay) / seconds) - rain_delay = options.get('effect_black_delay', matrix.get('BLACK_RAIN_TIMEOUT', 600)) - black_start = black_delay + seconds if transparent else 0 - if rain_delay > 0 and (not transparent or black_delay > 0): - effect = 1. - clamp((age - black_start - rain_delay) / seconds) - return {'opacity': alpha, 'black': black, 'effect': effect} - - -class Matrix: - def __init__(self, name, config, binary): - m = config.get('matrix', {}) - args = [binary, '--render', '--ambient', '--output', name, '--transparent', - '--transparent-timeout', '0', '--black-rain-timeout', '0', '--fade-frames', '1'] - for key in ('FONT_SIZE', 'DEPTH_LAYERS', 'MIN_SPEED', 'MAX_SPEED', 'AVG_SPEED', - 'MIN_TRAIL', 'MAX_TRAIL', 'MIN_DENSITY', 'MAX_DENSITY', 'HEAD_HOLD_FRAMES', - 'HEAD_MUTATION_CHANCE', 'HEAD_WHITE_MIN', 'HEAD_WHITE_MAX', - 'LAYER_NEAR_DENSITY', 'LAYER_FAR_DENSITY'): - if key in m: - args += ['--' + key.lower().replace('_', '-'), str(m[key])] - args += ['--color', config.get('output_colors', {}).get(name, m.get('COLOR', 'green'))] - self.channel, child = socket.socketpair(socket.AF_UNIX, socket.SOCK_SEQPACKET) - self.channel.setblocking(False) - args += ['--report-fd', str(child.fileno())] - try: - self.process = subprocess.Popen(args, pass_fds=(child.fileno(),), - stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - except BaseException: - self.channel.close() - raise - finally: - child.close() - - def alive(self): - # Drain bounded renderer reports so their channel never blocks rendering. - for _ in range(8): - try: - if not self.channel.recv(8192): - break - except BlockingIOError: - break - return self.process.poll() is None - - def close(self): - self.channel.close() # EOF is the renderer's explicit lifetime boundary. - try: - self.process.wait(timeout=1) - except subprocess.TimeoutExpired: - self.process.terminate() - try: - self.process.wait(timeout=1) - except subprocess.TimeoutExpired: - self.process.kill() - self.process.wait(timeout=1) - - -class Controller: - def __init__(self, binary): - self.binary = binary - self.dr = None - self.matrix = {} - self.config = {} - self.config_key = None - self.fingerprint = None - self.generation = None - self.error = None - self.manager_process = None - self.last_manager_start = 0. - - def release(self): - for child in self.matrix.values(): - child.close() - self.matrix.clear() - if self.dr: - self.dr.close() - self.dr = None - - def tick(self, options): - if not options['enabled']: - self.release() - return - if not self.dr: - socket_path = Path(os.environ['XDG_RUNTIME_DIR']) / 'digitalrain/control.sock' - if not socket_path.exists() and time.monotonic() - self.last_manager_start > 10: - self.last_manager_start = time.monotonic() - self.manager_process = subprocess.Popen([self.binary], stdin=subprocess.DEVNULL, - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True) - self.dr = connect(socket_path, socket.SOCK_SEQPACKET) - dr_request(self.dr, 'ambient', enabled=True) - generation = options['reset_generation'] - if self.generation is not None and generation != self.generation: - dr_request(self.dr, 'wake') - self.generation = generation - status = dr_request(self.dr, 'status') - path = status.get('config_file', '') - key = (path, status.get('settings_revision')) - if key != self.config_key: - # DR validates edits before changing this revision. Retain the last - # valid settings if an editor is midway through a replacement. - try: - with open(path, 'rb') as handle: - candidate = tomllib.load(handle) - # Scalars come from the manager's validated current settings, - # including runtime API changes and defaults absent from TOML. - for qualified, entry in dr_request(self.dr, 'schema').items(): - group, name = qualified.split('.', 1) - candidate.setdefault(group, {})[name] = entry['value'] - self.config = candidate - self.config_key = key - except (OSError, ValueError): - pass - targets = {item['name'] for item in options['outputs']} - wanted = targets if options['backend'] == 'matrix' and status['lock'] == 'unlocked' else set() - appearance = self.config.copy() - if 'matrix' in options: - appearance['matrix'] = options['matrix'].copy() - if not appearance['matrix'].get('MONITOR_COLOURS', True): - appearance['output_colors'] = {} - # Keep temporary cross-field edits valid while changing sliders. - m = appearance['matrix'] - for minimum, maximum in [('MIN_SPEED', 'MAX_SPEED'), ('MIN_TRAIL', 'MAX_TRAIL'), - ('MIN_DENSITY', 'MAX_DENSITY'), ('HEAD_WHITE_MIN', 'HEAD_WHITE_MAX')]: - m[maximum] = max(m[minimum], m[maximum]) - m['AVG_SPEED'] = min(m['MAX_SPEED'], max(m['MIN_SPEED'], m['AVG_SPEED'])) - # Backdrop controls are composited natively and do not restart glyphs. - for key in ('BACKGROUND', 'BACKGROUND_OPACITY', 'MONITOR_COLOURS'): - m.pop(key, None) - fingerprint = json.dumps([appearance.get('matrix', {}), appearance.get('output_colors', {})], sort_keys=True) - for name in list(self.matrix): - if name not in wanted or fingerprint != self.fingerprint or not self.matrix[name].alive(): - self.matrix.pop(name).close() - for name in sorted(wanted): - if name not in self.matrix: - self.matrix[name] = Matrix(name, appearance, self.binary) - self.fingerprint = fingerprint - activity = {item['name']: item for item in status['outputs']} - updates = [] - for name in sorted(targets): - output = activity.get(name) - state = phase(options, output, self.config) if output and status['lock'] == 'unlocked' else { - 'opacity': 1., 'black': 0., 'effect': 1.} - updates.append({'name': name, **state}) - ipc('ambient/sync', {'outputs': updates}) - self.error = None - - -def main(): - parser = argparse.ArgumentParser(description='Continuous ambient background and per-monitor desktop fading') - parser.add_argument('--daemon', action='store_true', help='run the compositor helper') - parser.add_argument('--toggle', action='store_true', help='toggle ambient-only mode on all monitors') - parser.add_argument('--restore', action='store_true', help='restore desktop elements and keyboard focus') - parser.add_argument('--status', action='store_true') - parser.add_argument('--renderer', '--digitalrain', dest='digitalrain', default='/usr/bin/ambient-idle', help=argparse.SUPPRESS) - args = parser.parse_args() - if args.toggle or args.restore or args.status: - print(json.dumps(ipc('ambient/toggle' if args.toggle else 'ambient/restore' if args.restore else 'ambient/status'), indent=2)) - return - if not args.daemon: - parser.print_help() - return - if os.geteuid() == 0: - parser.error('Run the ambient controller as the desktop user') - # One daemon per compositor, including independent test sessions. - runtime = Path(os.environ['XDG_RUNTIME_DIR']) - with connect(os.environ['WAYFIRE_SOCKET']) as stream: - pid = peer(stream) - handle = open(runtime / f'wayfire-ambient-{pid}.lock', 'w') - try: - fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB) - except BlockingIOError: - return - def stop(*_): - global STOP - STOP = True - signal.signal(signal.SIGTERM, stop) - signal.signal(signal.SIGINT, stop) - controller = Controller(args.digitalrain) - try: - while not STOP: - try: - options = ipc('ambient/status') - except (OSError, RuntimeError, ValueError): - break # compositor exit or plugin unload - try: - controller.tick(options) - except (OSError, RuntimeError, ValueError) as error: - controller.release() - message = str(error) - if message != controller.error: - print('Ambient controller:', message, file=sys.stderr, flush=True) - controller.error = message - # Fail open immediately; the plugin also has an independent watchdog. - try: - ipc('ambient/sync', {'outputs': [{'name': z['name'], 'opacity': 1., 'black': 0., 'effect': 1.} - for z in options['outputs']]}) - except (OSError, RuntimeError, ValueError): - break - time.sleep(.25) - finally: - controller.release() - handle.close() - - -if __name__ == '__main__': - main() diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/core/ambient-background.cpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/core/ambient-background.cpp deleted file mode 100644 index 93c94e2e2d1f..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/core/ambient-background.cpp +++ /dev/null @@ -1,497 +0,0 @@ -#include "ambient-api.hpp" -#include "hyperspace-background.hpp" -#include "nebula-background.hpp" -#include <wayfire/core.hpp> -#include <wayfire/config/config-manager.hpp> -#include <wayfire/scene-operations.hpp> -#include <wayfire/output.hpp> -#include <wayfire/output-layout.hpp> -#include <wayfire/output-stitch.hpp> -#include <wayfire/plugin.hpp> -#include <wayfire/render-manager.hpp> -#include <wayfire/scene.hpp> -#include <wayfire/scene-render.hpp> -#include <wayfire/seat.hpp> -#include <wayfire/signal-definitions.hpp> -#include <wayfire/view.hpp> -#include <wayfire/plugins/common/shared-core-data.hpp> -#include <wayfire/plugins/ipc/ipc-method-repository.hpp> -#include <algorithm> -#include <chrono> -#include <cmath> -#include <map> -#include <vector> -extern "C" { -#include <wlr/types/wlr_compositor.h> -#include <wlr/types/wlr_keyboard.h> -} - -namespace { -using clock_t = std::chrono::steady_clock; -double now() { return std::chrono::duration<double>(clock_t::now().time_since_epoch()).count(); } -struct zone_t { - std::string name; - wf::geometry_t box; - float opacity = 1, target = 1, black = 0, effect = 1; -}; -class ambient_node_t : public wf::scene::node_t { - wf::output_t *output; - std::function<void(const wf::render_target_t&, const wf::regionf_t&)> paint; - std::function<wf::regionf_t()> coverage; - public: - ambient_node_t(wf::output_t *o, decltype(paint) p, decltype(coverage) c = {}) : - node_t(false), output(o), paint(p), coverage(c) {} - wf::geometry_t get_bounding_box() override { return output->get_layout_geometry(); } - class instance_t : public wf::scene::render_instance_t { - ambient_node_t *self; - public: - explicit instance_t(ambient_node_t *s) : self(s) {} - void schedule_instructions(std::vector<wf::scene::render_instruction_t>& list, - const wf::render_target_t& target, wf::regionf_t& damage) override { - auto clipped = damage & self->get_bounding_box(); - if (self->coverage) clipped &= self->coverage(); - if (clipped.empty()) return; - list.push_back({.instance=this, .target=target, .damage=clipped}); - // A translucent output cover must retain the desktop damage below. - if (!self->coverage) damage ^= clipped; - } - void render(const wf::scene::render_instruction_t& data) override { - auto box = self->get_bounding_box(); - auto damage = data.damage; - damage += wf::pointf_t{-double(box.x), -double(box.y)}; - auto target = data.target.translated({-box.x, -box.y}); - data.pass->custom_gles_subpass([&] { self->paint(target, damage); }); - } - }; - void gen_render_instances(std::vector<wf::scene::render_instance_uptr>& list, - wf::scene::damage_callback, wf::output_t *shown_on) override { - if (shown_on == output) list.push_back(std::make_unique<instance_t>(this)); - } -}; -class shield_t : public wf::scene::node_t { - std::function<std::vector<wf::geometry_t>()> regions; - public: - explicit shield_t(decltype(regions) fn) : node_t(false), regions(fn) {} - std::optional<wf::scene::input_node_t> find_node_at(const wf::pointf_t& point) override { - for (auto b : regions()) - if (point.x >= b.x && point.y >= b.y && point.x < b.x+b.width && point.y < b.y+b.height) - return wf::scene::input_node_t{this, point}; - return {}; - } -}; -class ambient_plugin_t : public wf::plugin_interface_t { - wf::option_wrapper_t<bool> enabled, black_enabled; - wf::option_wrapper_t<std::string> backend, controller; - wf::option_wrapper_t<double> focused_delay, inactive_delay, fade_seconds, wake_seconds, - min_opacity, manual_seconds; - wf::option_wrapper_t<wf::activatorbinding_t> toggle_binding, restore_binding; - wf::shared_data::ref_ptr_t<wf::ipc::method_repository_t> repo; - struct output_state_t { - std::vector<zone_t> zones; - std::unique_ptr<ambient_hyperspace_background> hyperspace; - std::unique_ptr<ambient_nebula_background> nebula; - wf::scene::node_ptr node, fade_node; - wf::auxilliary_buffer_t fade_buffer; - }; - std::map<wf::output_t*, output_state_t> outputs; - wf::scene::node_ptr shield; - wf::wl_timer<true> timer; - bool manual = false, daemon_started = false, cursor_hidden = false; - unsigned reset_generation = 0; - double last_tick = now(), heartbeat = 0, restore_until = 0, last_launch = 0; - std::weak_ptr<wf::view_interface_t> previous_focus; - - template<typename T> void register_setting(const char *name, T value) { - auto section = wf::get_core().config->get_section("ambient-background"); - if (!section) { - section = std::make_shared<wf::config::section_t>("ambient-background"); - wf::get_core().config->merge_section(section); - } - auto old = section->get_option_or(name); - if (!std::dynamic_pointer_cast<wf::config::option_t<T>>(old)) { - auto typed = std::make_shared<wf::config::option_t<T>>(name, value); - if (old) typed->set_value_str(old->get_value_str()); - section->register_new_option(typed); - } - } - template<typename T> void option(wf::option_wrapper_t<T>& wrapper, const char *name, T value) { - register_setting<T>(name, value); - wrapper.load_option(std::string("ambient-background/") + name); - } - template<typename T> T setting(const char *name) const { - auto option = wf::get_core().config->get_option(std::string("ambient-background/") + name); - return std::dynamic_pointer_cast<wf::config::option_t<T>>(option)->get_value(); - } - static void insert_before(wf::scene::node_ptr node, wf::scene::layer layer) { - auto root = wf::get_core().scene(); - auto children = root->get_children(); - auto anchor = root->layers[(int)layer]; - auto it = std::find(children.begin(), children.end(), anchor); - children.insert(it, node); - root->set_children_list(children); - } - bool locked() const { - for (auto& [o, s] : outputs) if (o->is_inhibited()) return true; - return false; - } - static std::vector<wf::output_stitch_zone_t> targets(wf::output_t *o) { - if (auto stitch = o->get_data<wf::output_stitch_layout_t>()) - if (!stitch->zones.empty()) return stitch->zones; - return {{o->to_string(), o->get_relative_geometry()}}; - } - void update_zones(wf::output_t *o, output_state_t& state) { - auto old = state.zones; - state.zones.clear(); - for (auto z : targets(o)) { - zone_t zone{z.name, z.geometry}; - for (auto& p : old) if (p.name == zone.name) { - zone.opacity=p.opacity; zone.target=p.target; zone.black=p.black; zone.effect=p.effect; - } - state.zones.push_back(zone); - } - } - void add_output(wf::output_t *o) { - if (outputs.count(o)) return; - auto& state = outputs[o]; - update_zones(o, state); - state.hyperspace = std::make_unique<ambient_hyperspace_background>([o] { o->render->damage_whole(); }); - state.nebula = std::make_unique<ambient_nebula_background>([o] { o->render->damage_whole(); }); - state.nebula->set_active(enabled && std::string(backend) == "nebula"); - state.hyperspace->set_active(enabled && std::string(backend) == "hyperspace"); - o->add_activator(toggle_binding, &toggle); - o->add_activator(restore_binding, &restore); - if (enabled) attach(o, state); - } - void attach(wf::output_t *o, output_state_t& state) { - if (state.node) return; - state.node = std::make_shared<ambient_node_t>(o, [this,o] (auto& t,auto& d) { paint(o,t,d); }); - insert_before(state.node, wf::scene::layer::BOTTOM); - state.fade_node = std::make_shared<ambient_node_t>(o, - [this,o] (auto& t,auto& d) { paint_fade(o,t,d); }, - [this,o] { return fade_coverage(o); }); - // Blend above the completed desktop (including Cube and launchers), - // but below screensavers and the protected session-lock layer. - insert_before(state.fade_node, wf::scene::layer::OVERLAY); - o->render->damage_whole(); - } - static bool belongs(wayfire_view view, wf::output_t *output, bool wallpaper = false) { - if (!view->is_mapped()) return false; - using layer = wf::scene::layer; - auto layers = wallpaper ? std::vector<layer>{layer::BACKGROUND} : - std::vector<layer>{layer::BOTTOM,layer::WORKSPACE,layer::TOP,layer::UNMANAGED,layer::OVERLAY,layer::DWIDGET}; - for (wf::scene::node_t *n=view->get_root_node().get(); n; n=n->parent()) - for (auto l:layers) if (n == output->node_for_layer(l).get()) return true; - return false; - } - wf::regionf_t fade_coverage(wf::output_t *o) { - wf::regionf_t covered; - if (!enabled || locked() || !outputs.count(o)) return covered; - auto offset = wf::origin(o->get_layout_geometry()); - for (const auto& z:outputs.at(o).zones) - if (z.opacity < 1.0f) covered |= z.box + offset; - return covered; - } - void paint_fade(wf::output_t *o, const wf::render_target_t& target, const wf::regionf_t& damage) { - if (!enabled || locked() || !outputs.count(o)) return; - auto& state = outputs.at(o); - bool needed = false; - for (const auto& z:state.zones) needed |= z.opacity < 1.0f; - if (!needed) return; - auto box = o->get_relative_geometry(); - state.fade_buffer.allocate(wf::dimensions(box), target.scale); - if (!state.fade_buffer.get_texture()) return; - wf::render_target_t image{state.fade_buffer}; - image.geometry = box; image.scale = target.scale; - wf::gles::bind_render_buffer(image); - // Reuse the same wallpaper renderer as the desktop and Cube backdrop. - // Composite its complete opaque image once, avoiding separate star/cloud - // alpha passes that would darken the desktop nonlinearly. - paint(o, image, box); - wf::gles::bind_render_buffer(target); - GL_CALL(glDisable(GL_DEPTH_TEST)); GL_CALL(glDisable(GL_CULL_FACE)); - auto texture = wf::gles_texture_t::from_aux(state.fade_buffer); - for (const auto& z:state.zones) { - float alpha = std::clamp(1.0f-z.opacity, 0.0f, 1.0f); - if (alpha <= 0) continue; - wf::gles::for_each_scissor_rect(target, damage & z.box, [&] { - OpenGL::render_texture(texture, target, box, glm::vec4(1,1,1,alpha)); - }); - } - GL_CALL(glDisable(GL_BLEND)); - } - void update_shield() { - auto cursor=wf::get_core().get_cursor_position(); - bool hide=manual; - for (auto& [o,s]:outputs) { - auto layout=o->get_layout_geometry(); - for (auto& z:s.zones) { - auto b=z.box;b.x+=layout.x;b.y+=layout.y; - if ((b & cursor) && z.opacity<.02f) hide=true; - } - } - hide &= bool(enabled) && !locked(); - if (hide != cursor_hidden) { - if (hide) wf::get_core().hide_cursor(); else wf::get_core().unhide_cursor(); - cursor_hidden=hide; - } - bool needed = manual; - for (auto& [o,s]:outputs) for (auto& z:s.zones) needed |= z.opacity < .02f; - needed &= bool(enabled) && !locked(); - if (needed && !shield) { - shield=std::make_shared<shield_t>([this] { - std::vector<wf::geometry_t> result; - for (auto& [o,s]:outputs) { - auto layout=o->get_layout_geometry(); - for (auto& z:s.zones) if (manual || z.opacity < .02f) { - auto b=z.box; b.x+=layout.x; b.y+=layout.y; result.push_back(b); - } - } - return result; - }); - insert_before(shield,wf::scene::layer::SCREENSAVER); - } else if (!needed && shield) { - wf::scene::remove_child(shield); shield.reset(); - } - if (shield) wf::scene::update(shield,wf::scene::update_flag::INPUT_STATE); - } - void set_manual(bool state) { - if (state == manual) return; - if (state && (!enabled || locked())) return; - manual=state; - if (state) { if (auto v=wf::get_core().seat->get_active_view()) previous_focus=v->weak_from_this(); } - else { - ++reset_generation; restore_until=now()+1; - for (auto& [o,s]:outputs) for (auto& z:s.zones) { z.target=1; z.black=0; z.effect=1; } - if (auto view=previous_focus.lock()) if (view->is_mapped()) wf::get_core().seat->focus_view(view->self()); - previous_focus.reset(); - } - update_shield(); - } - wf::activator_callback toggle = [this] (auto) { if (!enabled || locked()) return false; set_manual(!manual); return true; }; - wf::activator_callback restore = [this] (auto) { if (!manual) return false; set_manual(false); return true; }; - wf::signal::connection_t<wf::input_event_signal<wlr_keyboard_key_event>> on_key = [this] (auto ev) { - if (manual && !locked()) ev->mode=wf::input_event_processing_mode_t::NO_CLIENT; - }; - void paint(wf::output_t *o,const wf::render_target_t& target,const wf::regionf_t& damage) { - auto it=outputs.find(o); if (it==outputs.end() || !enabled || locked()) return; - auto& state=it->second; - GL_CALL(glDisable(GL_DEPTH_TEST)); GL_CALL(glDisable(GL_CULL_FACE)); - wf::gles::for_each_scissor_rect(target,damage,[&] { - OpenGL::render_rectangle(o->get_relative_geometry(),{0,0,0,1},wf::gles::render_target_orthographic_projection(target)); - }); - auto prefix = std::string(backend); - if (prefix != "hyperspace" && prefix != "nebula") prefix = "matrix"; - bool wallpaper = setting<std::string>((std::string(prefix) + "_background").c_str()) == "wallpaper"; - float wallpaper_alpha = std::clamp(setting<double>((std::string(prefix) + "_background_opacity").c_str()), 0.0, 1.0); - auto draw_background_clients = [&](bool matrix) { - // Import only wallpaper or this module's Matrix GPU buffers. - for (auto view:wf::get_core().get_all_views()) { - if (!belongs(view,o,true) || !view->get_wlr_surface()) continue; - bool ambient=view->get_app_id() == "ambient-matrix"; - if (ambient != matrix) continue; - auto texture=wlr_surface_get_texture(view->get_wlr_surface()); - if (!texture) continue; - auto box=view->get_bounding_box(); - wf::gles::for_each_scissor_rect(target,damage & box,[&] { - OpenGL::render_texture(wf::gles_texture_t(texture),target,box, - glm::vec4(1,1,1,matrix ? 1.0f : wallpaper_alpha)); - }); - } - }; - if (wallpaper && wallpaper_alpha > 0) draw_background_clients(false); - for (auto& z:state.zones) { - float black=manual ? 0 : z.black; - if (black>0) wf::gles::for_each_scissor_rect(target,damage & z.box,[&] { - OpenGL::render_rectangle(z.box,{0,0,0,black},wf::gles::render_target_orthographic_projection(target)); - }); - } - if (std::string(backend) == "hyperspace") - state.hyperspace->render(target,damage,wf::dimensions(o->get_relative_geometry())); - else if (std::string(backend) == "nebula") - state.nebula->render(target,damage,wf::dimensions(o->get_relative_geometry())); - else draw_background_clients(true); - for (auto& z:state.zones) { - auto d=damage & z.box; - float alpha=manual ? 0 : 1-z.effect; - if (alpha > 0) wf::gles::for_each_scissor_rect(target,d,[&] { - OpenGL::render_rectangle(z.box,{0,0,0,alpha},wf::gles::render_target_orthographic_projection(target)); - }); - } - GL_CALL(glDisable(GL_BLEND)); - } - bool tick() { - double t=now(), dt=std::clamp(t-last_tick,0.0,.1); last_tick=t; - if (locked() && manual) set_manual(false); - bool stale=(heartbeat ? t-heartbeat>3 : daemon_started && t-last_launch>3); - if (stale && manual) set_manual(false); - for (auto& [o,s]:outputs) { - update_zones(o,s); - s.hyperspace->set_active(enabled && !locked() && std::string(backend)=="hyperspace"); - s.nebula->set_active(enabled && !locked() && std::string(backend)=="nebula"); - for (auto& z:s.zones) { - float target = manual ? 0 : (stale || t<restore_until || locked() ? 1 : z.target); - double duration=manual ? double(manual_seconds) : double(wake_seconds); - z.opacity += std::clamp(target-z.opacity,float(-dt/std::max(.05,duration)),float(dt/std::max(.05,duration))); - z.opacity=std::clamp(z.opacity,0.f,1.f); - if (stale) { z.target=1;z.black=0;z.effect=1; } - } - // The locker owns animation while protected. Do not keep waking - // the desktop renderer behind a black or powered-off lock screen. - if (enabled && !locked()) o->render->damage_whole(); - } - update_shield(); - if (enabled && stale && !std::string(controller).empty() && t-last_launch>10) { - last_launch=t;wf::get_core().run(std::string(controller)); - } - return true; - } - wf::json_t status() { - wf::json_t j=wf::ipc::json_ok(); - j["enabled"]=bool(enabled);j["backend"]=std::string(backend);j["manual"]=manual; - j["fade_scope"]=std::string("output"); - j["reset_generation"]=int(reset_generation);j["black_enabled"]=bool(black_enabled); - j["focused_delay"]=double(focused_delay);j["inactive_delay"]=double(inactive_delay); - j["fade_seconds"]=double(fade_seconds);j["wake_seconds"]=double(wake_seconds); - j["min_opacity"]=double(min_opacity);j["manual_seconds"]=double(manual_seconds); - j["background_black_delay"]=setting<double>("background_black_delay"); - j["effect_black_delay"]=setting<double>("effect_black_delay"); - j["black_fade_duration"]=setting<double>("black_fade_duration"); - wf::json_t matrix, lock_screen, hyperspace, nebula; - auto section=wf::get_core().config->get_section("ambient-background"); - for (auto option:section->get_registered_options()) { - auto name=option->get_name(); - if (name.rfind("lock_",0)==0 || name.rfind("hyperspace_",0)==0 || name.rfind("nebula_",0)==0) { - auto& group=name.rfind("lock_",0)==0 ? lock_screen : name.rfind("nebula_",0)==0 ? nebula : hyperspace; - auto key=name.rfind("lock_",0)==0 ? name.substr(5) : name; - if (auto value=std::dynamic_pointer_cast<wf::config::option_t<double>>(option)) group[key]=value->get_value(); - else if (auto value=std::dynamic_pointer_cast<wf::config::option_t<int>>(option)) group[key]=value->get_value(); - else if (auto value=std::dynamic_pointer_cast<wf::config::option_t<bool>>(option)) group[key]=value->get_value(); - else group[key]=option->get_value_str(); - } - if (name.rfind("matrix_",0)!=0) continue; - name=name.substr(7); - std::transform(name.begin(),name.end(),name.begin(),[](unsigned char c){return std::toupper(c);}); - if (auto value=std::dynamic_pointer_cast<wf::config::option_t<double>>(option)) matrix[name]=value->get_value(); - else if (auto value=std::dynamic_pointer_cast<wf::config::option_t<int>>(option)) matrix[name]=value->get_value(); - else if (auto value=std::dynamic_pointer_cast<wf::config::option_t<bool>>(option)) matrix[name]=value->get_value(); - else matrix[name]=option->get_value_str(); - } - j["matrix"]=matrix; - j["lock_screen"]=lock_screen;j["hyperspace"]=hyperspace;j["nebula"]=nebula; - j["scenes"]=wf::json_t(); - for (auto& [o,s]:outputs) if (s.hyperspace->has_scene()) { - wf::json_t scene=wf::json_t::array(); - for (double v:s.hyperspace->scene()) scene.append(v); - j["scenes"][o->to_string()]=scene; - } - j["nebula_scenes"]=wf::json_t(); - for (auto& [o,s]:outputs) if (s.nebula->has_scene()) { - wf::json_t scene=wf::json_t::array(); - for (double v:s.nebula->scene()) scene.append(v); - j["nebula_scenes"][o->to_string()]=scene; - } - j["outputs"]=wf::json_t::array(); - for (auto& [o,s]:outputs) for (auto& z:s.zones) { - wf::json_t item;item["name"]=z.name;item["desktop"]=o->to_string(); - item["opacity"]=double(z.opacity);item["target"]=double(z.target); - j["outputs"].append(item); - } - return j; - } - void changed() { - if (!enabled) set_manual(false); - for (auto& [o,s]:outputs) { - s.hyperspace->set_active(enabled && std::string(backend)=="hyperspace"); - s.nebula->set_active(enabled && std::string(backend)=="nebula"); - if (enabled) attach(o,s); - else if (s.node) { - wf::scene::remove_child(s.node);s.node.reset(); - wf::scene::remove_child(s.fade_node);s.fade_node.reset(); - wf::gles::run_in_context([&] { s.fade_buffer.free(); }); - } - o->render->damage_whole(); - } - update_shield(); - wf::ambient_changed_signal signal;wf::get_core().emit(&signal); - if (enabled && !daemon_started && !std::string(controller).empty()) { - daemon_started=true;last_launch=now();wf::get_core().run(std::string(controller)); - } - } - wf::signal::connection_t<wf::ambient_query_signal> query = [this] (auto ev) { - ev->enabled=enabled && outputs.count(ev->output); - }; - wf::signal::connection_t<wf::ambient_render_signal> render = [this] (auto ev) { - if (enabled && outputs.count(ev->output)) { paint(ev->output,*ev->target,*ev->damage);ev->handled=true; } - }; - wf::signal::connection_t<wf::ambient_transition_signal> transition = [this] (auto ev) { - if (outputs.count(ev->output)) outputs.at(ev->output).hyperspace->set_transition(ev->moving); - }; - wf::signal::connection_t<wf::output_added_signal> added = [this] (auto ev) { add_output(ev->output); }; - wf::signal::connection_t<wf::output_pre_remove_signal> removed = [this] (auto ev) { - if (outputs.count(ev->output)) { - ev->output->rem_binding(&toggle);ev->output->rem_binding(&restore); - auto& s=outputs.at(ev->output); - if(s.node)wf::scene::remove_child(s.node); - if(s.fade_node)wf::scene::remove_child(s.fade_node); - outputs.erase(ev->output); - } - }; - public: - void init() override { - #include "effect-settings.inc" - option(enabled,"enabled",false);option(black_enabled,"fade_to_black",true); - option(backend,"backend",std::string("hyperspace")); - option(controller,"controller",std::string("ambient-background --daemon")); - option(focused_delay,"focused_delay",240.0);option(inactive_delay,"inactive_delay",120.0); - option(fade_seconds,"fade_duration",20.0);option(wake_seconds,"restore_duration",.25); - option(min_opacity,"minimum_opacity",0.0);option(manual_seconds,"manual_duration",.7); - option(toggle_binding,"toggle",wf::activatorbinding_t{}); - option(restore_binding,"restore",wf::option_type::from_string<wf::activatorbinding_t>("KEY_ESC").value()); - for (auto o:wf::get_core().output_layout->get_outputs()) add_output(o); - wf::get_core().output_layout->connect(&added);wf::get_core().output_layout->connect(&removed); - wf::get_core().connect(&query);wf::get_core().connect(&render);wf::get_core().connect(&transition); - wf::get_core().connect(&on_key); - enabled.set_callback([this]{changed();});backend.set_callback([this]{changed();}); - repo->register_method("ambient/status",[this](wf::json_t){return status();}); - repo->register_method("ambient/toggle",[this](wf::json_t){set_manual(!manual);return status();}); - repo->register_method("ambient/restore",[this](wf::json_t){set_manual(false);return status();}); - repo->register_method("ambient/sync",[this](wf::json_t data){ - if (!data.has_member("outputs") || !data["outputs"].is_array())return wf::ipc::json_error("outputs array required"); - for (size_t i=0;i<data["outputs"].size();i++) { - wf::json_t item=data["outputs"][i]; - if (!item.has_member("name") || !item["name"].is_string())return wf::ipc::json_error("name required"); - for (auto key:{"opacity","black","effect"}) { - if (!item.has_member(key) || (!item[key].is_int() && !item[key].is_double()) || - !std::isfinite(item[key].as_double()) || item[key].as_double()<0 || item[key].as_double()>1) - return wf::ipc::json_error("opacity, black and effect must be in [0,1]"); - } - } - for (size_t i=0;i<data["outputs"].size();i++) { - wf::json_t item=data["outputs"][i]; - for(auto& [o,s]:outputs)for(auto& z:s.zones)if(z.name==item["name"].as_string()) { - z.target=item["opacity"].as_double();z.black=item["black"].as_double();z.effect=item["effect"].as_double(); - } - } - heartbeat=now();return wf::ipc::json_ok(); - }); - changed();timer.set_timeout(33,[this]{return tick();}); - } - void fini() override { - timer.disconnect();enabled.set_callback(nullptr);backend.set_callback(nullptr); - set_manual(false); - if(cursor_hidden) { wf::get_core().unhide_cursor();cursor_hidden=false; } - if(shield) {wf::scene::remove_child(shield);shield.reset();} - query.disconnect();render.disconnect();transition.disconnect();on_key.disconnect();added.disconnect();removed.disconnect(); - for(auto& [o,s]:outputs) { - o->rem_binding(&toggle);o->rem_binding(&restore); - if(s.node)wf::scene::remove_child(s.node); - if(s.fade_node)wf::scene::remove_child(s.fade_node); - o->render->damage_whole(); - } - outputs.clear(); - for(auto m:{"ambient/status","ambient/toggle","ambient/restore","ambient/sync"})repo->unregister_method(m); - wf::ambient_changed_signal signal;wf::get_core().emit(&signal); - } -}; -} -DECLARE_WAYFIRE_PLUGIN(ambient_plugin_t); diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/docs/README.md b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/docs/README.md deleted file mode 100644 index 28f62c10d79d..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/docs/README.md +++ /dev/null @@ -1,79 +0,0 @@ -# Ambient Background - -One Wayfire plugin and Gentoo package provide continuous Hyperspace and Digital Rain live wallpapers, per-physical-monitor desktop fading, manual ambient-only mode, and native idle/session integration. Effects have separate preferences and default to no desktop wallpaper behind them. - -WCM → Desktop → Ambient Background has Desktop and idle, Hyperspace, Digital Rain, Lock screen, and Integration tabs. Each effect has a background selector (None or Desktop wallpaper) and wallpaper opacity. None keeps the animated effect and removes the static wallpaper. Hyperspace owns its travel, stars, dust, nebula, pulsar, asteroid, galaxy and quality controls here; changing them does not change Cube's independent fallback effect. Digital Rain owns glyphs, depth, motion, trails, density and colour controls here. - -Hyperspace clouds are separate seeded dust volumes with torn edges, dark holes and varied depths. Stars span the full viewing frustum, with a middle-distance population joining nearby trails to the distant background. Their density follows Passing stars. Cloud shapes stay fixed as the camera approaches; banks expand and move outwards in perspective, fading before they recycle beyond the horizon. Cloud colour variation blends the chosen Hyperspace tint with seeded blue, violet, teal, rose and occasional amber banks; zero uses only the chosen tint, while one uses the full varied palette. Colours stay attached to their banks during travel. The glow control lights dust and stars. Pulsars retain their own irregular event schedule. Rare anomalies adds occasional distant binary pairs and broken glowing remnants on a separate schedule, with gradual fades and quiet intervals. Its intensity can be set to zero independently of pulsars. Speed zero freezes clouds, particles and all encounters. - -Distant galaxy density adds a sparse deep population of spiral, inclined disc and elliptical galaxies between the distant starfield and passing stars. Seeded cores, dusty arms, star clusters, inclinations, sizes and warm/cool stellar colours remain attached to each galaxy. Their fixed world positions expand slowly outwards in perspective. Distant star drift controls their unboosted motion; zero holds galaxies and distant stars still. Soft near/far fades hide recycling, including the bounded drift-clock wrap. Zero galaxy density removes this layer without resetting the journey or other populations. Small projected quads keep their GPU work local. Cube and the native lock use the same shared layer. - -Asteroid field density adds intermittent fields of solid procedural rocks. Each body has a seeded irregular mesh, broken planar faces, craters, fractured strata, chipped mineral patches and fine surface relief under matte lighting. Texture and lighting follow each body's slow tumble. Charcoal, stony grey and occasional iron-rich or pale bodies range from small fragments through boulders to rare huge asteroids; size varies independently of mesh and material. Half the encounters are fragments, with progressively fewer rocks and boulders; two percent are huge bodies. Sizes span a 500-fold range in world space. Independent flight clearance preserves visible size differences rather than making every body pass at the same apparent scale. A depth buffer resolves overlapping bodies and hides stars behind their silhouettes. Rocks approach near the central vanishing point, pass faster than the stars and sweep completely out of view beside the camera. Close bodies remain opaque and their final facets clip naturally at the viewport edges; there is no central-area disappearance fade. Only distant entry uses a fade. World-space paths keep joined-monitor approaches centred, and fast depth travel keeps edge crossings brief. Larger bodies gain extra velocity so their huge silhouettes do not linger around the edges. Field scheduling remains sparse, leaving long quiet stretches between encounters. Zero disables rocks without resetting travel or the other encounters. Quality changes mesh detail, and existing speed, brightness, pause, Cube delegation and lock controls apply. Meshes are cached per seed/quality; bounded travel keeps long sessions and lock entry continuous. Renderer internals have hidden visibility so a plugin reload cannot bind to an older resident mesh, material or trajectory implementation. - -The default monitor delays are 240 seconds focused and 120 seconds inactive. Existing per-connector overrides and application/presentation inhibitors remain effective, including physical members of joined outputs. The final desktop composition fades separately inside each physical monitor, after Cube projects all faces and rows. Windows, panels, launchers, docks and other workspace content on an idle monitor reveal the live wallpaper; active monitor pixels stay unchanged. Windows spanning monitors fade only on idle portions. Screensavers and session locks remain above the composition cover. Fade-to-black retains its two independent delays; zero preserves the disabled-stage behavior. Manual mode keeps the animation visible. Native session locking remains separate and above all ambient surfaces, with automatic locking disabled unless configured. - -No manual shortcut is assigned by default. Set Toggle ambient-only mode to an unused binding, or run `ambient-background --toggle`; repeat it or press Escape to restore the desktop and prior focus. Ordinary typing and mouse movement do not dismiss manual mode. A watchdog restores desktop opacity if the controller fails. - -Persistent appearance and fading settings are in the [ambient-background] section of wayfire.ini. Edit them through WCM or the file; `wayfire/set-config-options` creates locked temporary overrides and is unsuitable for persistent settings. - -Idle, locking, monitor overrides, alerts and audio configuration is selected by AMBIENT_BACKGROUND_CONFIG, ~/.config/ambient-background.toml, then /etc/ambient-background.toml. DIGITALRAIN_CONFIG remains an explicit legacy override. The Integration tab opens the user's TOML and contains compositor integration switches. The related configuration, API, locking, inhibition, monitor and audio guides are packaged here alongside this document. `ambient-idle --check-config FILE` validates a TOML file. `ambient-idle --help` lists manager controls. Audio remains opt-in, and the optional MCP adapter is `ambient-background-mcp`. - -The plugin controller starts the idle manager when needed. Existing autostart scripts can run `ambient-idle`; existing digitalrain commands are compatibility aliases owned by this package. There is no dependency on the old gui-apps/digitalrain package. Configured lock commands and application inhibitor scripts continue to work through those aliases. - -Source modules: - -- core/: output composition, damage, fading, input protection and shared effect state. -- effects/hyperspace/: native GPU shaders and procedural particles, with their own Ambient settings. -- effects/digital-rain/: Matrix renderer and retained idle/session, audio, PAM and monitor support. -- controller/: ordinary-user activity controller and child-renderer ownership. -- include/ambient-api.hpp: the small signal interface through which Cube consumes the current live wallpaper. -- metadata/: the WCM controls for each effect. -- tests/: isolated headless GPU and WCM regressions. - -New backgrounds belong in effects/ and register their controls in metadata/. Cube consumes the same Ambient interface without learning about individual effects. It retains its original standalone background controls when Ambient is disabled. - -Tests use separate ordinary-user headless GPU compositors, synthetic wallpaper/panels/windows and private D-Bus/runtime directories. They cover no-background rendering, independent settings, opacity, Cube composition, partial damage, monitor fading, inhibitors, black transitions, manual mode, crashes, plugin unload and real WCM controls. They do not capture or inject input into the live desktop. - -## Lock-screen mode - -WCM → Desktop → Ambient Background → Lock screen configures the existing native -`ambient-lock` client. Run `ambient-idle --lock` or use the configured session-lock -shortcut. Ambient-only mode remains a reversible desktop presentation. - -The locked background can follow Ambient, select Hyperspace or Digital Rain -independently, or remain black. Speed and brightness are multipliers; changing -them does not change the live desktop. The optional colour override supplies a -base colour without modifying saved effect colours. Hyperspace uses the same -renderer, particles, cloud banks and encounters inside the lock client's own EGL -contexts. Its initial scene continues from the desktop's generated scene state. -No desktop buffers, windows or wallpapers are sampled while locked. Every lock -surface is opaque. Lock controls monitor chooses one physical monitor or All -monitors, including monitors sharing a stitched output. If a selected monitor is -disconnected, the controls move to an available monitor without changing the -saved choice. The animated background and session protection cover every output. - -By default the clock, date and unlock controls are invisible until keyboard or -mouse interaction. They fade in over 0.25 seconds and fade away after 15 seconds -without input, clearing any unfinished password. Both durations are adjustable; -a zero hide delay keeps the controls visible after input. Escape hides them. -Authentication in progress stays visible. Disabling Reveal lock controls on -interaction keeps the optional clock/date visible between interactions. - -Lock fade-to-black delay measures inactivity since lock entry or last input. Zero -keeps the effect visible. Lock fade duration controls both blackening and animation -restoration. Input restores opted-in powered-off monitors and shows the password -prompt; it never unlocks. Once fully black, the locker stops GPU animation, and -Ambient suspends desktop animation while the session is protected. Monitor power -policy remains in `ambient-background.toml`; automatic session locking remains -controlled by `[global] LOCK_TIMEOUT`. - -Existing PAM authentication, compositor lock confirmation, duplicate rejection, -crash recovery and output hot-plug protection remain in the native locker. The -appearance settings grant no unlock API. If Ambient is unavailable, the locker -keeps the last valid appearance or uses its existing Digital Rain configuration. - -## Nebula - -Nebula is an independent procedural live wallpaper adapted from okcomput3/wayfire-v-kube (MIT; upstream commit and attribution are bundled). It combines fractal light points and coloured volumetric clouds with continuous slow orbital drift. No image is embedded. Nebula has independent background, opacity, speed, brightness, clouds, stars, colours, seed, resolution and detail controls. Speed zero freezes it. Reduced render resolution limits GPU cost while leaving desktop surfaces full resolution. The periodic camera path is continuous at its bounded clock wrap. Cube and the native lock share this renderer; lock entry restores the generated scene and drift phase. - -Hyperspace → Fractal cloud detail optionally blends the same fractal fold into world-space cloud banks. It preserves the current cloud geometry, bank colours, perspective motion and every particle/encounter layer. Zero keeps the previous rendition. diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/Makefile b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/Makefile deleted file mode 100644 index 62fc28d2d810..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/Makefile +++ /dev/null @@ -1,158 +0,0 @@ -CC ?= gcc -CXX ?= c++ -CFLAGS ?= -Wall -Wextra -Wno-format-truncation -O2 -std=c11 -CXXFLAGS ?= -Wall -Wextra -O2 -std=c++17 -PIPEWIRE ?= 1 -AUDIO_BIN := $(if $(filter 1,$(PIPEWIRE)),digitalrain-audio) -LDFLAGS ?= -PREFIX ?= /usr -DESTDIR ?= - -# json-c via pkg-config -JSON_C_CFLAGS := $(shell pkg-config --cflags json-c) -JSON_C_LIBS := $(shell pkg-config --libs json-c) - -# Wayland + EGL + GLESv2 + FreeType -WL_CFLAGS := $(shell pkg-config --cflags wayland-client wayland-egl egl glesv2 freetype2) -WL_LIBS := $(shell pkg-config --libs wayland-client wayland-egl egl glesv2 freetype2) -lrt -lm - -# Wayfire plugin helper for compositor cursor control -WF_CFLAGS := $(shell pkg-config --cflags wayfire wf-utils) -WF_LIBS := $(shell pkg-config --libs wayfire wf-utils) -WF_PLUGINDIR := $(shell pkg-config --variable=plugindir wayfire) -WF_METADATADIR := $(shell pkg-config --variable=metadatadir wayfire) - -# Protocol XML sources -WLR_LAYER_SHELL_XML ?= wlr-layer-shell-unstable-v1.xml -XDG_OUTPUT_XML ?= $(shell pkg-config --variable=pkgdatadir wayland-protocols)/unstable/xdg-output/xdg-output-unstable-v1.xml -XDG_SHELL_XML ?= $(shell pkg-config --variable=pkgdatadir wayland-protocols)/stable/xdg-shell/xdg-shell.xml -EXT_IDLE_NOTIFY_XML ?= $(shell pkg-config --variable=pkgdatadir wayland-protocols)/staging/ext-idle-notify/ext-idle-notify-v1.xml - -# Generated protocol files -PROTO_HEADERS := wlr-layer-shell-unstable-v1-client-protocol.h \ - xdg-output-unstable-v1-client-protocol.h \ - xdg-shell-client-protocol.h \ - ext-idle-notify-v1-client-protocol.h -PROTO_SOURCES := wlr-layer-shell-unstable-v1-protocol.c \ - xdg-output-unstable-v1-protocol.c \ - xdg-shell-protocol.c \ - ext-idle-notify-v1-protocol.c - -.PHONY: all clean install install-conf - -IDLE_PLUGIN := $(if $(wildcard digitalrain-idle.cpp),libdigitalrain-idle.so) -SAMPLER_PLUGIN := $(if $(wildcard digitalrain-sampler.cpp),libdigitalrain-sampler.so) -DESKTOP_PLUGIN := $(if $(wildcard digitalrain-desktop.cpp),libdigitalrain-desktop.so) -POWER_PLUGIN := libdigitalrain-power.so - -all: digitalrain digitalrain-lock digitalrain-auth $(AUDIO_BIN) libdigitalrain-cursor.so $(IDLE_PLUGIN) $(SAMPLER_PLUGIN) $(DESKTOP_PLUGIN) $(POWER_PLUGIN) - -# ── Protocol generation ────────────────────────────────────────────── - -wlr-layer-shell-unstable-v1-client-protocol.h: $(WLR_LAYER_SHELL_XML) - wayland-scanner client-header $< $@ - -wlr-layer-shell-unstable-v1-protocol.c: $(WLR_LAYER_SHELL_XML) - wayland-scanner private-code $< $@ - -xdg-output-unstable-v1-client-protocol.h: $(XDG_OUTPUT_XML) - wayland-scanner client-header $< $@ - -xdg-output-unstable-v1-protocol.c: $(XDG_OUTPUT_XML) - wayland-scanner private-code $< $@ - -xdg-shell-client-protocol.h: $(XDG_SHELL_XML) - wayland-scanner client-header $< $@ - -xdg-shell-protocol.c: $(XDG_SHELL_XML) - wayland-scanner private-code $< $@ - -ext-idle-notify-v1-client-protocol.h: $(EXT_IDLE_NOTIFY_XML) - wayland-scanner client-header $< $@ - -ext-idle-notify-v1-protocol.c: $(EXT_IDLE_NOTIFY_XML) - wayland-scanner private-code $< $@ - -# ── digitalrain (combined manager + renderer) ────────────────────────── - -digitalrain: digitalrain.c digitalrain-api.h digitalrain-control.h digitalrain-config.h digitalrain-config-manager.h digitalrain-config-api.h digitalrain-config-tools.h digitalrain-live.h digitalrain-live-render.h digitalrain-live-manager.h digitalrain-audio-protocol.h tomlc17.c tomlc17.h $(PROTO_SOURCES) $(PROTO_HEADERS) - $(CC) $(CFLAGS) $(JSON_C_CFLAGS) $(WL_CFLAGS) -o $@ digitalrain.c tomlc17.c $(PROTO_SOURCES) $(LDFLAGS) $(JSON_C_LIBS) $(WL_LIBS) -lpthread - -ext-session-lock-v1-client-protocol.h: - wayland-scanner client-header $(shell pkg-config --variable=pkgdatadir wayland-protocols)/staging/ext-session-lock/ext-session-lock-v1.xml $@ - -ext-session-lock-v1-protocol.c: - wayland-scanner private-code $(shell pkg-config --variable=pkgdatadir wayland-protocols)/staging/ext-session-lock/ext-session-lock-v1.xml $@ - -lock-nebula.o: ../nebula/nebula-background.cpp ../nebula/nebula-background.hpp ../nebula/nebula-shaders.tpp ../nebula/nebula-field.tpp ../hyperspace/hyperspace-standalone.hpp - $(CXX) $(CXXFLAGS) $(JSON_C_CFLAGS) $(WL_CFLAGS) -DAMBIENT_LOCK_RENDERER -c $< -o $@ - -lock-hyperspace.o: ../hyperspace/hyperspace-background.cpp ../nebula/nebula-field.tpp ../hyperspace/hyperspace-background.hpp ../hyperspace/hyperspace-standalone.hpp ../hyperspace/hyperspace-shaders.tpp ../hyperspace/hyperspace-anomalies.tpp ../hyperspace/hyperspace-asteroids.tpp ../hyperspace/hyperspace-asteroid-shaders.tpp ../hyperspace/hyperspace-galaxies.tpp ../hyperspace/hyperspace-galaxy-shaders.tpp - $(CXX) $(CXXFLAGS) $(JSON_C_CFLAGS) $(WL_CFLAGS) -DAMBIENT_LOCK_RENDERER -c $< -o $@ - -lock-bridge.o: ../hyperspace/hyperspace-lock.cpp ../nebula/nebula-background.hpp ../hyperspace/hyperspace-lock.h ../hyperspace/hyperspace-background.hpp ../hyperspace/hyperspace-standalone.hpp - $(CXX) $(CXXFLAGS) $(JSON_C_CFLAGS) $(WL_CFLAGS) -DAMBIENT_LOCK_RENDERER -c $< -o $@ - -digitalrain-lock: lock-nebula.o lock-hyperspace.o lock-bridge.o digitalrain-lock.c digitalrain.c digitalrain-api.h digitalrain-control.h digitalrain-config.h digitalrain-config-manager.h digitalrain-config-api.h digitalrain-config-tools.h digitalrain-live.h digitalrain-live-render.h digitalrain-live-manager.h digitalrain-audio-protocol.h tomlc17.c tomlc17.h $(PROTO_SOURCES) $(PROTO_HEADERS) ext-session-lock-v1-client-protocol.h ext-session-lock-v1-protocol.c - $(CC) $(CFLAGS) $(JSON_C_CFLAGS) $(WL_CFLAGS) $(shell pkg-config --cflags xkbcommon) -o $@ digitalrain-lock.c tomlc17.c $(PROTO_SOURCES) ext-session-lock-v1-protocol.c lock-nebula.o lock-hyperspace.o lock-bridge.o $(LDFLAGS) $(JSON_C_LIBS) $(WL_LIBS) $(shell pkg-config --libs xkbcommon) -lpthread -lstdc++ - -digitalrain-audio: digitalrain-audio.c digitalrain-audio-protocol.h - $(CC) $(CFLAGS) $(shell pkg-config --cflags libpipewire-0.3 json-c) -o $@ $< $(LDFLAGS) $(shell pkg-config --libs libpipewire-0.3 json-c) -lm - -digitalrain-auth: digitalrain-auth.c - $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) -lpam - -libdigitalrain-cursor.so: digitalrain-cursor.cpp - $(CXX) $(CXXFLAGS) -fPIC -shared -DWAYFIRE_PLUGIN -DWLR_USE_UNSTABLE $(WF_CFLAGS) -o $@ $< $(LDFLAGS) $(WF_LIBS) - -libdigitalrain-idle.so: digitalrain-idle.cpp digitalrain-targets.hpp - $(CXX) $(CXXFLAGS) -fPIC -shared -DWAYFIRE_PLUGIN -DWLR_USE_UNSTABLE $(WF_CFLAGS) -o $@ $< $(LDFLAGS) $(WF_LIBS) - -libdigitalrain-sampler.so: digitalrain-sampler.cpp digitalrain-targets.hpp - $(CXX) $(CXXFLAGS) -fPIC -shared -DWAYFIRE_PLUGIN -DWLR_USE_UNSTABLE $(WF_CFLAGS) -o $@ $< $(LDFLAGS) $(WF_LIBS) - -libdigitalrain-desktop.so: digitalrain-desktop.cpp digitalrain-targets.hpp - $(CXX) $(CXXFLAGS) -fPIC -shared -DWAYFIRE_PLUGIN -DWLR_USE_UNSTABLE $(WF_CFLAGS) -o $@ $< $(LDFLAGS) $(WF_LIBS) - -libdigitalrain-power.so: digitalrain-power.cpp - $(CXX) $(CXXFLAGS) -fPIC -shared -DWAYFIRE_PLUGIN -DWLR_USE_UNSTABLE $(WF_CFLAGS) -o $@ $< $(LDFLAGS) $(WF_LIBS) - -# ── Install / Clean ───────────────────────────────────────────────── - -install: all -ifneq ($(AUDIO_BIN),) - install -Dm755 digitalrain-audio $(DESTDIR)$(PREFIX)/bin/digitalrain-audio -endif - install -Dm755 digitalrain $(DESTDIR)$(PREFIX)/bin/digitalrain - install -Dm755 digitalrain-lock $(DESTDIR)$(PREFIX)/bin/digitalrain-lock - install -Dm755 digitalrain-auth $(DESTDIR)$(PREFIX)/bin/digitalrain-auth - install -Dm755 digitalrain-mcp.py $(DESTDIR)$(PREFIX)/bin/digitalrain-mcp - install -Dm644 digitalrain.pam $(DESTDIR)/etc/pam.d/digitalrain - install -Dm755 libdigitalrain-cursor.so $(DESTDIR)$(WF_PLUGINDIR)/libdigitalrain-cursor.so - install -Dm644 digitalrain-cursor.xml $(DESTDIR)$(WF_METADATADIR)/digitalrain-cursor.xml - install -Dm755 libdigitalrain-power.so $(DESTDIR)$(WF_PLUGINDIR)/libdigitalrain-power.so - install -Dm755 libdigitalrain-power.so $(DESTDIR)$(WF_PLUGINDIR)/ambient-power-r1/libdigitalrain-power.so - install -Dm644 digitalrain-power.xml $(DESTDIR)$(WF_METADATADIR)/digitalrain-power.xml - -ifneq ($(IDLE_PLUGIN),) - install -Dm755 libdigitalrain-idle.so $(DESTDIR)$(WF_PLUGINDIR)/libdigitalrain-idle.so - install -Dm644 digitalrain-idle.xml $(DESTDIR)$(WF_METADATADIR)/digitalrain-idle.xml -endif - -ifneq ($(SAMPLER_PLUGIN),) - install -Dm755 libdigitalrain-sampler.so $(DESTDIR)$(WF_PLUGINDIR)/libdigitalrain-sampler.so - install -Dm644 digitalrain-sampler.xml $(DESTDIR)$(WF_METADATADIR)/digitalrain-sampler.xml -endif - -ifneq ($(DESKTOP_PLUGIN),) - install -Dm755 libdigitalrain-desktop.so $(DESTDIR)$(WF_PLUGINDIR)/libdigitalrain-desktop.so - install -Dm644 digitalrain-desktop.xml $(DESTDIR)$(WF_METADATADIR)/digitalrain-desktop.xml -endif - -install-conf: - install -Dm644 digitalrain.toml.default $(DESTDIR)/etc/digitalrain.toml - -clean: - rm -f lock-nebula.o lock-hyperspace.o lock-bridge.o - rm -f digitalrain-lock digitalrain-auth digitalrain-audio ext-session-lock-v1-client-protocol.h ext-session-lock-v1-protocol.c - rm -f digitalrain libdigitalrain-cursor.so libdigitalrain-idle.so libdigitalrain-sampler.so libdigitalrain-desktop.so $(POWER_PLUGIN) $(PROTO_HEADERS) $(PROTO_SOURCES) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/adaptive-rendering.md b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/adaptive-rendering.md deleted file mode 100644 index daecd939a6e0..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/adaptive-rendering.md +++ /dev/null @@ -1,52 +0,0 @@ -# Adaptive colors - -This integration is included in Ambient Background. See the configuration guide for current file paths and the WCM controls for continuous live wallpapers; helper and socket identifiers retain their compatible names. - -For all settings, units, defaults and reload behavior, see -[Configuration](configuration.md). Enable `ipc`, `ipc-rules`, and -`digitalrain-sampler` in Wayfire alongside the normal Digital Rain helpers. - -```toml -[matrix] -COLOR = "adaptive" -ADAPTIVE_SAMPLE_INTERVAL = 60 -ADAPTIVE_HUE_INTERVAL = 300 -``` - -Each physical monitor receives a random hue. `adaptive` selects a light or dark -shade according to the desktop below the screensaver. `adaptive-light` always -uses the light shade; `adaptive-dark` uses the dark shade over the desktop. -All adaptive modes use a light shade on a black background. Head glyphs use the -same adaptive palette, so `HEAD_WHITE_MIN` and `HEAD_WHITE_MAX` do not apply. - -Both intervals are **seconds**. Sampling has a five-second minimum; the default -is 60. Hue selection is timed separately, defaults to 900 seconds (15 minutes), -and is held at its initial value when `ADAPTIVE_HUE_INTERVAL=0`. Each physical -monitor is sampled independently, including members of a stitched desktop. -Changes to intervals can affect running adaptive renderers; changes to renderer -color modes update running rain through the live appearance channel. - -The sampler composes the scene below Wayfire's screensaver layer, including cube -content, downsamples on the GPU and reads a small luminance buffer. It does not -load wallpaper files, capture screenshots to disk or integrate with the bar. -Sampling failures retain the last palette. The sample precedes final output -postprocessing and software cursors, so it does not include output-wide filters, -zoom postprocessing or display calibration. - -Glyph bodies cover the desktop beneath them; trail age and depth affect color -intensity, while antialiasing and phase fades control coverage. The glow remains -translucent. Transitioning to black keeps the hue and blends toward its light -shade. - -## Diagnostics - -The `digitalrain/sample_desktop` IPC method accepts an empty data object and -returns protocol `version: 1` with an `outputs` map keyed by physical connector. -Successful entries contain `luminance` in 0–1 and `cached`; failed or locked -outputs contain `error`. Samples are computed on request; a one-second cache -coalesces requests. Adaptive palette state is published in -`$XDG_RUNTIME_DIR/digitalrain/palette.json`. - -The background sequence and optional power-off are described in -[Configuration](configuration.md#understand-the-timing) and -[Monitor power](monitor-power.md). diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/alerts-and-audio.md b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/alerts-and-audio.md deleted file mode 100644 index dbed43dbeae1..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/alerts-and-audio.md +++ /dev/null @@ -1,137 +0,0 @@ -# Automation alerts and audio response - -This integration is included in Ambient Background. See the configuration guide for current file paths and the WCM controls for continuous live wallpapers; helper and socket identifiers retain their compatible names. - -Digital Rain 1.2 can show temporary status colors and react to playback audio. -These affect existing rain. They do not start the saver, extend its idle/phase -clocks, wake a black screen, or change monitor/bar brightness. Configuration is -TOML; see [configuration.md](configuration.md) for migration and live reload. - -## Alerts from programs and agents - -Send an event when something changes: - -```sh -ambient-idle --request '{"version":1,"method":"alert","source":"backup","id":"nightly","severity":"error","seconds":60,"output":"DP-1"}' -ambient-idle --request '{"version":1,"method":"clear_alert","source":"backup","id":"nightly"}' -ambient-idle --list-alerts -``` - -Omit `output` to target all eligible physical monitors. A `(source, id)` pair is -updated in place and its lifetime renewed. Alerts remain after a client exits, -expire using monotonic time even while the screen is black, and are not persisted -across manager restart. Clearing an absent alert succeeds. A source is a label; -all clients use the existing same-user socket permissions. - -| Request | Fields and limits | -| --- | --- | -| `alert` | `source` and `id`: 1–63 bytes each; `severity`: info/success/warning/error; `seconds`: 1–86400; optional physical `output`; optional `message`: at most 127 bytes. | -| `clear_alert` | Required `source`, `id`. | -| `list_alerts` | Optional integer `offset` 0–64 and `limit` 1–16; defaults 0/16. Reply has `alerts`, `total`, and `next_offset` (-1 means complete). | - -There are at most 64 active alerts. An unknown named monitor is rejected at -submission. Disconnecting it retains the alert until expiry; only that connector -can receive it when it returns. A higher priority wins; the newest update breaks -ties. Clearing or expiring the winner reveals another eligible alert or the latest -base palette. Config edits underneath an alert are preserved. A preview suppresses -alerts and audio on its monitor while it runs. - -| TOML setting | Default | Meaning | -| --- | --- | --- | -| `[alerts] ENABLED` | `true` | Accept stored events but display their colors only when enabled. | -| `[alerts] SHOW_WHILE_LOCKED` | `false` | Display colors in native lock surfaces. Authentication is independent. | -| `[alerts.info] COLOR / PRIORITY` | `"#00bfff"` / `10` | Informational status. | -| `[alerts.success] COLOR / PRIORITY` | `"#40ff80"` / `20` | Successful completion. | -| `[alerts.warning] COLOR / PRIORITY` | `"#ffb000"` / `30` | Warning. | -| `[alerts.error] COLOR / PRIORITY` | `"#ff4040"` / `40` | Failure. | -| Each theme's `PULSE` | `0.0` | Optional gentle brightness reduction, 0–0.25, over a two-second cycle. | - -Theme colors accept fixed named or hex colors; priorities accept integers 0–100. -The `message` field is status metadata. Rendering text, frequency-band effects, -beat recognition and displaying alerts over a solid-black screen are future work. - -The optional MCP adapter exposes `alert`, `clear_alert`, `list_alerts`, and -`reload` alongside existing controls. `--watch` publishes state transitions, -alert revisions and selected alerts per output; it does not stream animation -frames or audio samples. Ordinary scripts can use the same CLI or socket without -an agent. The adapter only runs when an MCP client starts it. - -For a build or backup wrapper, publish success/failure after the command exits: - -```sh -if your-build-command; then - ambient-idle --request '{"version":1,"method":"alert","source":"build","id":"result","severity":"success","seconds":15}' -else - ambient-idle --request '{"version":1,"method":"alert","source":"build","id":"result","severity":"error","seconds":120}' -fi -``` - -A service-health program sends an error when health fails, renews it while the -failure persists, and clears it when service returns. No executable hooks run -inside Digital Rain. - -## Audio-reactive rain - -Build/install with Gentoo's `pipewire` USE flag and enable it in the selected TOML: - -```toml -[audio] -ENABLED = true -SOURCE = "default-output" -MODE = "energy" -``` - -`ambient-audio` monitors one playback sink through native PipeWire. The -`default-output` selection follows the session manager's default audio sink. -Alternatively, use the sink's PipeWire `node.name`; it reconnects that named sink -after unplug/replug. It never substitutes a microphone, records audio, changes -routing, or changes volume. Only playback routed to the selected sink contributes. - -| `[audio]` setting | Default | Range / behavior | -| --- | --- | --- | -| `ENABLED` | `false` | Start analysis only for eligible visible rain. | -| `SOURCE` | `"default-output"` | Default playback sink or explicit PipeWire node.name, at most 127 bytes. | -| `MODE` | `"energy"` | The implemented mode uses short-window RMS energy and peaks. | -| `SHOW_WHILE_LOCKED` | `false` | Allow effects in native lock surfaces. | -| `OUTPUTS` | `[]` | Up to eight physical connector names; empty means all eligible monitors. File-only setting. | -| `SPEED_AMOUNT` | `0.25` | Maximum proportional speed increase, 0–2; .25 means up to 25%. | -| `BRIGHTNESS_AMOUNT` | `0.10` | Maximum proportional glyph-brightness increase, 0–1. RGB output remains capped. | -| `ATTACK_MS` | `50` | Envelope rise time, 1–2000 ms. | -| `RELEASE_MS` | `250` | Envelope fall time, 1–5000 ms. | -| `GAIN` | `4` | Measurement gain, .01–100, with normalized output capped at 1. | -| `NOISE_GATE` | `0.005` | RMS below this level produces no effect, 0–1. | - -Energy changes speed; peaks change brightness. There is no tempo detection. -Alerts retain their hue while audio can modulate motion/brightness. Silence or -missing/stale samples smoothly returns to the current normal appearance. - -The helper is shared across participating monitors and stops when all eligible -surfaces are awake, black, or powered off. It restarts on later eligible rain; -server/helper failures use bounded retries. Application idle inhibitors keep their -normal behavior. Audio does not count as user input or prevent a later black phase. -A standalone locker without the manager renders ordinary rain without this helper. - -The selected sink's mute property suppresses visual modulation. Virtual-sink -verification covers mute/unmute; the PCM level available from a sink monitor can -differ from physical loudness with device-specific hardware volume. The feature -responds to the monitor stream and does not claim calibrated speaker loudness. - -Status reports `audio.state`: disabled, idle, unavailable, waiting_for_source, -or monitoring. A missing optional helper, absent PipeWire server, or missing sink -leaves normal rain operational. `SOURCE`, numeric controls and booleans can be -updated using the settings API; themes and the output list are edited in TOML. - -## Performance and verification - -Analysis runs in a separate helper. Its capture callback accumulates bounded -samples without allocations, blocking, or I/O. A timer emits at most 20 small -measurement packets per second; the manager forwards current values to eligible -renderers. Renderers smooth them at the existing 30 FPS. No raw audio or per-frame -JSON goes through the public API/MCP. With audio disabled, no helper runs. - -Tests use private headless compositor sessions and private PipeWire instances -with policy-only WirePlumber and virtual sinks. They cover live appearance without -renderer replacement, per-monitor alert priority/restoration, black-screen policy, -locked appearance without unlocking, playback energy, silence, mute, source -changes and unplug/reconnect. The source package’s `tests/README.live-config-alerts-audio.md` documents commands -and benchmark methodology. diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/configuration.md b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/configuration.md deleted file mode 100644 index 7c90b88c37dc..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/configuration.md +++ /dev/null @@ -1,312 +0,0 @@ -# Ambient Background idle and Digital Rain configuration - -Ambient Background runs one idle manager and continuously renders the selected live wallpaper on each physical monitor. Shared Wayfire workspaces and Cube do not combine monitor timers. - -WCM → Desktop → Ambient Background owns the live wallpaper selection, desktop fades, black delays, and effect appearance. The Hyperspace and Digital Rain tabs each have their own background selector and opacity. None removes the desktop wallpaper behind the effect. These preferences are saved in the [ambient-background] section of wayfire.ini. - -The TOML described below retains idle detection, per-monitor overrides, locking, inhibitors, adaptive sampling, alerts and audio policy. Its Matrix appearance and transition values are defaults for legacy idle/lock renderers; continuous wallpaper mode uses the independent WCM appearance and black-transition controls instead. Existing helper/API names remain compatible. - -## Choose the configuration file - -The idle manager uses TOML. It selects one file: an explicit `AMBIENT_BACKGROUND_CONFIG` (or the legacy `DIGITALRAIN_CONFIG` override), otherwise `$XDG_CONFIG_HOME/ambient-background.toml` (or `~/.config/ambient-background.toml` when XDG_CONFIG_HOME is unset), otherwise `/etc/ambient-background.toml`. It keeps that path until restarted. Files are not merged. -An explicitly selected missing or invalid file causes startup to fail clearly. - -Convert the old format once, preserving the original: - -```sh -ambient-idle --migrate-config ~/.config/digitalrain.conf ~/.config/ambient-background.toml -ambient-idle --check-config ~/.config/ambient-background.toml -``` - -The converter refuses to overwrite its destination and reports obsolete keys. -Use `schema_version = 1` at the top of a new file. Sections and keys are -case-sensitive. Quote strings, including `COLOR = "#00ff00"`; use `true`/`false` -for switches. TOML supports inline `#` comments. Unknown keys, duplicate keys, -invalid types, non-finite numbers and out-of-range values are rejected. -`~/` expands in the selected file path and `LOG_FILE`; shell expansion is absent. -Old `MOUSE_WARPING`, `CMD`, and `LOCK_CMD` options are removed by conversion. - -## Enable the Wayfire helpers - -Add the required names to your existing `[core] plugins` list in `wayfire.ini`: - -| Plugin | Purpose | -| --- | --- | -| `ipc`, `ipc-rules` | Manager communication, output discovery and focus information. | -| `digitalrain-cursor` | Pointer tracking and cursor visibility on sleeping monitors. | -| `digitalrain-idle` | Per-monitor application idle inhibition and compositor lock status/recovery. | -| `session-lock` | Required Wayfire protocol for the native password lock screen. | -| `digitalrain-sampler` | Desktop contrast samples for adaptive colors. | -| `digitalrain-desktop` | Optional wallpaper-only rain with `HIDE_DESKTOP=true`. | -| `digitalrain-power` | Optional monitor power-off with `POWER_OFF=true`. | - -Keep the installed helpers compatible with the installed Wayfire package. -Config edits do not require a compositor restart. Loading new plugins may need -metadata/plugin reload or a new session, depending on what Wayfire has loaded. - -With Wayfire's `autostart` plugin enabled, the manager can be started with: - -```ini -[autostart] -autostart_ambient_background = ambient-idle -``` - -Duplicate starts exit without disturbing the existing manager. - -## Legacy idle/lock renderer timing - -With `TRANSPARENT=true` and positive background/rain timers, each monitor follows: - -```text -Idle delay (TIMEOUT or FOCUSED_TIMEOUT) - → rain over the desktop (TRANSPARENT_TIMEOUT) - → rain over black (BLACK_RAIN_TIMEOUT) - → solid black (POWER_OFF=false) or monitor power-off (POWER_OFF=true) -``` - -Fades add transition time. `TRANSPARENT_TIMEOUT` starts when that monitor's -renderer starts, including its initial rain fade. `BLACK_RAIN_TIMEOUT` starts -only after the background has become fully black. With `TRANSPARENT=false`, there -is no transparent phase. `FADE_FRAMES` controls each fade stage at a target of -30 frames per second; slower rendering can lengthen a fade. - -Zero has different meanings for different options: - -| Setting | Meaning of zero | -| --- | --- | -| `TRANSPARENT_TIMEOUT` | Transparent rain never changes to black. | -| `BLACK_RAIN_TIMEOUT` | Rain on black never stops, so power-off is never reached. | -| `ADAPTIVE_HUE_INTERVAL` | Keep the initially selected hue. | -| Per-monitor `[output_timeouts]` value | Use the global unfocused `TIMEOUT`. | - -Use positive values for `TIMEOUT`, `FOCUSED_TIMEOUT` and `CHECK_INTERVAL`; zero -is not an idle-disable option. Times are approximate because manager checks and -input-idle notifications also contribute delay. - -Pointer crossings and view-focus changes identify the physical monitor you are -using. Focus changes refresh the arriving and departing monitors' idle timers. -Input resuming on the same monitor refreshes that monitor; others keep counting. -An active application inhibitor prevents its affected monitor from sleeping and -restarts its countdown until the inhibitor is released. - -## Apply configuration changes - -The manager watches saves, including atomic file replacement, with a 150 ms -save debounce and a periodic fallback. It parses a complete candidate before -applying anything. Invalid or missing replacements retain the last valid settings; -`--status` reports `config_error`. Removing an option restores its default. -`ambient-idle --reload` explicitly reloads the selected file and resets temporary -API settings even if the file contents are unchanged. - -Colors, speed, head behavior and phase durations reach running renderers. Fonts -and depth layers rebuild resources at a frame boundary while preserving renderer -PIDs and phase clocks; an allocation failure retains previous resources. Initial -density settings take effect at the next grid rebuild or activation. Changes to -`TRANSPARENT`, `HIDE_DESKTOP`, and `POWER_OFF` apply at the next saver/lock -activation. A black surface stays black after an appearance update. - -Idle thresholds use existing activity timestamps. A changed phase duration uses -its existing start time. `LOG_FILE` and the selected config path require a manager -restart. `--schema` describes application timing, and status reports desired versus -applied revisions per active renderer. `appearance_error` means a renderer retained -its prior appearance. Reloading does not restart the compositor or release a lock. - -## Option reference - -Defaults below describe a fresh manager with omitted options, rather than your -personal configuration. The annotated `ambient-background.toml.example` also ships in -the documentation directory; `/etc/ambient-background.toml` may contain local changes. - -### [global] Idle detection and logging - -| Option | Default | Meaning | -| --- | --- | --- | -| `TIMEOUT` | `300` | Seconds before an unfocused physical monitor starts rain. Use a positive integer. | -| `FOCUSED_TIMEOUT` | `30` | Seconds before the focused monitor starts rain while input is idle. Use a positive integer. This takes precedence over [output_timeouts]. | -| `CHECK_INTERVAL` | `5` | Seconds between normal manager checks; use a positive integer. Cursor tracking and wake checks continue during this wait. | -| `INHIBIT_VIDEO` | `true` | true honors visible applications' Wayland idle inhibitors on their monitors; false ignores them. Requires digitalrain-idle. This follows application requests, not video detection. | -| `LOG_FILE` | `~/.local/state/digitalrain/digitalrain.log` | Writable log path; ~/ expands to your home. Reopened and truncated on manager startup. Restart the manager after changing this path. | -| `LOCK_TIMEOUT` | `0` | Whole-seat inactivity seconds before native session locking. Zero disables automatic locking. Separate from per-monitor saver timers and unaffected by saver inhibitors. | - -If `LOG_FILE` is omitted, `$XDG_STATE_HOME/digitalrain/digitalrain.log` is -preferred when `XDG_STATE_HOME` is set, followed by the home-based path -shown above, then `/tmp/digitalrain.log` when no home is available. -The example explicitly sets the home-based path. Parent directories are -created when permitted; a failed file open reports to standard error. - -### [matrix] Background, transitions, and monitor power - -| Option | Default | Meaning | -| --- | --- | --- | -| `TRANSPARENT` | `false` | true starts rain over visible desktop content; false starts with a black background. | -| `TRANSPARENT_TIMEOUT` | `600` | Seconds from renderer startup until transparent rain starts fading to a black background. 0 keeps it transparent indefinitely. Used only with TRANSPARENT=true. | -| `BLACK_RAIN_TIMEOUT` | `0` | Additional seconds after the background is fully black before the rain fades away. 0 keeps rain running on black indefinitely; it does not immediately stop rain. | -| `POWER_OFF` | `false` | false retains a solid-black surface with the monitor powered for instant wake. true powers off after rain ends. Requires a positive BLACK_RAIN_TIMEOUT and digitalrain-power; unavailable power control falls back to black. | -| `HIDE_DESKTOP` | `false` | false leaves apps and bars visible under transparent rain; the black background covers them naturally. true optionally fades them away over the wallpaper. Requires TRANSPARENT=true and digitalrain-desktop. | -| `FADE_FRAMES` | `30` | Frames per fade stage, valid range 1–300. Target frame rate is 30 fps: 20 frames ≈ 0.67 seconds; 30 ≈ 1 second. A wake can contain both a rain fade and a background fade. | - -### [matrix] Color and adaptive sampling - -| Option | Default | Meaning | -| --- | --- | --- | -| `COLOR` | `green` | Named color, six-digit hex, random mode, or adaptive mode; see the color table in configuration.md. | -| `ADAPTIVE_SAMPLE_INTERVAL` | `60` | Seconds between desktop contrast samples in adaptive modes; minimum 5. Requires digitalrain-sampler. | -| `ADAPTIVE_HUE_INTERVAL` | `900` | Seconds between new random hues in adaptive modes. 0 holds the initial hue; negative values are rejected. Independent of contrast sampling. | - -### [matrix] Glyphs and depth layers - -| Option | Default | Meaning | -| --- | --- | --- | -| `FONT_SIZE` | `12` | Base glyph size in renderer pixels, valid range 6–72. Depth layers scale the glyph grid. | -| `DEPTH_LAYERS` | `4` | Number of rain layers, valid range 1–8. Far layers are smaller, dimmer and slower; near layers are larger, brighter and faster. | - -### [matrix] Motion and trail lifetime - -| Option | Default | Meaning | -| --- | --- | --- | -| `MIN_SPEED` | `0.4` | Minimum fall speed in cells per animation frame before depth scaling. Valid range 0.001–100. | -| `MAX_SPEED` | `2.0` | Maximum fall speed in cells per animation frame before depth scaling. Must be at least MIN_SPEED. | -| `AVG_SPEED` | `0.8` | Bias for the random fall-speed distribution. Keep it between MIN_SPEED and MAX_SPEED; this is not an exact mean. | -| `MIN_TRAIL` | `4` | Minimum glyph trail lifetime in animation frames; minimum 1. Visible trail length also depends on fall speed. | -| `MAX_TRAIL` | `28` | Maximum glyph trail lifetime in animation frames. Must be at least MIN_TRAIL. | - -### [matrix] Initial rain density - -| Option | Default | Meaning | -| --- | --- | --- | -| `MIN_DENSITY` | `0.25` | With DEPTH_LAYERS=1, the midpoint of MIN_DENSITY and MAX_DENSITY seeds active columns. Valid range 0–1. Ignored for multiple layers. | -| `MAX_DENSITY` | `0.45` | Upper density value for the single-layer midpoint. Valid between MIN_DENSITY and 1. | -| `LAYER_FAR_DENSITY` | `0.35` | With DEPTH_LAYERS>1, initial active-column probability in the far layer. Intermediate layers interpolate toward LAYER_NEAR_DENSITY. Normal range 0–1; values >=1 seed every eligible column. | -| `LAYER_NEAR_DENSITY` | `0.15` | With DEPTH_LAYERS>1, initial active-column probability in the near layer. Normal range 0–1. Ignored in single-layer mode. | - -Density currently affects initial column activation, not a continuously -enforced fraction of active columns as trails respawn. The multi-layer -values accept 0–2: values at or above 1 seed every eligible column; -zero seeds none. This does not make the animation permanently -full or empty. Use the normal 0–1 range when tuning. - -### [matrix] Leading glyph behavior - -| Option | Default | Meaning | -| --- | --- | --- | -| `HEAD_HOLD_FRAMES` | `5` | Minimum frames before a head glyph may change while it stays in one row. Valid range 0–120; entering a new row chooses a new glyph regardless. | -| `HEAD_MUTATION_CHANCE` | `0.15` | Probability per eligible frame of changing the head glyph, valid range 0–1. 0 disables these stationary changes. | -| `HEAD_WHITE_MIN` | `0.75` | Minimum white blend for heads in non-adaptive colors, valid range 0–1. 0 uses the trail color; 1 uses white. Adaptive modes use their palette instead. | -| `HEAD_WHITE_MAX` | `1.00` | Maximum white blend, valid range 0–1. If the minimum exceeds the maximum, the configuration is rejected. | - -## Color modes - -The same values work for `[matrix] COLOR` and `[output_colors]`: - -| Value | Behavior | -| --- | --- | -| `green`, `red`, `blue`, `white`, `yellow`, `cyan`, `magenta` | Fixed named color. | -| `#RRGGBB` or `RRGGBB` | Fixed six-digit hexadecimal color. | -| `random_named` (also `random` in manager config) | One random named color per monitor activation; different monitors may pick the same color. | -| `random_hex` | One random RGB color per monitor activation. | -| `random_trail_named` | Each new trail chooses a named color. | -| `random_trail_hex` | Each new trail chooses an RGB color. | -| `adaptive` | A per-monitor hue with a light or dark shade selected for desktop contrast. | -| `adaptive-light` | Always use the light shade of the per-monitor hue. | -| `adaptive-dark` | Use the dark shade over the desktop and the light shade over black. | - -Adaptive modes require `digitalrain-sampler`, use a light shade on black, and -ignore head white-blend settings. Sampling and hue rotation have separate -intervals. On a sampling failure, the last available palette is retained. -See [Adaptive colors](adaptive-rendering.md) for details. - -## Per-monitor overrides - -Use physical connector names such as `DP-1` or `HDMI-A-1`, including when Wayfire -combines them into `VIRTUAL-1`. Names are case-sensitive. Only the following -per-monitor sections are supported; there are no per-monitor speed, density, -focused-timeout or rain-duration sections. - -```toml -[output_timeouts] -HDMI-A-1 = 600 -DP-1 = 300 - -[output_colors] -HDMI-A-1 = "adaptive" -DP-1 = "random_trail_hex" - -[output_power_off] -HDMI-A-1 = false -DP-1 = true -``` - -`[output_timeouts]` overrides the **unfocused** `TIMEOUT`; the focused monitor -always uses `FOCUSED_TIMEOUT`. Positive overrides are used; zero values -fall back to `TIMEOUT`. `[output_colors]` overrides `COLOR`. -`[output_power_off]` overrides `POWER_OFF` with `false` or `true`. -Unlisted monitors use the global setting. An override only takes effect for a -currently discovered physical connector; output discovery reloads overrides. - -## Example: transparent rain, then instant-wake black - -These settings give an unfocused monitor two minutes before rain, or four minutes -on the focused monitor after input becomes idle. Rain stays transparent for ten -minutes, then runs on black for another ten minutes before stopping. Fades and -manager polling add small delays. - -```toml -[global] -TIMEOUT = 120 -FOCUSED_TIMEOUT = 240 -CHECK_INTERVAL = 3 -INHIBIT_VIDEO = true - -[matrix] -COLOR = "adaptive" -TRANSPARENT = true -TRANSPARENT_TIMEOUT = 600 -BLACK_RAIN_TIMEOUT = 600 -POWER_OFF = false -HIDE_DESKTOP = false -FADE_FRAMES = 20 -``` - -Apps and bars stay visible beneath transparent rain. The black surface covers -everything below the screensaver layer; session locks remain above it. No bar -integration is required. Solid black stops animation while retaining the display -connection, avoiding a monitor power-on delay. - -For an LCD, enable `digitalrain-power` and opt in with `POWER_OFF=true`, globally -or for one monitor under `[output_power_off]`. This replaces only the final -solid-black state. See [Monitor power](monitor-power.md). - -## Commands and diagnostics - -- `ambient-idle --reload`, `--check-config FILE`: reload or validate TOML. -- `ambient-idle --list-alerts`: inspect temporary visual alerts. -- `digitalrain`: start the idle manager, using the selected config. -- `ambient-idle --activate`: ask the running manager to start rain on all monitors. -- `ambient-idle --black [OUTPUT]`: make one monitor or all monitors black. -- `ambient-idle --wake [OUTPUT]`: wake the saver or show a lock prompt; never unlock. -- `ambient-idle --lock`: request native locking and wait for protocol confirmation. -- `ambient-idle --status`, `--explain`, `--lock-status`: inspect current state. -- `ambient-idle --schema`, `--watch`, `--request JSON`: local API controls. -- `ambient-idle --help`: show manager commands. -- `ambient-idle --render --help`: show renderer arguments. - -The native password screen authenticates through PAM. Ordinary saver activation -is still independent of locking. See [Interfaces and locking](interfaces-and-locking.md) -for required plugins, the shortcut, API examples, and optional MCP support. -`LOCK_CMD` no longer executes an external command. - -A manually launched `--render` process uses CLI arguments and renderer defaults; -it does not read the manager's config. Its timeout defaults are both zero. -Stop a standalone renderer with SIGTERM, or send SIGUSR1 for its normal wake fade. - -Normal session logs record the selected config, discovered monitors and active -renderers. Runtime files live under `$XDG_RUNTIME_DIR/digitalrain/`; the manager -PID file is `pid`. See [Video inhibition](video-inhibition.md), -[Wallpaper-only rain](hide-desktop.md), and [Stitched monitors](stitched-outputs.md) -for feature-specific behavior. - -## Alerts and audio - -The [alerts and audio guide](alerts-and-audio.md) documents every `[alerts]`, -`[alerts.info/success/warning/error]`, and `[audio]` setting, API examples and -performance behavior. The annotated example contains all their defaults. diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-api.h b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-api.h deleted file mode 100644 index 345863ba06fa..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-api.h +++ /dev/null @@ -1,614 +0,0 @@ -static void live_locker_reset(void); -static void live_locker_state(struct json_object *o); -static void live_status(struct json_object *o, bool event); -/* Manager-thread control API. Included after manager helpers; no renderer or - * password work is performed by socket clients. API version 1, JSON packets. */ -struct api_client { - int fd; - bool watch; -}; -struct api_inhibitor { - unsigned id; - double until; - char output[64], reason[128]; - pid_t owner; -}; -static struct { - int listener, locker, ambient_fd; - pid_t locker_pid; - char lock_state[32]; - double lock_started, last_activity, retry_lock_after; - unsigned lock_failures; - struct api_client clients[DR_CLIENTS]; - struct api_inhibitor inhibitors[32]; - unsigned next_inhibitor, revision; - char *last_event; -} A = {.listener = -1, .locker = -1, .ambient_fd = -1}; -static const char *api_phases[] = {"fading_to_black", "fading_in", "rain", - "fading_rain", "black", "fading_out", - "fading_to_desktop"}; -static bool api_locked(void) { - return !strcmp(A.lock_state, "locked") || !strcmp(A.lock_state, "locking") || - !strcmp(A.lock_state, "locked_unavailable"); -} -static bool api_inhibited(const char *output, double now) { - for (size_t i = 0; i < 32; i++) - if (A.inhibitors[i].until > now && - (!A.inhibitors[i].output[0] || !strcmp(output, A.inhibitors[i].output))) - return true; - return false; -} -static void api_probe_lock(void) { - struct json_object *o = mgr_ipc_request("digitalrain/lock_status", "{}"), *v; - const char *state = "unknown"; - if (o && json_object_object_get_ex(o, "protected", &v)) - state = json_object_get_boolean(v) ? "locked_unavailable" : "unlocked"; - snprintf(A.lock_state, sizeof(A.lock_state), "%s", state); - if (o) - json_object_put(o); -} -static void api_connect_locker(void) { - if (A.locker >= 0) - return; - int fd = dr_socket("lock.sock", false); - if (fd < 0) - return; - struct json_object *o = json_object_new_object(); - json_object_object_add(o, "method", json_object_new_string("watch")); - if (dr_send(fd, o)) { - A.locker = fd; live_locker_reset(); - } - else - close(fd); - json_object_put(o); -} -static struct json_object *api_lock(void) { - api_connect_locker(); - if (A.locker < 0 && !A.locker_pid) { - char path[PATH_MAX]; - if (dr_sibling(path, sizeof(path), "digitalrain-lock")) - return dr_error("locker path unavailable"); - posix_spawn_file_actions_t actions; - posix_spawn_file_actions_init(&actions); - posix_spawn_file_actions_addclosefrom_np(&actions, 3); - char *argv[] = {path, NULL}; - int rc = posix_spawn(&A.locker_pid, path, &actions, NULL, argv, environ); - posix_spawn_file_actions_destroy(&actions); - if (rc) { - A.locker_pid = 0; - return dr_error("unable to start native locker"); - } - snprintf(A.lock_state, sizeof(A.lock_state), "locking"); - A.lock_started = mgr_monotime(); - } - struct json_object *o = json_object_new_object(); - json_object_object_add(o, "state", json_object_new_string(A.lock_state)); - return o; -} - -#include "digitalrain-config-api.h" - -static struct json_object *api_snapshot(bool event) { - struct json_object *o = json_object_new_object(), *outputs = json_object_new_array(), - *inhibitors = json_object_new_array(); - json_object_object_add(o, "version", json_object_new_int(1)); - json_object_object_add(o, "ambient_active", json_object_new_boolean(A.ambient_fd >= 0)); - json_object_object_add(o, "lock", json_object_new_string(A.lock_state)); - json_object_object_add(o, "settings_revision", json_object_new_int(C.revision)); - double now = mgr_monotime(); - pthread_mutex_lock(&G.lock); - for (int i = 0; i < G.output_count; i++) { - mgr_output_info_t *p = &G.outputs[i]; - struct json_object *v = json_object_new_object(); - bool focused = !strcmp(G.focused_output, p->name); - json_object_object_add(v, "name", json_object_new_string(p->name)); - json_object_object_add(v, "desktop", json_object_new_string(p->target.desktop)); - json_object_object_add(v, "focused", json_object_new_boolean(focused)); - json_object_object_add(v, "saver", json_object_new_boolean(!strcmp(p->state, "off"))); - json_object_object_add( - v, "phase", - json_object_new_string(p->render_phase >= 0 && p->render_phase <= PHASE_FADE_TO_DESKTOP - ? api_phases[p->render_phase] - : !strcmp(p->state, "on") ? "awake" - : "starting")); - json_object_object_add(v, "desired_revision", json_object_new_int(C.revision)); - json_object_object_add(v, "applied_revision", json_object_new_int(p->applied_revision)); - json_object_object_add(v, "appearance_error", json_object_new_boolean(p->live_error)); - json_object_object_add(v, "font_size", json_object_new_int(p->applied_font)); - json_object_object_add(v, "depth_layers", json_object_new_int(p->applied_depth)); - json_object_object_add(v, "powered_off", json_object_new_boolean(p->powered_off)); - json_object_object_add(v, "application_inhibited", - json_object_new_boolean(p->video_inhibited)); - bool inhibited = api_inhibited(p->name, now); - json_object_object_add(v, "timed_inhibited", json_object_new_boolean(inhibited)); - json_object_object_add(v, "reason", - json_object_new_string(api_locked() ? "session lock" - : inhibited ? "timed inhibitor" - : p->video_inhibited ? "application inhibitor" - : !strcmp(p->state, "off") ? "saver active" - : focused && !G.seat_idle - ? "focused input" - : "idle countdown")); - if (!event) { - double timeout = focused ? G.config.focused_timeout : mgr_get_output_timeout(p); - json_object_object_add(v, "idle_seconds", - json_object_new_double(fmax(0, now - p->last_input))); - json_object_object_add(v, "timeout", json_object_new_double(timeout)); - json_object_object_add( - v, "remaining_seconds", - json_object_new_double(fmax(0, timeout - (now - p->last_input)))); - } - json_object_array_add(outputs, v); - } - pthread_mutex_unlock(&G.lock); - for (size_t i = 0; i < 32; i++) - if (A.inhibitors[i].until > now) { - struct api_inhibitor *p = &A.inhibitors[i]; - struct json_object *v = json_object_new_object(); - json_object_object_add(v, "id", json_object_new_int(p->id)); - json_object_object_add(v, "owner_pid", json_object_new_int(p->owner)); - json_object_object_add(v, "reason", json_object_new_string(p->reason)); - json_object_object_add(v, "output", json_object_new_string(p->output)); - if (!event) - json_object_object_add(v, "remaining_seconds", - json_object_new_double(p->until - now)); - json_object_array_add(inhibitors, v); - } - json_object_object_add(o, "outputs", outputs); - live_status(o, event); - json_object_object_add(o, "inhibitors", inhibitors); - json_object_object_add(o, "config_file", json_object_new_string(G.config_file)); - json_object_object_add(o, "config_error", json_object_new_string(C.error)); - json_object_object_add(o, "restart_required", json_object_new_boolean(strcmp(C.log_destination, G.config.log_file) != 0)); - return o; -} -static bool api_number(struct json_object *o, const char *key, double *out) { - struct json_object *v; - if (!json_object_object_get_ex(o, key, &v) || - (!json_object_is_type(v, json_type_int) && !json_object_is_type(v, json_type_double))) - return false; - *out = json_object_get_double(v); - return isfinite(*out); -} -#include "digitalrain-live-manager.h" -static struct json_object *api_request(struct api_client *client, struct json_object *req) { - struct json_object *v; - if (!json_object_object_get_ex(req, "version", &v) || !json_object_is_type(v, json_type_int) || - json_object_get_int(v) != 1) - return dr_error("unsupported API version (expected 1)"); - const char *method = dr_string(req, "method"), *output = dr_string(req, "output"); - if (!method) - return dr_error("method required"); - if (!strcmp(method, "alert") || !strcmp(method, "clear_alert") || !strcmp(method, "list_alerts")) return live_alert_request(method, req); - if (json_object_object_get_ex(req, "output", &v) && !output) - return dr_error("output must be a physical connector name"); - json_object_object_foreach(req, key, value) { - (void)value; - bool allowed = - !strcmp(key, "version") || !strcmp(key, "method") || - (!strcmp(key, "output") && - (!strcmp(method, "activate") || !strcmp(method, "black") || !strcmp(method, "wake") || - !strcmp(method, "preview") || !strcmp(method, "inhibit"))) || - (!strcmp(key, "settings") && - (!strcmp(method, "settings") || !strcmp(method, "preview"))) || - (!strcmp(key, "enabled") && !strcmp(method, "ambient")) || - (!strcmp(key, "save") && !strcmp(method, "settings")) || - (!strcmp(key, "seconds") && - (!strcmp(method, "preview") || !strcmp(method, "inhibit"))) || - (!strcmp(key, "reason") && !strcmp(method, "inhibit")) || - (!strcmp(key, "id") && !strcmp(method, "uninhibit")); - if (!allowed) - return dr_error("unexpected request field"); - } - if (output && !mgr_find_output(output)) - return dr_error("unknown physical output"); - if (!strcmp(method, "status") || !strcmp(method, "explain")) - return api_snapshot(false); - if (!strcmp(method, "ambient")) { - struct json_object *enabled; - if (!json_object_object_get_ex(req, "enabled", &enabled) || - !json_object_is_type(enabled, json_type_boolean)) - return dr_error("ambient requires a boolean enabled"); - if (A.ambient_fd >= 0 && A.ambient_fd != client->fd) - return dr_error("ambient is already owned by another connection"); - A.ambient_fd = json_object_get_boolean(enabled) ? client->fd : -1; - if (A.ambient_fd >= 0) { - /* Keep all activity clocks intact while removing any legacy overlay. */ - for (int i = 0; i < G.output_count; i++) mgr_unblank_output(G.outputs[i].name); - } - return api_snapshot(false); - } - if (!strcmp(method, "watch")) { - client->watch = true; - return api_snapshot(false); - } - if (!strcmp(method, "reload")) { C.force = true; if (!mgr_load_config()) return dr_error(C.error); return api_schema(); } - if (!strcmp(method, "schema")) - return api_schema(); - if (!strcmp(method, "lock")) - return api_lock(); - if (!strcmp(method, "lock_status")) { - struct json_object *o = json_object_new_object(); - json_object_object_add(o, "state", json_object_new_string(A.lock_state)); - return o; - } - if (!strcmp(method, "inhibit")) { - double seconds; - const char *reason = dr_string(req, "reason"); - if (!api_number(req, "seconds", &seconds) || seconds <= 0 || seconds > 86400 || !reason || - strlen(reason) > 127) - return dr_error("inhibit requires seconds in (0,86400] and a reason <=127 bytes"); - for (size_t i = 0; i < 32; i++) - if (A.inhibitors[i].until <= mgr_monotime()) { - struct api_inhibitor *p = &A.inhibitors[i]; - p->id = ++A.next_inhibitor; - p->until = mgr_monotime() + seconds; - snprintf(p->reason, sizeof(p->reason), "%s", reason); - snprintf(p->output, sizeof(p->output), "%s", output ? output : ""); - struct ucred cred; - socklen_t len = sizeof(cred); - p->owner = getsockopt(client->fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) == 0 - ? cred.pid - : 0; - struct json_object *o = json_object_new_object(); - json_object_object_add(o, "id", json_object_new_int(p->id)); - return o; - } - return dr_error("too many timed inhibitors"); - } - if (!strcmp(method, "uninhibit")) { - double id; - if (!api_number(req, "id", &id)) - return dr_error("inhibitor id required"); - for (size_t i = 0; i < 32; i++) - if (A.inhibitors[i].id == id && A.inhibitors[i].until > 0) { - A.inhibitors[i].until = 0; - return json_object_new_object(); - } - return dr_error("unknown inhibitor"); - } - if (!strcmp(method, "settings") || !strcmp(method, "preview")) { - struct json_object *values = NULL; - json_object_object_get_ex(req, "settings", &values); - dr_config_t candidate = G.config; - const char *error = api_validate(values, &candidate); - if (error) - return dr_error(error); - if (!strcmp(method, "settings")) { - bool save = false; - if (json_object_object_get_ex(req, "save", &v)) { - if (!json_object_is_type(v, json_type_boolean)) - return dr_error("save must be boolean"); - save = json_object_get_boolean(v); - } - if (save && !api_save(values)) - return dr_error( - "cannot atomically save selected config (must be owned by this user)"); - api_copy_settings(&candidate); - ++C.revision; - if (save) { - G.config_mtime = 0; - mgr_load_config(); - } - return api_schema(); - } - if (api_locked()) - return dr_error("preview unavailable while session is locked"); - double seconds = 15; - if (json_object_object_get_ex(req, "seconds", &v) && - (!api_number(req, "seconds", &seconds) || seconds <= 0 || seconds > 600)) - return dr_error("preview seconds must be in (0,600]"); - dr_config_t before = G.config; - api_copy_settings(&candidate); - for (int i = 0; i < G.output_count; i++) - if (!output || !strcmp(output, G.outputs[i].name)) { - mgr_unblank_output(G.outputs[i].name); - mgr_blank_output(G.outputs[i].name); - G.outputs[i].preview_until = mgr_monotime() + seconds; - } - api_copy_settings(&before); - return api_snapshot(false); - } - if (!strcmp(method, "activate") || !strcmp(method, "black") || !strcmp(method, "wake")) { - bool wake = !strcmp(method, "wake"); - if (!wake && api_locked()) - return dr_error("session is locked; use wake to show its prompt"); - if (wake && A.locker >= 0) { - struct json_object *o = json_object_new_object(); - json_object_object_add(o, "method", json_object_new_string("wake")); - dr_send(A.locker, o); - json_object_put(o); - } - for (int i = 0; i < G.output_count; i++) - if (!output || !strcmp(output, G.outputs[i].name)) { - if (wake) { - mgr_unblank_output(G.outputs[i].name); - G.outputs[i].last_input = mgr_monotime(); - G.outputs[i].preview_until = 0; - } else { - G.manual_activation = 1; - G.seat_idle = 1; - mgr_blank_output(G.outputs[i].name); - if (!strcmp(method, "black")) - for (int j = 0; j < G.outputs[i].pid_count; j++) - kill(G.outputs[i].pids[j], SIGRTMIN + 1); - } - } - return api_snapshot(false); - } - return dr_error("unsupported method"); -} -static void api_events(void) { - bool watch = false; - for (int i = 0; i < DR_CLIENTS; i++) - watch |= A.clients[i].fd >= 0 && A.clients[i].watch; - if (!watch) - return; - struct json_object *o = api_snapshot(true); - const char *text = json_object_to_json_string_ext(o, JSON_C_TO_STRING_PLAIN); - if (!A.last_event || strcmp(text, A.last_event)) { - free(A.last_event); - A.last_event = strdup(text); - for (int i = 0; i < DR_CLIENTS; i++) - if (A.clients[i].fd >= 0 && A.clients[i].watch && !dr_send(A.clients[i].fd, o)) { - if (A.ambient_fd == A.clients[i].fd) A.ambient_fd = -1; - close(A.clients[i].fd); - A.clients[i].fd = -1; - } - } - json_object_put(o); -} -static void api_tick(double now) { - mgr_reap_fading(); - mgr_config_tick(now); - if (!strcmp(A.lock_state, "locked_unavailable") && now >= A.retry_lock_after) { - struct json_object *r = api_lock(); - json_object_put(r); - A.retry_lock_after = now + fmin(30, 1u << (A.lock_failures < 5 ? A.lock_failures++ : 5)); - } - if (!G.seat_idle) - A.last_activity = now; - if (G.config.lock_timeout > 0 && !api_locked() && now - A.last_activity >= G.config.lock_timeout) { - struct json_object *r = api_lock(); - json_object_put(r); - A.last_activity = now; - } - if (A.locker_pid && A.locker < 0) { - api_connect_locker(); - if (kill(A.locker_pid, 0) || now - A.lock_started > 8) { - A.locker_pid = 0; - if (A.locker < 0) - api_probe_lock(); - } - } - for (int i = 0; i < G.output_count; i++) - if (G.outputs[i].preview_until > 0 && now >= G.outputs[i].preview_until) { - G.outputs[i].preview_until = 0; - mgr_unblank_output(G.outputs[i].name); - G.outputs[i].last_input = now; - } -} -static void api_poll(int timeout) { - struct pollfd fds[4 + DR_CLIENTS + MAX_OUTPUTS] = {{.fd = A.listener, .events = POLLIN}, - {.fd = A.locker, .events = POLLIN}}; - for (int i = 0; i < DR_CLIENTS; i++) - fds[2 + i] = (struct pollfd){.fd = A.clients[i].fd, .events = POLLIN}; - for (int i = 0; i < MAX_OUTPUTS; i++) - fds[2 + DR_CLIENTS + i] = (struct pollfd){ - .fd = i < G.output_count ? G.outputs[i].report_fd : -1, .events = POLLIN}; - fds[2 + DR_CLIENTS + MAX_OUTPUTS] = (struct pollfd){.fd=C.fd, .events=POLLIN}; - fds[3 + DR_CLIENTS + MAX_OUTPUTS] = (struct pollfd){.fd=U.audio_fd, .events=POLLIN}; - int ready = poll(fds, 4 + DR_CLIENTS + MAX_OUTPUTS, timeout); - if (ready < 0 && errno != EINTR) - LOG_WARNING("control poll: %s", strerror(errno)); - if (fds[0].revents & POLLIN) { - int fd = dr_accept(A.listener), slot = -1; - for (int i = 0; i < DR_CLIENTS; i++) - if (A.clients[i].fd < 0) { - slot = i; - break; - } - if (fd >= 0) { - if (slot < 0) - close(fd); - else - A.clients[slot] = (struct api_client){.fd = fd}; - } - } - if (A.locker >= 0 && fds[1].revents) { - struct json_object *o = dr_receive(A.locker); - const char *state = dr_string(o, "state"); - if (state) { - live_locker_state(o); - snprintf(A.lock_state, sizeof(A.lock_state), "%s", state); - if (!strcmp(state, "locked")) { - G.activate_all = 0; - A.lock_failures = 0; - } - } - if (!o) { - close(A.locker); - A.locker = -1; - A.locker_pid = 0; - api_probe_lock(); - } - if (o) - json_object_put(o); - } - for (int i = 0; i < G.output_count; i++) - if (G.outputs[i].report_fd >= 0 && fds[2 + DR_CLIENTS + i].revents) { - dr_report_packet report; - ssize_t n; - int packets = 0; - while (packets++ < 16 && (n = recv(G.outputs[i].report_fd, &report, sizeof(report), MSG_DONTWAIT | MSG_TRUNC)) == sizeof(report)) { - if (report.magic != DR_LIVE_MAGIC || report.kind != DR_REPORT) continue; - if (report.phase >= 0 && report.phase <= PHASE_FADE_TO_DESKTOP) G.outputs[i].render_phase = report.phase; - G.outputs[i].powered_off = report.power != 0; - G.outputs[i].applied_revision = report.revision; - G.outputs[i].live_error = report.error; - G.outputs[i].applied_font = report.font_size; - G.outputs[i].applied_depth = report.depth_layers; - } - if (n == 0) { - close(G.outputs[i].report_fd); - G.outputs[i].report_fd = -1; - } - } - for (int i = 0; i < DR_CLIENTS; i++) - if (A.clients[i].fd >= 0 && fds[2 + i].revents) { - struct json_object *req = dr_receive(A.clients[i].fd); - if (!req) { - if (A.ambient_fd == A.clients[i].fd) A.ambient_fd = -1; - close(A.clients[i].fd); - A.clients[i].fd = -1; - continue; - } - struct json_object *reply = api_request(&A.clients[i], req); - if (!dr_send(A.clients[i].fd, reply)) { - if (A.ambient_fd == A.clients[i].fd) A.ambient_fd = -1; - close(A.clients[i].fd); - A.clients[i].fd = -1; - } - json_object_put(reply); - json_object_put(req); - } - api_tick(mgr_monotime()); - live_tick(mgr_monotime()); - api_events(); -} -static bool api_init(void) { - for (int i = 0; i < DR_CLIENTS; i++) - A.clients[i].fd = -1; - A.listener = dr_socket("control.sock", true); - if (A.listener < 0) { - LOG_ERROR("Cannot create private control socket: %s", strerror(errno)); - return false; - } - A.last_activity = mgr_monotime(); - api_probe_lock(); - api_connect_locker(); - return true; -} -static void api_close(void) { - live_audio_stop(); - if (U.audio_pid) { kill(U.audio_pid, SIGKILL); waitpid(U.audio_pid, NULL, 0); } - if (C.fd >= 0) close(C.fd); - for (int i = 0; i < DR_CLIENTS; i++) - if (A.clients[i].fd >= 0) { - if (A.ambient_fd == A.clients[i].fd) A.ambient_fd = -1; - close(A.clients[i].fd); - } - if (A.locker >= 0) - close(A.locker); /* Does not stop or unlock the independent locker. */ - close(A.listener); - char path[PATH_MAX]; - if (!dr_path(path, sizeof(path), "control.sock")) - unlink(path); - free(A.last_event); -} -#include "digitalrain-config-tools.h" -static int api_cli(int argc, char **argv) { - if (!strcmp(argv[1], "--check-config") || !strcmp(argv[1], "--migrate-config")) return cf_cli(argc, argv); - if (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")) { - puts("digitalrain Run the idle manager\n" - "digitalrain --status|--explain|--schema|--watch|--lock-status\n" - "digitalrain --activate|--black|--wake [OUTPUT]\n" - "digitalrain --lock Request native session locking\n" - "digitalrain --reload|--list-alerts\n" - "digitalrain --check-config FILE\n" - "digitalrain --migrate-config OLD.conf NEW.toml\n" - "digitalrain --request JSON Send one API v1 request\n" - "digitalrain --render --help Renderer options"); - return 0; - } - struct json_object *req = NULL; - if (!strcmp(argv[1], "--request") && argc == 3) - req = json_tokener_parse(argv[2]); - else if (!strncmp(argv[1], "--", 2)) { - const char *method = argv[1] + 2; - if (!strcmp(method, "list-alerts")) method = "list_alerts"; - if (!strcmp(method, "lock-status")) - method = "lock_status"; - req = json_object_new_object(); - json_object_object_add(req, "version", json_object_new_int(1)); - json_object_object_add(req, "method", json_object_new_string(method)); - if (argc == 3) - json_object_object_add(req, "output", json_object_new_string(argv[2])); - } - if (!req || !json_object_is_type(req, json_type_object)) { - fprintf(stderr, "Invalid request; use --help\n"); - if (req) - json_object_put(req); - return 2; - } - int fd = dr_socket("control.sock", false); - if (fd < 0) { - fprintf(stderr, "Digital Rain manager unavailable\n"); - json_object_put(req); - return 1; - } - const char *method = dr_string(req, "method"); - bool watch = method && !strcmp(method, "watch"); - bool locking = method && !strcmp(method, "lock"); - if (!dr_send(fd, req)) { - close(fd); - json_object_put(req); - return 1; - } - json_object_put(req); - bool first = true; - for (;;) { - struct pollfd p = {.fd = fd, .events = POLLIN}; - int rc = poll(&p, 1, watch && !first ? -1 : 10000); - struct json_object *reply = rc > 0 ? dr_receive(fd) : NULL; - if (!reply) { - fprintf(stderr, "Control request timed out or disconnected\n"); - close(fd); - return 1; - } - struct json_object *error; - bool failed = json_object_object_get_ex(reply, "error", &error); - puts(json_object_to_json_string_ext(reply, JSON_C_TO_STRING_PLAIN)); - fflush(stdout); - json_object_put(reply); - if (failed) { - close(fd); - return 1; - } - if (!watch) - break; - first = false; - } - close(fd); - /* --lock is a confirmation boundary, not merely a process launch. */ - if (locking) { - double deadline = mgr_monotime() + 8; - do { - int locker = dr_socket("lock.sock", false); - if (locker >= 0) { - struct json_object *r = json_object_new_object(); - json_object_object_add(r, "method", json_object_new_string("status")); - dr_send(locker, r); - json_object_put(r); - struct pollfd p = {.fd = locker, .events = POLLIN}; - if (poll(&p, 1, 1000) > 0) { - r = dr_receive(locker); - const char *state = dr_string(r, "state"); - bool locked = state && !strcmp(state, "locked"); - if (r) - json_object_put(r); - close(locker); - if (locked) { - puts("{\"state\":\"locked\"}"); - return 0; - } - } else - close(locker); - } - usleep(50000); - } while (mgr_monotime() < deadline); - fprintf(stderr, "Native session lock was not confirmed\n"); - return 1; - } - return 0; -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-audio-protocol.h b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-audio-protocol.h deleted file mode 100644 index d74b2b6b151b..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-audio-protocol.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef DIGITALRAIN_AUDIO_PROTOCOL_H -#define DIGITALRAIN_AUDIO_PROTOCOL_H -#include <stdint.h> -#define DR_AUDIO_MAGIC UINT32_C(0x44524132) -typedef struct { - uint32_t magic, available; - double time; - float rms, peak; -} dr_audio_packet; -#endif diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-audio.c b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-audio.c deleted file mode 100644 index c6cfa2ab41fd..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-audio.c +++ /dev/null @@ -1,297 +0,0 @@ -/* Optional playback-monitor analyzer. No GL, saved audio, or public API. */ -#define _GNU_SOURCE -#include <pipewire/pipewire.h> -#include <pipewire/extensions/metadata.h> -#include <spa/param/audio/format-utils.h> -#include <spa/param/props.h> -#include <spa/pod/parser.h> -#include <json-c/json.h> -#include <math.h> -#include <signal.h> -#include <sys/prctl.h> -#include <sys/socket.h> -#include <time.h> -#include <unistd.h> -#include <errno.h> -#include "digitalrain-audio-protocol.h" - -static struct { - struct pw_main_loop *loop; - struct pw_context *context; - struct pw_core *core; - struct pw_registry *registry; - struct pw_metadata *metadata; - struct pw_stream *stream; - struct pw_node *node; - struct spa_hook registry_hook, metadata_hook, stream_hook, node_hook, core_hook; - struct spa_source *timer; - struct { - uint32_t id; - char name[128]; - } sinks[128]; - int count, fd; - uint32_t selected, metadata_id; - char requested[128], default_sink[128]; - double sum, last_samples; - uint64_t samples; - float peak; - bool muted, format_ok, available; -} D; -static double monotime(void) { - struct timespec t; - clock_gettime(CLOCK_MONOTONIC, &t); - return t.tv_sec + t.tv_nsec / 1e9; -} -static void quit(void *data, int signal) { - (void)data; - (void)signal; - pw_main_loop_quit(D.loop); -} -static void core_error(void *data, uint32_t id, int seq, int res, const char *message) { - (void)data; - (void)id; - (void)seq; - (void)message; - if (res == -EPIPE) - pw_main_loop_quit(D.loop); -} -static const struct pw_core_events core_events = {PW_VERSION_CORE_EVENTS, .error = core_error}; -static void process(void *data) { - (void)data; - struct pw_buffer *buffer = pw_stream_dequeue_buffer(D.stream); - if (!buffer) - return; - struct spa_buffer *b = buffer->buffer; - if (D.format_ok && b->n_datas && b->datas[0].data && b->datas[0].chunk) { - struct spa_data *d = &b->datas[0]; - uint32_t offset = d->chunk->offset; - uint32_t size = d->chunk->size; - if (offset <= d->maxsize && size <= d->maxsize - offset && offset % sizeof(float) == 0) { - const float *samples = (const float *)((char *)d->data + offset); - uint32_t n = size / sizeof(float); - if (n > 65536) - n = 65536; - for (uint32_t i = 0; i < n; i++) { - float value = samples[i]; - if (!isfinite(value)) - value = 0; - value = fminf(1, fabsf(value)); - D.sum += value * value; - if (value > D.peak) - D.peak = value; - } - D.samples += n; - D.last_samples = monotime(); - } - } - pw_stream_queue_buffer(D.stream, buffer); -} -static void stream_state(void *data, enum pw_stream_state old, enum pw_stream_state state, - const char *error) { - (void)data; - (void)old; - (void)error; - D.available = state == PW_STREAM_STATE_PAUSED || state == PW_STREAM_STATE_STREAMING; -} -static void stream_param(void *data, uint32_t id, const struct spa_pod *param) { - (void)data; - if (id != SPA_PARAM_Format || !param) - return; - struct spa_audio_info_raw info = {0}; - D.format_ok = - spa_format_audio_raw_parse(param, &info) >= 0 && info.format == SPA_AUDIO_FORMAT_F32; -} -static const struct pw_stream_events stream_events = { - PW_VERSION_STREAM_EVENTS, .state_changed = stream_state, .param_changed = stream_param, - .process = process}; -static void node_param(void *data, int seq, uint32_t id, uint32_t index, uint32_t next, - const struct spa_pod *param) { - (void)data; - (void)seq; - (void)index; - (void)next; - if (id == SPA_PARAM_Props && param) { - const struct spa_pod_prop *p = spa_pod_find_prop(param, NULL, SPA_PROP_mute); - if (p) - spa_pod_get_bool(&p->value, &D.muted); - } -} -static const struct pw_node_events node_events = {PW_VERSION_NODE_EVENTS, .param = node_param}; -static void select_sink(void) { - const char *name = !strcmp(D.requested, "default-output") ? D.default_sink : D.requested; - uint32_t selected = SPA_ID_INVALID; - for (int i = 0; i < D.count; i++) - if (!strcmp(D.sinks[i].name, name)) - selected = D.sinks[i].id; - if (selected == D.selected) - return; - if (D.stream) { - pw_stream_destroy(D.stream); - D.stream = NULL; - } - if (D.node) { - spa_hook_remove(&D.node_hook); - pw_proxy_destroy((struct pw_proxy *)D.node); - D.node = NULL; - } - D.selected = selected; - D.available = false; - D.format_ok = false; - D.muted = false; - D.sum = 0; - D.samples = 0; - D.peak = 0; - if (selected == SPA_ID_INVALID) - return; - - D.node = pw_registry_bind(D.registry, selected, PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, 0); - if (D.node) { - pw_node_add_listener(D.node, &D.node_hook, &node_events, NULL); - uint32_t ids[] = {SPA_PARAM_Props}; - pw_node_subscribe_params(D.node, ids, 1); - } - D.stream = pw_stream_new( - D.core, "Digital Rain audio energy", - pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Capture", - PW_KEY_MEDIA_ROLE, "DSP", PW_KEY_STREAM_CAPTURE_SINK, "true", - PW_KEY_STREAM_MONITOR, "true", PW_KEY_NODE_PASSIVE, "true", - PW_KEY_NODE_DONT_RECONNECT, "true", PW_KEY_TARGET_OBJECT, name, NULL)); - if (!D.stream) - return; - pw_stream_add_listener(D.stream, &D.stream_hook, &stream_events, NULL); - uint8_t buffer[1024]; - struct spa_pod_builder builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); - struct spa_audio_info_raw info = {.format = SPA_AUDIO_FORMAT_F32, - .rate = 48000, - .channels = 2, - .position = {SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR}}; - const struct spa_pod *params[] = { - spa_format_audio_raw_build(&builder, SPA_PARAM_EnumFormat, &info)}; - /* Main-loop callbacks: no RT_PROCESS. The callback only accumulates samples; - * the separate timer sends metrics outside that callback. */ - pw_stream_connect(D.stream, PW_DIRECTION_INPUT, PW_ID_ANY, - PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS, params, 1); -} -static int metadata_property(void *data, uint32_t subject, const char *key, const char *type, - const char *value) { - (void)data; - (void)type; - if (subject != PW_ID_CORE || !key || strcmp(key, "default.audio.sink")) - return 0; - struct json_object *obj = value ? json_tokener_parse(value) : NULL, *name; - D.default_sink[0] = 0; - if (obj && json_object_object_get_ex(obj, "name", &name) && - json_object_is_type(name, json_type_string)) - snprintf(D.default_sink, sizeof(D.default_sink), "%s", json_object_get_string(name)); - if (obj) - json_object_put(obj); - select_sink(); - return 0; -} -static const struct pw_metadata_events metadata_events = {PW_VERSION_METADATA_EVENTS, - .property = metadata_property}; -static void global(void *data, uint32_t id, uint32_t permissions, const char *type, - uint32_t version, const struct spa_dict *props) { - (void)data; - (void)permissions; - (void)version; - if (!props) - return; - if (!strcmp(type, PW_TYPE_INTERFACE_Metadata)) { - const char *name = spa_dict_lookup(props, PW_KEY_METADATA_NAME); - if (!D.metadata && name && !strcmp(name, "default")) { - D.metadata_id = id; - D.metadata = pw_registry_bind(D.registry, id, type, PW_VERSION_METADATA, 0); - if (D.metadata) - pw_metadata_add_listener(D.metadata, &D.metadata_hook, &metadata_events, NULL); - } - } else if (!strcmp(type, PW_TYPE_INTERFACE_Node)) { - const char *kind = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS), - *name = spa_dict_lookup(props, PW_KEY_NODE_NAME); - if (kind && name && !strcmp(kind, "Audio/Sink") && strlen(name) < 128 && D.count < 128) { - D.sinks[D.count].id = id; - snprintf(D.sinks[D.count++].name, 128, "%s", name); - select_sink(); - } - } -} -static void global_remove(void *data, uint32_t id) { - (void)data; - if (D.metadata && id == D.metadata_id) { - spa_hook_remove(&D.metadata_hook); - pw_proxy_destroy((struct pw_proxy *)D.metadata); - D.metadata = NULL; - D.default_sink[0] = 0; - select_sink(); - } - for (int i = 0; i < D.count; i++) - if (D.sinks[i].id == id) { - D.sinks[i] = D.sinks[--D.count]; - select_sink(); - break; - } -} -static const struct pw_registry_events registry_events = { - PW_VERSION_REGISTRY_EVENTS, .global = global, .global_remove = global_remove}; -static void report(void *data, uint64_t expirations) { - (void)data; - (void)expirations; - dr_audio_packet p = {.magic = DR_AUDIO_MAGIC, .available = D.available, .time = monotime()}; - if (D.samples && !D.muted && p.time - D.last_samples < .3) { - p.rms = sqrt(D.sum / D.samples); - p.peak = D.peak; - } - D.sum = 0; - D.samples = 0; - D.peak = 0; - if (send(D.fd, &p, sizeof(p), MSG_DONTWAIT | MSG_NOSIGNAL) < 0 && errno != EAGAIN && - errno != EINTR) - pw_main_loop_quit(D.loop); -} -int main(int argc, char **argv) { - if (argc != 3 || strlen(argv[2]) >= 128) { - fprintf(stderr, "digitalrain-audio PRIVATE_FD SINK_NAME\n"); - return 2; - } - D.fd = atoi(argv[1]); - if (D.fd < 3) - return 2; - pid_t parent = getppid(); - if (prctl(PR_SET_PDEATHSIG, SIGTERM) || getppid() != parent) - return 1; - snprintf(D.requested, sizeof(D.requested), "%s", argv[2]); - D.selected = SPA_ID_INVALID; - pw_init(NULL, NULL); - D.loop = pw_main_loop_new(NULL); - if (!D.loop) - return 1; - struct pw_loop *loop = pw_main_loop_get_loop(D.loop); - pw_loop_add_signal(loop, SIGTERM, quit, NULL); - pw_loop_add_signal(loop, SIGINT, quit, NULL); - D.context = pw_context_new(loop, NULL, 0); - D.core = D.context ? pw_context_connect(D.context, NULL, 0) : NULL; - if (!D.core) { - pw_main_loop_destroy(D.loop); - return 1; - } - pw_core_add_listener(D.core, &D.core_hook, &core_events, NULL); - D.registry = pw_core_get_registry(D.core, PW_VERSION_REGISTRY, 0); - pw_registry_add_listener(D.registry, &D.registry_hook, ®istry_events, NULL); - D.timer = pw_loop_add_timer(loop, report, NULL); - struct timespec first = {0, 50000000}, interval = {0, 50000000}; - pw_loop_update_timer(loop, D.timer, &first, &interval, false); - pw_main_loop_run(D.loop); - if (D.stream) - pw_stream_destroy(D.stream); - if (D.node) - pw_proxy_destroy((struct pw_proxy *)D.node); - if (D.metadata) - pw_proxy_destroy((struct pw_proxy *)D.metadata); - pw_proxy_destroy((struct pw_proxy *)D.registry); - pw_core_disconnect(D.core); - pw_context_destroy(D.context); - pw_main_loop_destroy(D.loop); - pw_deinit(); - close(D.fd); - return 0; -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-auth.c b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-auth.c deleted file mode 100644 index ba34e1412177..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-auth.c +++ /dev/null @@ -1,101 +0,0 @@ -/* Unprivileged PAM worker. Passwords arrive only over the locker's private pipe. - * No CLI password, configurable username, or configurable production service. */ -#define _GNU_SOURCE -#include <security/pam_appl.h> -#include <sys/mman.h> -#include <sys/prctl.h> -#include <sys/resource.h> -#include <pwd.h> -#include <unistd.h> -#include <stdlib.h> -#include <string.h> -#include <stdint.h> -#include <errno.h> -#ifndef DIGITALRAIN_PAM_SERVICE -#define DIGITALRAIN_PAM_SERVICE "digitalrain" -#endif -static char secret[1024]; -static const char *username; -static int converse(int n, const struct pam_message **msg, struct pam_response **out, void *data) { - (void)data; - if (n <= 0 || n > 32) - return PAM_CONV_ERR; - struct pam_response *r = calloc(n, sizeof(*r)); - if (!r) - return PAM_BUF_ERR; - for (int i = 0; i < n; ++i) { - switch (msg[i]->msg_style) { - case PAM_PROMPT_ECHO_OFF: - r[i].resp = strdup(secret); - break; - case PAM_PROMPT_ECHO_ON: - r[i].resp = strdup(username); - break; - case PAM_TEXT_INFO: - case PAM_ERROR_MSG: - continue; - default: - goto fail; - } - if (!r[i].resp) - goto fail; - } - *out = r; - return PAM_SUCCESS; -fail: - for (int i = 0; i < n; ++i) - if (r[i].resp) { - explicit_bzero(r[i].resp, strlen(r[i].resp)); - free(r[i].resp); - } - free(r); - return PAM_CONV_ERR; -} -int main(int argc, char **argv) { - (void)argv; - if (argc != 1) - return 2; - struct rlimit core = {0, 0}; - setrlimit(RLIMIT_CORE, &core); - prctl(PR_SET_DUMPABLE, 0); - mlock(secret, sizeof(secret)); - /* A parent disappearing must not leave a password worker behind. */ - pid_t parent = getppid(); - prctl(PR_SET_PDEATHSIG, 9); - if (parent == 1 || getppid() != parent) - return 1; - struct passwd *pw = getpwuid(getuid()); - if (!pw || geteuid() != getuid()) - return 1; - username = pw->pw_name; - size_t used = 0; - while (used < sizeof(secret)) { - ssize_t n = read(STDIN_FILENO, secret + used, sizeof(secret) - used); - if (n < 0 && errno == EINTR) - continue; - if (n < 0) - goto done; - if (!n) - break; - used += n; - } - if (!used || used >= sizeof(secret) || memchr(secret, 0, used)) - goto done; - secret[used] = 0; - struct pam_conv conv = {.conv = converse}; - pam_handle_t *pam = NULL; - int result = pam_start(DIGITALRAIN_PAM_SERVICE, username, &conv, &pam); - if (result == PAM_SUCCESS) - result = pam_authenticate(pam, PAM_DISALLOW_NULL_AUTHTOK); - if (result == PAM_SUCCESS) - result = pam_acct_mgmt(pam, PAM_SILENT); - if (pam) - pam_end(pam, result); - explicit_bzero(secret, sizeof(secret)); - /* The parent requires both this byte and a successful worker exit. */ - if (result == PAM_SUCCESS && write(STDOUT_FILENO, "Y", 1) == 1) - return 0; -done: - explicit_bzero(secret, sizeof(secret)); - return 1; -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-config-api.h b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-config-api.h deleted file mode 100644 index b6791dd2c7cc..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-config-api.h +++ /dev/null @@ -1,228 +0,0 @@ -/* File updates use parser source coordinates, then validate the entire result. */ -static void api_copy_settings(const dr_config_t *candidate) { - pthread_mutex_lock(&G.lock); - G.config = *candidate; - pthread_mutex_unlock(&G.lock); -} -static const char *cf_application(const struct api_setting *s) { - if (!strcmp(s->name, "global.LOG_FILE")) - return "restart required"; - if (!strncmp(s->name, "global.", 7)) - return "next manager check"; - if (!strcmp(s->name, "matrix.TRANSPARENT") || !strcmp(s->name, "matrix.HIDE_DESKTOP") || - !strcmp(s->name, "matrix.POWER_OFF")) - return "next activation"; - if (strstr(s->name, "DENSITY")) - return "next grid rebuild or activation"; - if (!strcmp(s->name, "matrix.FONT_SIZE") || !strcmp(s->name, "matrix.DEPTH_LAYERS")) - return "frame boundary; resource rebuild"; - return "live"; -} -static struct json_object *api_schema(void) { - struct json_object *o = json_object_new_object(); - for (size_t i = 0; i < SETTING_COUNT; i++) { - const struct api_setting *s = &api_settings[i]; - struct json_object *v = json_object_new_object(); - json_object_object_add(v, "type", - json_object_new_string(s->type >= 2 ? "string" - : s->type == 1 ? "number" - : "integer")); - if (cf_boolean(s)) - json_object_object_add(v, "toml_type", json_object_new_string("boolean")); - json_object_object_add(v, "value", api_value(s, &G.config)); - json_object_object_add(v, "default", - s->type >= 2 ? json_object_new_string(s->text) - : json_object_new_double(s->initial)); - if (s->type < 2) { - json_object_object_add(v, "minimum", json_object_new_double(s->lo)); - json_object_object_add(v, "maximum", json_object_new_double(s->hi)); - } - json_object_object_add(v, "applies", json_object_new_string(cf_application(s))); - json_object_object_add(v, "file_only", - json_object_new_boolean(!strcmp(s->name, "global.LOG_FILE"))); - json_object_object_add(o, s->name, v); - } - return o; -} -static size_t cf_position(const char *text, int line, int col) { - size_t p = 0; - for (int n = 1; text[p] && n < line; p++) - if (text[p] == '\n') - n++; - return p + (col > 0 ? col - 1 : 0); -} -/* Parsed scalar tokens only: strings can contain delimiters and newlines. */ -static size_t cf_token_end(const char *text, size_t p) { - char quote = text[p]; - if (quote == '\'' || quote == '"') { - bool triple = text[p + 1] == quote && text[p + 2] == quote; - p += triple ? 3 : 1; - while (text[p]) { - if (quote == '"' && text[p] == '\\' && text[p + 1]) { - p += 2; - continue; - } - if (text[p] == quote && (!triple || (text[p + 1] == quote && text[p + 2] == quote))) { - p += triple ? 3 : 1; - if (triple) - while (text[p] == quote) - p++; - return p; - } - p++; - } - return p; - } - while (text[p] && !strchr(" \t\r\n#,}]", text[p])) - p++; - return p; -} -static char *cf_splice(char *text, size_t start, size_t end, const char *value) { - size_t len = strlen(text), size = start + strlen(value) + len - end; - if (start > end || end > len || size > CF_MAX_BYTES) - return NULL; - char *out = malloc(size + 1); - if (out) { - memcpy(out, text, start); - strcpy(out + start, value); - strcpy(out + start + strlen(value), text + end); - } - return out; -} -static bool api_save(struct json_object *values) { - struct stat st; - size_t len; - char *original = cf_read(G.config_file, &len, &st); - if (!original || st.st_uid != getuid()) { - free(original); - return false; - } - if (C.loaded && cf_hash(original, len) != C.hash) { - free(original); - return false; - } - char *text = strdup(original); - bool ok = text != NULL; - json_object_object_foreach(values, key, value) { - if (!ok) - break; - const struct api_setting *s = cf_setting(key); - toml_result_t t = toml_parse(text, strlen(text)); - if (!t.ok || !s) { - toml_free(t); - ok = false; - break; - } - toml_datum_t d = toml_seek(t.toptab, key); - const char *encoded = - cf_boolean(s) ? (json_object_get_boolean(value) ? "true" : "false") - : json_object_to_json_string_ext( - value, JSON_C_TO_STRING_PLAIN | JSON_C_TO_STRING_NOSLASHESCAPE); - size_t start, end; - char addition[2048]; - if (d.type != TOML_UNKNOWN) { - start = cf_position(text, d.lineno, d.colno); - if (d.type == TOML_STRING) { - if (start && text[start - 1] == '\n') { - start--; - if (start && text[start - 1] == '\r') - start--; - } - if (start >= 3 && (text[start - 1] == '\'' || text[start - 1] == '"') && - text[start - 2] == text[start - 1] && text[start - 3] == text[start - 1]) - start -= 3; - else if (start) - start--; - } - end = cf_token_end(text, start); - snprintf(addition, sizeof(addition), "%s", encoded); - } else { - char section[128]; - snprintf(section, sizeof(section), "%s", key); - char *dot = strrchr(section, '.'); - *dot++ = 0; - toml_datum_t parent = toml_seek(t.toptab, section); - if (parent.type == TOML_UNKNOWN) { - start = end = strlen(text); - snprintf(addition, sizeof(addition), "\n[%s]\n%s = %s\n", section, dot, encoded); - } else if (parent.flag & TOML_FLAG_INLINED) { - start = end = cf_position(text, parent.lineno, parent.colno) + 1; - snprintf(addition, sizeof(addition), "%s = %s%s", dot, encoded, - parent.u.tab.size ? ", " : ""); - } else if (parent.flag & TOML_FLAG_STDEXPR) { - start = cf_position(text, parent.lineno, 1); - while (text[start] && text[start] != '\n') - start++; - if (text[start]) - start++; - end = start; - snprintf(addition, sizeof(addition), "%s%s = %s\n", - start && text[start - 1] != '\n' ? "\n" : "", dot, encoded); - } else { - start = end = 0; - snprintf(addition, sizeof(addition), "%s = %s\n", key, encoded); - } - } - char *next = cf_splice(text, start, end, addition); - toml_free(t); - free(text); - text = next; - ok = text != NULL; - } - dr_config_t candidate; - char error[512]; - if (ok) - ok = cf_parse(text, strlen(text), &candidate, error); - char tmp[MAX_PATH_LEN + 32]; - snprintf(tmp, sizeof(tmp), "%s.tmp.XXXXXX", G.config_file); - int fd = ok ? mkstemp(tmp) : -1; - if (fd < 0) - ok = false; - if (ok) { - size_t size = strlen(text), done = 0; - while (done < size) { - ssize_t n = write(fd, text + done, size - done); - if (n < 0 && errno == EINTR) - continue; - if (n <= 0) { - ok = false; - break; - } - done += n; - } - if (fchmod(fd, st.st_mode & 0777) || fsync(fd)) - ok = false; - struct stat current; - size_t current_len; - char *current_text = cf_read(G.config_file, ¤t_len, ¤t); - if (!current_text || current.st_dev != st.st_dev || current.st_ino != st.st_ino || - current_len != len || memcmp(current_text, original, len)) - ok = false; - free(current_text); - } - if (fd >= 0 && close(fd)) - ok = false; - if (ok && rename(tmp, G.config_file)) - ok = false; - if (ok) { - char dir[MAX_PATH_LEN]; - snprintf(dir, sizeof(dir), "%s", G.config_file); - char *slash = strrchr(dir, '/'); - if (slash) { - if (slash == dir) - slash[1] = 0; - else - *slash = 0; - } else - strcpy(dir, "."); - int dfd = open(dir, O_DIRECTORY | O_RDONLY | O_CLOEXEC); - if (dfd >= 0) { - fsync(dfd); - close(dfd); - } - } else if (fd >= 0) - unlink(tmp); - free(original); - free(text); - return ok; -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-config-manager.h b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-config-manager.h deleted file mode 100644 index 8f4e594f7223..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-config-manager.h +++ /dev/null @@ -1,133 +0,0 @@ -/* Config state belongs to the manager thread. Watch the parent for atomic saves. */ -static struct { - int fd, watch; - unsigned revision; - uint64_t hash, attempted_hash; - bool loaded, dirty, force; - double deadline, checked; - char error[512], log_destination[512]; -} C = {.fd = -1, .watch = -1}; -static void mgr_set_defaults(void) { cf_defaults(&G.config); } -static void mgr_apply_output_config(void) { - for (int i = 0; i < G.output_count; i++) { - mgr_output_info_t *o = &G.outputs[i]; - o->timeout_override = o->power_off_override = -1; - o->color[0] = 0; - o->min_speed = o->max_speed = o->min_density = o->max_density = -1; - o->min_trail = o->max_trail = -1; - for (int j = 0; j < G.config.output_count; j++) - if (!strcmp(o->name, G.config.outputs[j].name)) { - o->timeout_override = G.config.outputs[j].timeout; - o->power_off_override = G.config.outputs[j].power_off; - snprintf(o->color, sizeof(o->color), "%s", G.config.outputs[j].color); - } - } -} -static void mgr_config_watch(void) { - if (C.fd < 0) - C.fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); - if (C.fd < 0 || C.watch >= 0) - return; - char dir[MAX_PATH_LEN]; - snprintf(dir, sizeof(dir), "%s", G.config_file); - char *slash = strrchr(dir, '/'); - if (slash) { - if (slash == dir) - slash[1] = 0; - else - *slash = 0; - } else - strcpy(dir, "."); - C.watch = inotify_add_watch(C.fd, dir, - IN_CLOSE_WRITE | IN_MOVED_TO | IN_CREATE | IN_DELETE | - IN_MOVE_SELF | IN_DELETE_SELF); -} -static bool mgr_load_config(void) { - if (!G.config_file[0]) { - const char *explicit = getenv("AMBIENT_BACKGROUND_CONFIG") ?: getenv("DIGITALRAIN_CONFIG"), *xdg = getenv("XDG_CONFIG_HOME"), - *home = getenv("HOME"); - if (explicit && *explicit) - mgr_expand_tilde(explicit, G.config_file, sizeof(G.config_file)); - else { - char path[MAX_PATH_LEN]; - if (xdg && *xdg) - snprintf(path, sizeof(path), "%s/ambient-background.toml", xdg); - else - snprintf(path, sizeof(path), "%s/.config/ambient-background.toml", home ? home : ""); - snprintf(G.config_file, sizeof(G.config_file), "%s", - access(path, R_OK) == 0 ? path : "/etc/ambient-background.toml"); - } - } - mgr_config_watch(); - bool force = C.force; - C.force = false; - size_t len; - struct stat st; - char *text = cf_read(G.config_file, &len, &st); - if (!text) { - snprintf(C.error, sizeof(C.error), "%s: %s", G.config_file, strerror(errno)); - return false; - } - uint64_t hash = cf_hash(text, len); - if (C.loaded && !force && hash == C.hash) { - free(text); - C.error[0] = 0; - mgr_apply_output_config(); - return true; - } - dr_config_t candidate; - char error[512]; - bool ok = cf_parse(text, len, &candidate, error); - free(text); - if (!ok) { - snprintf(C.error, sizeof(C.error), "%s: %s", G.config_file, error); - if (hash != C.attempted_hash) - LOG_WARNING("Config retained: %s", C.error); - C.attempted_hash = hash; - return false; - } - char expanded[512]; - mgr_expand_tilde(candidate.log_file, expanded, sizeof(expanded)); - snprintf(candidate.log_file, sizeof(candidate.log_file), "%s", expanded); - pthread_mutex_lock(&G.lock); - G.config = candidate; - mgr_apply_output_config(); - pthread_mutex_unlock(&G.lock); - G.last_adaptive_sample = 0; - G.config_mtime = st.st_mtim.tv_sec + st.st_mtim.tv_nsec / 1e9; - C.hash = hash; - C.loaded = true; - C.revision++; - C.error[0] = 0; - LOG_INFO("Config revision %u loaded from %s", C.revision, G.config_file); - return true; -} -static void mgr_config_tick(double now) { - if (C.fd >= 0) { - char buffer[8192] __attribute__((aligned(__alignof__(struct inotify_event)))); - ssize_t n; - while ((n = read(C.fd, buffer, sizeof(buffer))) > 0) { - for (char *p = buffer; p < buffer + n;) { - struct inotify_event *e = (void *)p; - const char *base = strrchr(G.config_file, '/'); - base = base ? base + 1 : G.config_file; - if (e->mask & (IN_IGNORED | IN_MOVE_SELF | IN_DELETE_SELF)) { - if (C.watch >= 0) - inotify_rm_watch(C.fd, C.watch); - C.watch = -1; - } - if (!e->len || !strcmp(e->name, base)) { - C.dirty = true; - C.deadline = now + .15; - } - p += sizeof(*e) + e->len; - } - } - } - if ((C.dirty && now >= C.deadline) || now - C.checked >= 10) { - C.dirty = false; - C.checked = now; - mgr_config_watch(); - mgr_load_config(); - } -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-config-tools.h b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-config-tools.h deleted file mode 100644 index 3a885547d5f2..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-config-tools.h +++ /dev/null @@ -1,122 +0,0 @@ -/* Offline validation/conversion. Conversion never overwrites the source. */ -static int cf_cli(int argc, char **argv) { - bool migrate = !strcmp(argv[1], "--migrate-config"); - if ((!migrate && argc != 3) || (migrate && argc != 4)) { - fprintf(stderr, "Use --check-config FILE or --migrate-config OLD.conf NEW.toml\n"); - return 2; - } - struct stat st; - size_t len; - char *text = cf_read(argv[2], &len, &st); - if (!text) { - perror(argv[2]); - return 1; - } - char *converted = NULL; - size_t size = 0; - if (migrate) { - FILE *out = open_memstream(&converted, &size); - if (!out) { - free(text); - return 1; - } - fputs("schema_version = 1\n", out); - char section[128] = "", *line, *save; - bool bad = false; - for (line = strtok_r(text, "\n", &save); line; line = strtok_r(NULL, "\n", &save)) { - if (strlen(line) >= 2048) { - fprintf(stderr, "Legacy config line is too long\n"); - bad = true; - break; - } - char copy[2048]; - snprintf(copy, sizeof(copy), "%s", line); - mgr_trim(copy); - if (!copy[0] || copy[0] == '#' || copy[0] == ';') { - fprintf(out, "%s%s\n", copy[0] == ';' ? "#" : "", copy[0] == ';' ? copy + 1 : line); - continue; - } - if (copy[0] == '[') { - char *end = strchr(copy, ']'); - if (!end) { - bad = true; - break; - } - *end = 0; - snprintf(section, sizeof(section), "%s", copy + 1); - fprintf(out, "[%s]\n", section); - continue; - } - char *eq = strchr(copy, '='); - if (!eq) { - bad = true; - break; - } - *eq++ = 0; - mgr_trim(copy); - mgr_trim(eq); - if (!strcmp(copy, "CMD") || !strcmp(copy, "LOCK_CMD") || - !strcmp(copy, "MOUSE_WARPING")) { - fprintf(stderr, "Removed obsolete %s.%s\n", section, copy); - continue; - } - char key[256]; - snprintf(key, sizeof(key), "%s.%s", section, copy); - const struct api_setting *s = cf_setting(key); - bool string = s ? s->type >= 2 : !strcmp(section, "output_colors"); - bool boolean = s ? cf_boolean(s) : !strcmp(section, "output_power_off"); - if (!s && strcmp(section, "output_colors") && strcmp(section, "output_timeouts") && - strcmp(section, "output_power_off")) { - fprintf(stderr, "Unknown setting %s\n", key); - bad = true; - break; - } - struct json_object *v = json_object_new_string(eq); - struct json_object *k = json_object_new_string(copy); - fprintf(out, "%s = %s\n", - json_object_to_json_string_ext(k, JSON_C_TO_STRING_PLAIN | - JSON_C_TO_STRING_NOSLASHESCAPE), - string ? json_object_to_json_string_ext(v, JSON_C_TO_STRING_PLAIN | - JSON_C_TO_STRING_NOSLASHESCAPE) - : boolean ? (!strcmp(eq, "1") ? "true" - : !strcmp(eq, "0") ? "false" - : "INVALID_BOOLEAN") - : eq); - json_object_put(v); - json_object_put(k); - } - fclose(out); - free(text); - text = converted; - len = size; - if (bad) { - free(text); - return 1; - } - } - dr_config_t c; - char error[512]; - if (!cf_parse(text, len, &c, error)) { - fprintf(stderr, "%s: %s\n", argv[2], error); - free(text); - return 1; - } - if (migrate) { - int fd = open(argv[3], O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0600); - if (fd < 0) { - perror(argv[3]); - free(text); - return 1; - } - bool ok = write(fd, text, len) == (ssize_t)len && fsync(fd) == 0; - close(fd); - if (!ok) { - unlink(argv[3]); - free(text); - return 1; - } - } - puts(migrate ? "Converted and validated; original preserved." : "Configuration valid."); - free(text); - return 0; -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-config.h b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-config.h deleted file mode 100644 index c5d0c5b73aeb..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-config.h +++ /dev/null @@ -1,406 +0,0 @@ -/* Shared configuration schema. Included after rain constants and color presets. */ -#ifndef DIGITALRAIN_CONFIG_H -#define DIGITALRAIN_CONFIG_H -#include "tomlc17.h" -#include <sys/inotify.h> -#include <ctype.h> -typedef struct { - int global_timeout; - int focused_timeout; - int check_interval; - int inhibit_video; - int lock_timeout; - int matrix_transparent; - int matrix_hide_desktop; - int matrix_power_off; - int matrix_transparent_timeout; - int matrix_black_rain_timeout; - int matrix_fade_frames; - int matrix_font_size; - int matrix_depth_layers; - int matrix_min_trail; - int matrix_max_trail; - int matrix_head_hold_frames; - int adaptive_sample_interval; - int adaptive_hue_interval; - double matrix_min_speed; - double matrix_max_speed; - double matrix_avg_speed; - double matrix_min_density; - double matrix_max_density; - double matrix_layer_near_density; - double matrix_layer_far_density; - double matrix_head_mutation_chance; - double matrix_head_white_min; - double matrix_head_white_max; - char matrix_color[32]; - char log_file[512]; - int alerts_enabled, alerts_locked, audio_enabled, audio_locked; - double audio_speed, audio_brightness, audio_attack, audio_release, audio_gain, audio_gate; - char audio_source[128], audio_mode[32]; - char audio_outputs[8][64]; - int audio_output_count; - struct { - char color[32]; - int priority; - double pulse; - } themes[4]; - struct { - char name[64], color[32]; - int timeout, power_off; - } outputs[64]; - int output_count; -} dr_config_t; -struct api_setting { - const char *name; - size_t offset; - int type; - double lo, hi, initial; - size_t width; - const char *text; -}; -#define INT_SETTING(section, key, field, min, max, def) \ - {section "." key, offsetof(dr_config_t, field), 0, min, max, def, 0, NULL} -#define NUM_SETTING(section, key, field, min, max, def) \ - {section "." key, offsetof(dr_config_t, field), 1, min, max, def, 0, NULL} -static const struct api_setting api_settings[] = { - INT_SETTING("global", "TIMEOUT", global_timeout, 1, 86400, 300), - INT_SETTING("global", "FOCUSED_TIMEOUT", focused_timeout, 1, 86400, 30), - INT_SETTING("global", "CHECK_INTERVAL", check_interval, 1, 60, 5), - INT_SETTING("global", "INHIBIT_VIDEO", inhibit_video, 0, 1, 1), - INT_SETTING("global", "LOCK_TIMEOUT", lock_timeout, 0, 86400, 0), - INT_SETTING("matrix", "TRANSPARENT", matrix_transparent, 0, 1, 0), - INT_SETTING("matrix", "HIDE_DESKTOP", matrix_hide_desktop, 0, 1, 0), - INT_SETTING("matrix", "POWER_OFF", matrix_power_off, 0, 1, 0), - INT_SETTING("matrix", "TRANSPARENT_TIMEOUT", matrix_transparent_timeout, 0, 86400, 600), - INT_SETTING("matrix", "BLACK_RAIN_TIMEOUT", matrix_black_rain_timeout, 0, 86400, 0), - INT_SETTING("matrix", "FADE_FRAMES", matrix_fade_frames, 1, 300, 30), - INT_SETTING("matrix", "FONT_SIZE", matrix_font_size, 6, 72, 12), - INT_SETTING("matrix", "DEPTH_LAYERS", matrix_depth_layers, 1, 8, 4), - INT_SETTING("matrix", "MIN_TRAIL", matrix_min_trail, 1, 1000, 4), - INT_SETTING("matrix", "MAX_TRAIL", matrix_max_trail, 1, 1000, 28), - INT_SETTING("matrix", "HEAD_HOLD_FRAMES", matrix_head_hold_frames, 0, 120, 5), - INT_SETTING("matrix", "ADAPTIVE_SAMPLE_INTERVAL", adaptive_sample_interval, 5, 86400, 60), - INT_SETTING("matrix", "ADAPTIVE_HUE_INTERVAL", adaptive_hue_interval, 0, 86400, 900), - NUM_SETTING("matrix", "MIN_SPEED", matrix_min_speed, .001, 100, .4), - NUM_SETTING("matrix", "MAX_SPEED", matrix_max_speed, .001, 100, 2), - NUM_SETTING("matrix", "AVG_SPEED", matrix_avg_speed, .001, 100, .8), - NUM_SETTING("matrix", "MIN_DENSITY", matrix_min_density, 0, 1, .25), - NUM_SETTING("matrix", "MAX_DENSITY", matrix_max_density, 0, 1, .45), - NUM_SETTING("matrix", "LAYER_NEAR_DENSITY", matrix_layer_near_density, 0, 2, .15), - NUM_SETTING("matrix", "LAYER_FAR_DENSITY", matrix_layer_far_density, 0, 2, .35), - NUM_SETTING("matrix", "HEAD_MUTATION_CHANCE", matrix_head_mutation_chance, 0, 1, .15), - NUM_SETTING("matrix", "HEAD_WHITE_MIN", matrix_head_white_min, 0, 1, .75), - NUM_SETTING("matrix", "HEAD_WHITE_MAX", matrix_head_white_max, 0, 1, 1), - {"matrix.COLOR", offsetof(dr_config_t, matrix_color), 2, 0, 0, 0, 32, "green"}, - {"global.LOG_FILE", offsetof(dr_config_t, log_file), 3, 0, 0, 0, 512, ""}, - INT_SETTING("alerts", "ENABLED", alerts_enabled, 0, 1, 1), - INT_SETTING("alerts", "SHOW_WHILE_LOCKED", alerts_locked, 0, 1, 0), - INT_SETTING("audio", "ENABLED", audio_enabled, 0, 1, 0), - INT_SETTING("audio", "SHOW_WHILE_LOCKED", audio_locked, 0, 1, 0), - NUM_SETTING("audio", "SPEED_AMOUNT", audio_speed, 0, 2, .25), - NUM_SETTING("audio", "BRIGHTNESS_AMOUNT", audio_brightness, 0, 1, .10), - NUM_SETTING("audio", "ATTACK_MS", audio_attack, 1, 2000, 50), - NUM_SETTING("audio", "RELEASE_MS", audio_release, 1, 5000, 250), - NUM_SETTING("audio", "GAIN", audio_gain, .01, 100, 4), - NUM_SETTING("audio", "NOISE_GATE", audio_gate, 0, 1, .005), - {"audio.SOURCE", offsetof(dr_config_t, audio_source), 3, 0, 0, 0, 128, "default-output"}, - {"audio.MODE", offsetof(dr_config_t, audio_mode), 3, 0, 0, 0, 32, "energy"}, -}; -#define SETTING_COUNT (sizeof(api_settings) / sizeof(api_settings[0])) -static bool api_color_valid(const char *v) { - if (!v) - return false; - for (size_t i = 0; i < RENDER_NUM_COLORS; i++) - if (!strcmp(v, RENDER_COLOR_TABLE[i].name)) - return true; - const char *names[] = { - "adaptive", "adaptive-light", "adaptive-dark", "random", - "random_named", "random_hex", "random_trail_named", "random_trail_hex"}; - for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i++) - if (!strcmp(v, names[i])) - return true; - if (*v == '#') - v++; - return strlen(v) == 6 && strspn(v, "0123456789abcdefABCDEF") == 6; -} - -static bool cf_boolean(const struct api_setting *s) { - return s->type == 0 && s->lo == 0 && s->hi == 1; -} -static const struct api_setting *cf_setting(const char *name) { - for (size_t i = 0; i < SETTING_COUNT; ++i) - if (!strcmp(name, api_settings[i].name)) - return &api_settings[i]; - return NULL; -} -static void cf_defaults(dr_config_t *c) { - memset(c, 0, sizeof(*c)); - for (size_t i = 0; i < SETTING_COUNT; i++) { - const struct api_setting *s = &api_settings[i]; - char *p = (char *)c + s->offset; - if (s->type >= 2) - snprintf(p, s->width, "%s", s->text); - else if (s->type == 1) - *(double *)p = s->initial; - else - *(int *)p = s->initial; - } - const char *state = getenv("XDG_STATE_HOME"), *home = getenv("HOME"); - if (state && *state) - snprintf(c->log_file, sizeof(c->log_file), "%s/digitalrain/digitalrain.log", state); - else if (home && *home) - snprintf(c->log_file, sizeof(c->log_file), "%s/.local/state/digitalrain/digitalrain.log", - home); - else - snprintf(c->log_file, sizeof(c->log_file), "/tmp/digitalrain.log"); - const char *colors[] = {"#00bfff", "#40ff80", "#ffb000", "#ff4040"}; - for (int i = 0; i < 4; i++) { - snprintf(c->themes[i].color, 32, "%s", colors[i]); - c->themes[i].priority = (i + 1) * 10; - } -} -static struct json_object *api_value(const struct api_setting *s, const dr_config_t *c) { - const char *p = (const char *)c + s->offset; - if (s->type >= 2) - return json_object_new_string(p); - if (s->type == 1) - return json_object_new_double(*(const double *)p); - return json_object_new_int(*(const int *)p); -} -static const char *cf_set(dr_config_t *c, const struct api_setting *s, struct json_object *v, - bool toml) { - char *p = (char *)c + s->offset; - if (s->type >= 2) { - if (!json_object_is_type(v, json_type_string) || - json_object_get_string_len(v) != (int)strlen(json_object_get_string(v)) || - strlen(json_object_get_string(v)) >= s->width) - return "invalid string"; - const char *str = json_object_get_string(v); - if (s->type == 2 && !api_color_valid(str)) - return "invalid color"; - if (!strcmp(s->name, "audio.MODE") && strcmp(str, "energy")) - return "audio.MODE must be energy"; - if (!strcmp(s->name, "audio.SOURCE") && (!*str || strpbrk(str, "\r\n") != NULL)) - return "invalid audio source"; - snprintf(p, s->width, "%s", str); - return NULL; - } - bool boolean = cf_boolean(s); - if (boolean && toml && !json_object_is_type(v, json_type_boolean)) - return "expected TOML boolean"; - if (!(boolean && json_object_is_type(v, json_type_boolean)) && - !json_object_is_type(v, json_type_int) && - !(s->type == 1 && json_object_is_type(v, json_type_double))) - return "incorrect type"; - double n = json_object_is_type(v, json_type_boolean) ? json_object_get_boolean(v) - : json_object_get_double(v); - if (!isfinite(n) || n < s->lo || n > s->hi) - return "outside allowed range"; - if (s->type == 1) - *(double *)p = n; - else - *(int *)p = n; - return NULL; -} -static const char *cf_relationships(const dr_config_t *c) { - if (c->matrix_min_speed > c->matrix_max_speed || c->matrix_min_trail > c->matrix_max_trail || - c->matrix_min_density > c->matrix_max_density || - c->matrix_head_white_min > c->matrix_head_white_max) - return "minimum exceeds maximum"; - return NULL; -} -static const char *api_validate(struct json_object *values, dr_config_t *c) { - if (!json_object_is_type(values, json_type_object)) - return "settings must be an object"; - json_object_object_foreach(values, key, val) { - const struct api_setting *s = cf_setting(key); - if (!s || !strcmp(key, "global.LOG_FILE")) - return "unknown or file-only setting (use schema)"; - const char *e = cf_set(c, s, val, false); - if (e) - return e; - } - return cf_relationships(c); -} -static struct json_object *cf_json(toml_datum_t d) { - switch (d.type) { - case TOML_STRING: - return json_object_new_string_len(d.u.str.ptr, d.u.str.len); - case TOML_BOOLEAN: - return json_object_new_boolean(d.u.boolean); - case TOML_INT64: - return json_object_new_int64(d.u.int64); - case TOML_FP64: - return json_object_new_double(d.u.fp64); - default: - return NULL; - } -} -static bool cf_walk(toml_datum_t table, const char *prefix, dr_config_t *c, char error[512], - int depth) { - if (depth > 4) { - snprintf(error, 512, "config nesting too deep"); - return false; - } - for (int i = 0; i < table.u.tab.size; i++) { - const char *key = table.u.tab.key[i]; - toml_datum_t d = table.u.tab.value[i]; - char full[256]; - if ((int)strlen(key) != table.u.tab.len[i] || - snprintf(full, sizeof(full), "%s%s%s", prefix, *prefix ? "." : "", key) >= - (int)sizeof(full)) { - snprintf(error, 512, "invalid/long key at line %d", d.lineno); - return false; - } - const char *e = NULL; - const struct api_setting *s = cf_setting(full); - if (!strcmp(full, "schema_version")) { - if (d.type != TOML_INT64 || d.u.int64 != 1) - e = "expected schema_version = 1"; - } else if (s) { - struct json_object *v = cf_json(d); - e = cf_set(c, s, v, true); - if (v) - json_object_put(v); - } else if (!strcmp(full, "audio.OUTPUTS")) { - if (d.type != TOML_ARRAY || d.u.arr.size > 8) - e = "expected up to 8 physical connector names"; - else - for (int j = 0; j < d.u.arr.size; j++) { - toml_datum_t a = d.u.arr.elem[j]; - if (a.type != TOML_STRING || a.u.str.len < 1 || a.u.str.len >= 64 || - a.u.str.len != (int)strlen(a.u.s)) { - e = "invalid physical connector name"; - break; - } - snprintf(c->audio_outputs[c->audio_output_count++], 64, "%s", a.u.s); - } - } else if (!strcmp(prefix, "output_timeouts") || !strcmp(prefix, "output_colors") || - !strcmp(prefix, "output_power_off")) { - int j; - for (j = 0; j < c->output_count; j++) - if (!strcmp(c->outputs[j].name, key)) - break; - if (strlen(key) >= 64 || j >= 64) - e = "too many/long output names"; - else { - if (j == c->output_count) { - c->output_count++; - snprintf(c->outputs[j].name, 64, "%s", key); - c->outputs[j].timeout = c->outputs[j].power_off = -1; - } - if (!strcmp(prefix, "output_timeouts")) { - if (d.type != TOML_INT64 || d.u.int64 < 0 || d.u.int64 > 86400) - e = "expected timeout 0..86400"; - else - c->outputs[j].timeout = d.u.int64; - } else if (!strcmp(prefix, "output_power_off")) { - if (d.type != TOML_BOOLEAN) - e = "expected boolean"; - else - c->outputs[j].power_off = d.u.boolean; - } else if (d.type != TOML_STRING || !api_color_valid(d.u.s) || d.u.str.len >= 32 || d.u.str.len != (int)strlen(d.u.s)) - e = "invalid output color"; - else - snprintf(c->outputs[j].color, 32, "%s", d.u.s); - } - } else { - const char *themes[] = {"alerts.info", "alerts.success", "alerts.warning", - "alerts.error"}; - int theme = -1; - for (int j = 0; j < 4; j++) - if (!strcmp(prefix, themes[j])) - theme = j; - if (theme >= 0) { - if (!strcmp(key, "COLOR") && d.type == TOML_STRING && d.u.str.len < 32 && d.u.str.len == (int)strlen(d.u.s) && - api_color_valid(d.u.s) && strncmp(d.u.s, "adaptive", 8) && - strncmp(d.u.s, "random", 6)) - snprintf(c->themes[theme].color, 32, "%s", d.u.s); - else if (!strcmp(key, "PRIORITY") && d.type == TOML_INT64 && d.u.int64 >= 0 && - d.u.int64 <= 100) - c->themes[theme].priority = d.u.int64; - else if (!strcmp(key, "PULSE") && (d.type == TOML_FP64 || d.type == TOML_INT64)) { - double n = d.type == TOML_FP64 ? d.u.fp64 : (double)d.u.int64; - if (!isfinite(n) || n < 0 || n > .25) - e = "PULSE must be 0..0.25"; - else - c->themes[theme].pulse = n; - } else - e = "unknown/invalid alert theme setting"; - } else if (d.type == TOML_TABLE && - (!strcmp(full, "global") || !strcmp(full, "matrix") || - !strcmp(full, "audio") || !strcmp(full, "alerts") || - !strcmp(full, "output_timeouts") || !strcmp(full, "output_power_off") || - !strcmp(full, "output_colors") || !strcmp(full, "alerts.info") || - !strcmp(full, "alerts.success") || !strcmp(full, "alerts.warning") || - !strcmp(full, "alerts.error"))) { - if (!cf_walk(d, full, c, error, depth + 1)) - return false; - } else - e = "unknown setting/table"; - } - if (e) { - snprintf(error, 512, "line %d, %s: %s", d.lineno, full, e); - return false; - } - } - return true; -} -static bool cf_parse(const char *text, size_t len, dr_config_t *c, char error[512]) { - toml_result_t t = toml_parse(text, len); - if (!t.ok) { - snprintf(error, 512, "%s", t.errmsg); - toml_free(t); - return false; - } - cf_defaults(c); - bool ok = cf_walk(t.toptab, "", c, error, 0); - const char *e = cf_relationships(c); - if (ok && e) { - snprintf(error, 512, "%s", e); - ok = false; - } - toml_free(t); - return ok; -} -#define CF_MAX_BYTES (1024 * 1024) -static char *cf_read(const char *path, size_t *len, struct stat *st) { - int fd = open(path, O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK); - if (fd < 0) - return NULL; - if (fstat(fd, st) || !S_ISREG(st->st_mode) || st->st_size > CF_MAX_BYTES) { - close(fd); - errno = EINVAL; - return NULL; - } - char *text = malloc(CF_MAX_BYTES + 1); - if (!text) { - close(fd); - return NULL; - } - size_t n = 0; - for (;;) { - ssize_t count = read(fd, text + n, CF_MAX_BYTES + 1 - n); - if (count < 0 && errno == EINTR) - continue; - if (count < 0 || (n += count) > CF_MAX_BYTES) { - free(text); - close(fd); - return NULL; - } - if (!count) - break; - } - close(fd); - text[n] = 0; - *len = n; - return text; -} -static uint64_t cf_hash(const char *text, size_t n) { - uint64_t h = UINT64_C(1469598103934665603); - for (size_t i = 0; i < n; i++) - h = (h ^ (unsigned char)text[i]) * UINT64_C(1099511628211); - return h; -} -#endif diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-control.h b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-control.h deleted file mode 100644 index 290a668a6a59..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-control.h +++ /dev/null @@ -1,123 +0,0 @@ -/* Local, bounded SOCK_SEQPACKET transport shared by manager and locker. */ -#ifndef DIGITALRAIN_CONTROL_H -#define DIGITALRAIN_CONTROL_H -#include <pwd.h> -#include <spawn.h> -#include <limits.h> -#define DR_PACKET 16384 -#define DR_CLIENTS 16 -extern char **environ; - -static int dr_path(char *path, size_t size, const char *name) { - const char *base = getenv("XDG_RUNTIME_DIR"); - struct stat st; - if (!base || base[0] != '/' || lstat(base, &st) || !S_ISDIR(st.st_mode) || - st.st_uid != getuid() || (st.st_mode & 0077)) { - errno = EACCES; - return -1; - } - char dir[PATH_MAX]; - if (snprintf(dir, sizeof(dir), "%s/digitalrain", base) >= (int)sizeof(dir)) - return -1; - if (mkdir(dir, 0700) && errno != EEXIST) - return -1; - int fd = open(dir, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); - if (fd < 0) - return -1; - int ok = fstat(fd, &st) == 0 && st.st_uid == getuid(); - if (ok && (st.st_mode & 0077)) - ok = fchmod(fd, 0700) == 0; - close(fd); - if (!ok) - return -1; - return snprintf(path, size, "%s/%s", dir, name) < (int)size ? 0 : -1; -} - -static int dr_socket(const char *name, bool server) { - struct sockaddr_un addr = {.sun_family = AF_UNIX}; - if (dr_path(addr.sun_path, sizeof(addr.sun_path), name)) - return -1; - int fd = socket(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - if (fd < 0) - return -1; - if (server) { - /* Caller must own its instance lock before removing a stale socket. */ - if (unlink(addr.sun_path) && errno != ENOENT) - goto fail; - if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) || chmod(addr.sun_path, 0600) || - listen(fd, DR_CLIENTS)) - goto fail; - } else if (connect(fd, (struct sockaddr *)&addr, sizeof(addr))) - goto fail; - return fd; -fail: - close(fd); - return -1; -} - -static int dr_accept(int listener) { - int fd = accept4(listener, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); - if (fd < 0) - return -1; - struct ucred cred; - socklen_t len = sizeof(cred); - if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) || cred.uid != getuid()) { - close(fd); - return -1; - } - return fd; -} - -static struct json_object *dr_receive(int fd) { - char buf[DR_PACKET + 1]; - ssize_t n = recv(fd, buf, DR_PACKET, MSG_DONTWAIT | MSG_TRUNC); - if (n <= 0 || n > DR_PACKET) - return NULL; - buf[n] = 0; - struct json_tokener *tok = json_tokener_new(); - json_tokener_set_flags(tok, JSON_TOKENER_STRICT); - struct json_object *obj = json_tokener_parse_ex(tok, buf, n + 1); - if (json_tokener_get_error(tok) != json_tokener_success || - !json_object_is_type(obj, json_type_object)) { - if (obj) - json_object_put(obj); - obj = NULL; - } - json_tokener_free(tok); - return obj; -} - -static bool dr_send(int fd, struct json_object *obj) { - const char *s = json_object_to_json_string_ext(obj, JSON_C_TO_STRING_PLAIN); - size_t n = strlen(s); - return n <= DR_PACKET && send(fd, s, n, MSG_DONTWAIT | MSG_NOSIGNAL) == (ssize_t)n; -} - -static const char *dr_string(struct json_object *obj, const char *key) { - struct json_object *v; - return obj && json_object_object_get_ex(obj, key, &v) && - json_object_is_type(v, json_type_string) - ? json_object_get_string(v) - : NULL; -} - -static struct json_object *dr_error(const char *message) { - struct json_object *o = json_object_new_object(); - json_object_object_add(o, "error", json_object_new_string(message)); - return o; -} - -static int dr_sibling(char *path, size_t size, const char *name) { - ssize_t n = readlink("/proc/self/exe", path, size - 1); - if (n <= 0 || (size_t)n >= size - 1) - return -1; - path[n] = 0; - char *slash = strrchr(path, '/'); - if (!slash) - return -1; - return snprintf(slash + 1, size - (slash + 1 - path), "%s", name) < - (int)(size - (slash + 1 - path)) - ? 0 - : -1; -} -#endif diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-cursor.cpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-cursor.cpp deleted file mode 100644 index 834e6634d78a..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-cursor.cpp +++ /dev/null @@ -1,104 +0,0 @@ -#include <cmath> -#include <string> - -#include <wayfire/core.hpp> -#include <wayfire/output-layout.hpp> -#include <wayfire/output.hpp> -#include <wayfire/plugin.hpp> -#include <wayfire/plugins/common/shared-core-data.hpp> -#include <wayfire/plugins/ipc/ipc-method-repository.hpp> - -namespace wf -{ -class digitalrain_cursor_plugin_t : public wf::plugin_interface_t -{ - public: - void init() override - { - repo->register_method("digitalrain/cursor_status", - [=] (wf::json_t data) { return this->cursor_status(data); }); - repo->register_method("digitalrain/hide_cursor", - [=] (wf::json_t data) { return this->hide_cursor(data); }); - repo->register_method("digitalrain/unhide_cursor", - [=] (wf::json_t data) { return this->unhide_cursor(data); }); - } - - void fini() override - { - repo->unregister_method("digitalrain/cursor_status"); - repo->unregister_method("digitalrain/hide_cursor"); - repo->unregister_method("digitalrain/unhide_cursor"); - set_cursor_hidden(false); - } - - bool is_unloadable() override - { - return false; - } - - private: - wf::shared_data::ref_ptr_t<wf::ipc::method_repository_t> repo; - bool cursor_hidden = false; - - wf::output_t *get_output_at(wf::pointf_t position) - { - for (auto output : wf::get_core().output_layout->get_outputs()) - { - if (output->get_layout_geometry() & position) - { - return output; - } - } - - return nullptr; - } - - void set_cursor_hidden(bool hidden) - { - if (hidden == cursor_hidden) - { - return; - } - - if (hidden) - { - wf::get_core().hide_cursor(); - } else - { - wf::get_core().unhide_cursor(); - } - - cursor_hidden = hidden; - } - - wf::json_t cursor_status(wf::json_t) - { - wf::json_t response; - auto pos = wf::get_core().get_cursor_position(); - bool valid = std::isfinite(pos.x) && std::isfinite(pos.y); - - response["valid"] = valid; - response["hidden"] = cursor_hidden; - response["x"] = valid ? pos.x : 0.0; - response["y"] = valid ? pos.y : 0.0; - - auto output = valid ? get_output_at(pos) : nullptr; - response["output"] = (output && output->handle) ? output->handle->name : ""; - return response; - } - - wf::json_t hide_cursor(wf::json_t) - { - set_cursor_hidden(true); - return wf::ipc::json_ok(); - } - - wf::json_t unhide_cursor(wf::json_t) - { - set_cursor_hidden(false); - return wf::ipc::json_ok(); - } -}; -} - -DECLARE_WAYFIRE_PLUGIN(wf::digitalrain_cursor_plugin_t); diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-cursor.xml b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-cursor.xml deleted file mode 100644 index 2f9b64289c67..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-cursor.xml +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version='1.0' encoding='utf-8'?> -<wayfire> - <plugin name="digitalrain-cursor"> - <_short>Monitor activity tracking</_short> - <_long>Track physical-monitor input activity for ambient idle policies.</_long> - <category>Utility</category> - </plugin> -</wayfire> diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-desktop.cpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-desktop.cpp deleted file mode 100644 index e664a7f5e19d..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-desktop.cpp +++ /dev/null @@ -1,278 +0,0 @@ -#include "digitalrain-targets.hpp" -#include <algorithm> -#include <optional> -#include <chrono> -#include <map> -#include <vector> -#include <wayfire/core.hpp> -#include <wayfire/output-layout.hpp> -#include <wayfire/output.hpp> -#include <wayfire/plugin.hpp> -#include <wayfire/render-manager.hpp> -#include <wayfire/scene.hpp> -#include <wayfire/util.hpp> -#include <wayfire/view.hpp> -#include <wayfire/view-transform.hpp> -#include <wayfire/plugins/common/shared-core-data.hpp> -#include <wayfire/plugins/ipc/ipc-method-repository.hpp> - -namespace -{ -/* Fade only the pixels within a monitor, including windows spanning a seam. */ -class monitor_fade_t : public wf::scene::view_2d_transformer_t -{ - public: - using view_2d_transformer_t::view_2d_transformer_t; - std::optional<wf::geometry_t> region; - class instance_t : public wf::scene::transformer_render_instance_t<monitor_fade_t> - { - public: - using transformer_render_instance_t::transformer_render_instance_t; - void render(const wf::scene::render_instruction_t& data) override - { - auto faded = self->region ? data.damage & *self->region : data.damage; - auto normal = data.damage ^ faded; - if (normal.empty() && (faded.empty() || self->alpha <= 0)) return; - auto texture = get_texture(data.target.scale); - if (!texture) return; - auto box = self->get_bounding_box(); - if (!normal.empty()) data.pass->add_texture(texture, data.target, box, normal); - if (!faded.empty() && self->alpha > 0) - data.pass->add_texture(texture, data.target, box, faded, self->alpha); - } - }; - void gen_render_instances(std::vector<wf::scene::render_instance_uptr>& instances, - wf::scene::damage_callback damage, wf::output_t *output) override - { - instances.push_back(std::make_unique<instance_t>(this, damage, output)); - } -}; - -/* Hide desktop content only while the renderer holds its IPC connection. - * Disconnect (including SIGKILL), plugin unload and output removal all restore - * our own scene enable references without changing any window's opacity/state. */ -class digitalrain_desktop_plugin_t : public wf::plugin_interface_t -{ - using client_t = wf::ipc::client_interface_t; - struct hidden_output_t - { - wf::output_t *output; - std::vector<wf::scene::node_ptr> nodes; - std::vector<std::shared_ptr<monitor_fade_t>> fades; - std::chrono::steady_clock::time_point started; - std::string name; - int fade_ms; - bool disabled = false; - bool revealing = false; - float alpha = 1.0f; - float start_alpha = 0.0f; - std::optional<wf::geometry_t> region = std::nullopt; - }; - wf::shared_data::ref_ptr_t<wf::ipc::method_repository_t> repo; - std::map<client_t*, hidden_output_t> hidden; - wf::wl_timer<true> fade_timer; - - static bool belongs_to(wayfire_view view, const hidden_output_t& state) - { - if (!view->is_mapped()) return false; - for (wf::scene::node_t *node = view->get_root_node().get(); node; node = node->parent()) - for (auto& layer : state.nodes) - if (node == layer.get()) return true; - return false; - } - - static void clear_fades(hidden_output_t& state) - { - for (auto& fade : state.fades) - if (auto view = fade->view.lock()) - view->get_transformed_node()->rem_transformer(fade); - state.fades.clear(); - } - - static bool update_fades(hidden_output_t& state) - { - bool changed = false; - // Remove our fade if a window moved to another output/layer. - for (auto it = state.fades.begin(); it != state.fades.end();) - { - auto view = (*it)->view.lock(); - if (!view || !belongs_to(view->self(), state)) - { - if (view) view->get_transformed_node()->rem_transformer(*it); - it = state.fades.erase(it); - changed = true; - } else ++it; - } - for (auto view : wf::get_core().get_all_views()) - { - if (!belongs_to(view, state)) continue; - auto manager = view->get_transformed_node(); - auto fade = manager->get_transformer<monitor_fade_t>(state.name); - if (!fade) - { - fade = std::make_shared<monitor_fade_t>(view); - manager->add_transformer(fade, wf::TRANSFORMER_BLUR + 1, state.name); - state.fades.push_back(fade); - changed = true; - } - fade->alpha = state.alpha; - fade->region = state.region; - } - return changed; - } - - bool animate() - { - bool running = false; - for (auto it = hidden.begin(); it != hidden.end();) - { - auto& [client, state] = *it++; - if (state.disabled) { - if (state.region) { - running = true; - if (update_fades(state)) state.output->render->damage_whole(); - } - continue; - } - auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>( - std::chrono::steady_clock::now() - state.started).count(); - if (elapsed >= state.fade_ms) - { - if (state.revealing) - { - restore(client); - continue; - } - state.disabled = true; - state.alpha = 0.0f; - if (state.region) { - update_fades(state); - running = true; - } else { - for (auto& node : state.nodes) wf::scene::set_node_enabled(node, false); - clear_fades(state); - } - } else - { - running = true; - float progress = float(elapsed) / state.fade_ms; - state.alpha = state.revealing ? - state.start_alpha + (1.0f - state.start_alpha) * progress : 1.0f - progress; - update_fades(state); - } - state.output->render->damage_whole(); - } - return running; - } - - void restore(client_t *client) - { - auto it = hidden.find(client); - if (it == hidden.end()) return; - clear_fades(it->second); - if (it->second.disabled && !it->second.region) - for (auto& node : it->second.nodes) - wf::scene::set_node_enabled(node, true); - it->second.output->render->damage_whole(); - hidden.erase(it); - // The timer stops itself on its next tick. Do not disconnect it from - // inside animate(), where restoring the final client also comes here. - } - - wf::json_t reveal(client_t *client) - { - auto it = hidden.find(client); - if (it == hidden.end() || it->second.revealing) return wf::ipc::json_ok(); - animate(); // Reverse from the actual opacity, including a partial fade-out. - auto& state = it->second; - state.revealing = true; - state.start_alpha = state.alpha; - state.started = std::chrono::steady_clock::now(); - if (state.disabled) - { - // Install zero-opacity transforms before making the layers visible. - update_fades(state); - if (!state.region) - for (auto& node : state.nodes) wf::scene::set_node_enabled(node, true); - state.disabled = false; - } - if (animate() && !fade_timer.is_connected()) - fade_timer.set_timeout(16, [this] { return animate(); }); - return wf::ipc::json_ok(); - } - - wf::signal::connection_t<wf::ipc::client_disconnected_signal> on_disconnect = - [this] (auto ev) { restore(ev->client); }; - wf::signal::connection_t<wf::output_pre_remove_signal> on_output_removed = - [this] (auto ev) - { - for (auto it = hidden.begin(); it != hidden.end();) - { - auto current = it++; - if (current->second.output == ev->output) restore(current->first); - } - }; - - public: - void init() override - { - repo->connect(&on_disconnect); - wf::get_core().output_layout->connect(&on_output_removed); - repo->register_method("digitalrain/show_desktop", [this] (wf::json_t, client_t *client) - { - return reveal(client); - }); - repo->register_method("digitalrain/hide_desktop", [this] (wf::json_t data, client_t *client) - { - if (!client || !data.has_member("output") || !data["output"].is_string()) - return wf::ipc::json_error("An IPC connection and output name are required"); - auto name = data["output"].as_string(); - auto output = wf::get_core().output_layout->find_output(name); - std::optional<wf::geometry_t> region; - if (!output) { - for (auto candidate : wf::get_core().output_layout->get_outputs()) - for (const auto& target : digitalrain_targets(candidate)) - if (target.name == name) { output = candidate; region = target.geometry; } - } - if (!output) return wf::ipc::json_error("Output not found"); - if (hidden.count(client) && hidden.at(client).output == output && hidden.at(client).region == region) - return wf::ipc::json_ok(); - restore(client); - int fade_ms = 0; - if (data.has_member("fade_ms")) - { - if (!data["fade_ms"].is_int()) return wf::ipc::json_error("fade_ms must be an integer"); - fade_ms = std::clamp(data["fade_ms"].as_int(), 0, 10000); - } - hidden_output_t state{output, {}, {}, std::chrono::steady_clock::now(), - "digitalrain-desktop-" + std::to_string(reinterpret_cast<uintptr_t>(client)), fade_ms}; - state.region = region; - using layer = wf::scene::layer; - // Keep wallpaper, screensaver and session-lock layers visible. - for (auto desktop_layer : {layer::BOTTOM, layer::WORKSPACE, layer::TOP, - layer::UNMANAGED, layer::OVERLAY, layer::DWIDGET}) - { - auto node = output->node_for_layer(desktop_layer); - if (!node) continue; - state.nodes.push_back(node); - } - hidden.emplace(client, std::move(state)); - if (animate() && !fade_timer.is_connected()) - fade_timer.set_timeout(16, [this] { return animate(); }); - return wf::ipc::json_ok(); - }); - } - - void fini() override - { - repo->unregister_method("digitalrain/hide_desktop"); - repo->unregister_method("digitalrain/show_desktop"); - fade_timer.disconnect(); - on_disconnect.disconnect(); - on_output_removed.disconnect(); - while (!hidden.empty()) restore(hidden.begin()->first); - } -}; -} - -DECLARE_WAYFIRE_PLUGIN(digitalrain_desktop_plugin_t); diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-desktop.xml b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-desktop.xml deleted file mode 100644 index 5f244dce7d1f..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-desktop.xml +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version='1.0' encoding='utf-8'?> -<wayfire> - <plugin name="digitalrain-desktop"> - <_short>Legacy desktop fading</_short> - <_long>Desktop fading support for compatible legacy saver clients.</_long> - <category>Utility</category> - </plugin> -</wayfire> diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-idle.cpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-idle.cpp deleted file mode 100644 index eb099b25b8be..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-idle.cpp +++ /dev/null @@ -1,181 +0,0 @@ -#include "digitalrain-targets.hpp" -#include <set> -#include <map> -#include <algorithm> -#include <string> -#include <wayfire/core.hpp> -#include <wayfire/output.hpp> -#include <wayfire/output-layout.hpp> -#include <wayfire/plugin.hpp> -#include <wayfire/view.hpp> -#include <wayfire/seat.hpp> -#include <wayfire/toplevel-view.hpp> -#include <wayfire/signal-definitions.hpp> -#include <wayfire/nonstd/wlroots-full.hpp> -#include <wayfire/plugins/common/shared-core-data.hpp> -#include <wayfire/plugins/ipc/ipc-method-repository.hpp> -#include <wayfire/plugins/ipc/ipc-helpers.hpp> -extern "C" { -#include <wlr/types/wlr_xdg_shell.h> -} - -/* Expose per-surface keep-awake requests without turning them into a global - * inhibition. This is separate from the non-unloadable cursor plugin so it - * can be enabled on an existing desktop without restarting Wayfire. */ -namespace { -class digitalrain_idle_plugin_t : public wf::plugin_interface_t -{ - wf::shared_data::ref_ptr_t<wf::ipc::method_repository_t> repo; - using client_t = wf::ipc::client_interface_t; - // A portal request holds one IPC connection and one exact window lifetime. - // Never turn an application id or a fullscreen state into a global lease. - std::map<client_t*, std::weak_ptr<wf::view_interface_t>> portal; - - static pid_t view_pid(wayfire_view view) - { - pid_t pid = -1; - if (view && view->get_client()) - wl_client_get_credentials(view->get_client(), &pid, nullptr, nullptr); - return pid; - } - - static void add_outputs(wayfire_view view, std::set<std::string>& outputs) - { - if (!view || !view->is_mapped() || !view->get_output()) return; - if (auto top = wf::toplevel_cast(view); top && top->minimized) return; - for (wf::scene::node_t *node = view->get_root_node().get(); node; node = node->parent()) - if (!node->is_enabled()) return; - auto output = view->get_output(); - auto box = view->get_bounding_box(); - auto screen = output->get_relative_geometry(); - if (wf::geometry_intersection(box, screen).width <= 0 || - wf::geometry_intersection(box, screen).height <= 0) return; - for (const auto& target : digitalrain_targets(output)) - { - auto overlap = wf::geometry_intersection(box, target.geometry); - if (overlap.width > 0 && overlap.height > 0) outputs.insert(target.name); - } - } - - wf::json_t portal_inhibit(wf::json_t data, client_t *client) - { - if (!client || portal.count(client) || portal.size() >= 128) - return wf::ipc::json_error("One bounded portal lease per IPC connection is required"); - auto pid = wf::ipc::json_get_int64(data, "pid"); - auto window = wf::ipc::json_get_string(data, "window"); - if (pid <= 0 || pid > INT32_MAX || window.size() > 512) - return wf::ipc::json_error("Invalid portal window identity"); - wayfire_view chosen; - if (window.rfind("wayland:", 0) == 0) - { - auto exported = wlr_xdg_foreign_registry_find_by_handle( - wf::get_core().protocols.foreign_registry, window.c_str() + 8); - if (exported) - chosen = wf::wl_surface_to_wayfire_view(exported->toplevel->base->surface->resource); - if (!chosen || view_pid(chosen) != pid) - return wf::ipc::json_error("Portal parent does not belong to its caller"); - } else - { - // Firefox sends g_get_prgname() here, not a native parent handle. - // A bare placeholder is no identity: select only by the original - // caller PID, just as for an omitted parent. Unsupported prefixed - // handles are rejected; invalid wayland: handles never fall back. - if (window.find(':') != std::string::npos) - return wf::ipc::json_error("Only native Wayland parent handles are supported"); - // Bind the focused root, or sole mapped root. Ambiguous unfocused - // multi-window apps must provide a native parent instead. - std::vector<wayfire_view> candidates; - for (auto view : wf::get_core().get_all_views()) - if (auto top = wf::toplevel_cast(view); top && !top->parent && - top->is_mapped() && !top->minimized && view_pid(view) == pid) - candidates.push_back(view); - auto active = wf::get_core().seat->get_active_view(); - while (auto top = wf::toplevel_cast(active)) - { - if (!top->parent) break; - active = top->parent; - } - if (std::find(candidates.begin(), candidates.end(), active) != candidates.end()) - chosen = active; - else if (candidates.size() == 1) chosen = candidates.front(); - } - if (!chosen || !chosen->is_mapped() || !wf::toplevel_cast(chosen)) - return wf::ipc::json_error("No unambiguous mapped portal window"); - portal[client] = chosen->weak_from_this(); - auto result = wf::ipc::json_ok(); - result["view-id"] = chosen->get_id(); - return result; - } - - wf::signal::connection_t<wf::ipc::client_disconnected_signal> disconnected = [this] (auto ev) - { portal.erase(ev->client); }; - wf::signal::connection_t<wf::view_pre_unmap_signal> unmapped = [this] (auto ev) - { - for (auto it = portal.begin(); it != portal.end();) - if (auto view = it->second.lock(); !view || view.get() == ev->view.get()) - it = portal.erase(it); - else ++it; - }; - - wf::json_t status() - { - std::set<std::string> outputs; - for (const auto& [_, weak] : portal) - if (auto view = weak.lock()) add_outputs(view->self(), outputs); - auto manager = wf::get_core().protocols.idle_inhibit; - if (manager) - { - wlr_idle_inhibitor_v1 *inhibitor; - wl_list_for_each(inhibitor, &manager->inhibitors, link) - { - auto surface = wlr_surface_get_root_surface(inhibitor->surface); - auto view = wf::wl_surface_to_wayfire_view(surface->resource); - // No occlusion test: a saver overlay must not hide its - // application's request from the manager below it. - add_outputs(view, outputs); - } - } - - wf::json_t response; - response["outputs"] = wf::json_t::array(); - for (const auto& output : outputs) - response["outputs"].append(output); - response["portal-requests"] = (int)portal.size(); - return response; - } - - public: - void init() override - { - repo->connect(&disconnected); - wf::get_core().connect(&unmapped); - repo->register_method("digitalrain/portal_inhibit", [this] (wf::json_t data, client_t *client) - { return portal_inhibit(data, client); }); - repo->register_method("digitalrain/idle_inhibitors", - [this] (wf::json_t) { return status(); }); - repo->register_method("digitalrain/lock_status", [] (wf::json_t) - { - bool inhibited = false; - for (auto output : wf::get_core().output_layout->get_outputs()) - inhibited |= output->is_inhibited(); - // Wayfire session-lock owns output input inhibition, including its - // crashed-client fallback. This method cannot release inhibition. - wf::json_t response; - response["protected"] = inhibited; - return response; - }); - } - - void fini() override - { - disconnected.disconnect(); - unmapped.disconnect(); - portal.clear(); - repo->unregister_method("digitalrain/portal_inhibit"); - repo->unregister_method("digitalrain/idle_inhibitors"); - repo->unregister_method("digitalrain/lock_status"); - } -}; - -} // Keep implementation symbols local when an old helper is still resident. -DECLARE_WAYFIRE_PLUGIN(digitalrain_idle_plugin_t); diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-idle.xml b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-idle.xml deleted file mode 100644 index 1bb955ed4786..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-idle.xml +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version='1.0' encoding='utf-8'?> -<wayfire> - <plugin name="digitalrain-idle"> - <_short>Presentation inhibitors</_short> - <_long>Honor application and browser idle inhibitors on their physical monitors.</_long> - <category>Utility</category> - </plugin> -</wayfire> diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-live-manager.h b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-live-manager.h deleted file mode 100644 index 412d257c2c13..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-live-manager.h +++ /dev/null @@ -1,386 +0,0 @@ -#include "digitalrain-audio-protocol.h" -struct dr_alert { - char source[64], id[64], output[64], message[128]; - int severity; - double until; - uint64_t sequence; -}; -static struct { - struct dr_alert alerts[64]; - uint64_t sequence; - int audio_fd; - pid_t audio_pid; - double retry, stopping, started; - unsigned failures, locker_revision; - char source[128]; - dr_audio_packet sample; - dr_effect_packet locker_effects; - bool locker_black, audio_failed; -} U = {.audio_fd = -1}; -static void live_locker_reset(void) { - U.locker_revision = 0; - U.locker_black = false; - memset(&U.locker_effects, 0, sizeof(U.locker_effects)); -} -static void live_locker_state(struct json_object *o) { - const char *phase = dr_string(o, "phase"); - U.locker_black = phase && !strcmp(phase, "black"); -} -static const char *alert_names[] = {"info", "success", "warning", "error"}; -static const struct dr_alert *live_winner(const char *output, double now, bool locked) { - if (!G.config.alerts_enabled || (locked && !G.config.alerts_locked)) - return NULL; - const struct dr_alert *winner = NULL; - for (int i = 0; i < 64; i++) { - const struct dr_alert *a = &U.alerts[i]; - if (a->until <= now || (a->output[0] && strcmp(a->output, output))) - continue; - int priority = G.config.themes[a->severity].priority; - if (!winner || priority > G.config.themes[winner->severity].priority || - (priority == G.config.themes[winner->severity].priority && - a->sequence > winner->sequence)) - winner = a; - } - return winner; -} -static bool live_audio_output(const char *output) { - if (!G.config.audio_output_count) - return true; - for (int i = 0; i < G.config.audio_output_count; i++) - if (!strcmp(output, G.config.audio_outputs[i])) - return true; - return false; -} -static bool live_audio_needed(void) { - if (!G.config.audio_enabled) - return false; - bool locked = api_locked(); - if (locked && (!G.config.audio_locked || U.locker_black || strcmp(A.lock_state, "locked"))) - return false; - for (int i = 0; i < G.output_count; i++) { - mgr_output_info_t *o = &G.outputs[i]; - if (live_audio_output(o->name) && - (locked || - (!strcmp(o->state, "off") && o->render_phase >= 0 && o->render_phase != PHASE_BLACK && - !o->powered_off && o->preview_until <= mgr_monotime()))) - return true; - } - return false; -} -static void live_audio_stop(void) { - if (U.audio_fd >= 0) { - close(U.audio_fd); - U.audio_fd = -1; - } - memset(&U.sample, 0, sizeof(U.sample)); - if (U.audio_pid && !U.stopping) { - kill(U.audio_pid, SIGTERM); - U.stopping = mgr_monotime(); - } -} -static void live_audio_tick(double now) { - if (U.audio_pid) { - int status; - pid_t result = waitpid(U.audio_pid, &status, WNOHANG); - if (result == U.audio_pid || (result < 0 && errno == ECHILD)) { - bool intentional = U.stopping > 0 && !U.audio_failed; - if (U.audio_fd >= 0) - close(U.audio_fd); - U.audio_fd = -1; - U.audio_pid = 0; - U.stopping = 0; - U.audio_failed = false; - memset(&U.sample, 0, sizeof(U.sample)); - if (!intentional) - U.retry = now + fmin(30, 1u << (U.failures < 5 ? U.failures++ : 5)); - } else if (U.stopping && now - U.stopping > 2) - kill(U.audio_pid, SIGKILL); - } - if (!live_audio_needed() || (U.audio_pid && strcmp(U.source, G.config.audio_source))) { - live_audio_stop(); - return; - } - if (U.audio_pid && !U.stopping && now - U.started > 5 && now - U.sample.time > 5) { - U.audio_failed = true; - live_audio_stop(); - return; - } - if (!U.audio_pid && now >= U.retry) { - int pair[2]; - char path[PATH_MAX]; - if (dr_sibling(path, sizeof(path), "digitalrain-audio") || - socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, pair)) { - U.retry = now + 30; - return; - } - posix_spawn_file_actions_t actions; - posix_spawn_file_actions_init(&actions); - posix_spawn_file_actions_adddup2(&actions, pair[1], 3); - posix_spawn_file_actions_addclosefrom_np(&actions, 4); - char *argv[] = {path, "3", G.config.audio_source, NULL}; - int rc = posix_spawn(&U.audio_pid, path, &actions, NULL, argv, environ); - posix_spawn_file_actions_destroy(&actions); - close(pair[1]); - if (rc) { - close(pair[0]); - U.audio_pid = 0; - U.retry = now + 30; - } else { - U.audio_failed = false; - U.audio_fd = pair[0]; - U.started = now; - snprintf(U.source, sizeof(U.source), "%s", G.config.audio_source); - } - } - if (U.audio_fd >= 0) { - dr_audio_packet p; - ssize_t n; - for (int i = 0; i < 8; i++) { - n = recv(U.audio_fd, &p, sizeof(p), MSG_DONTWAIT | MSG_TRUNC); - if (n <= 0) { - if (n == 0 || (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) { - U.audio_failed = true; - live_audio_stop(); - } - break; - } - if (n == sizeof(p) && p.magic == DR_AUDIO_MAGIC && p.available <= 1 && - isfinite(p.time) && fabs(now - p.time) < 2 && isfinite(p.rms) && p.rms >= 0 && - p.rms <= 1 && isfinite(p.peak) && p.peak >= 0 && p.peak <= 1) { - U.sample = p; - if (p.available) - U.failures = 0; - } - } - } -} -static void live_effect(dr_effect_t *e, const mgr_output_info_t *o, bool locked, double now) { - memset(e, 0, sizeof(*e)); - snprintf(e->output, sizeof(e->output), "%s", o->name); - if (locked) { - e->x = o->target.x; - e->y = o->target.y; - e->width = o->target.width; - e->height = o->target.height; - } - const struct dr_alert *a = live_winner(o->name, now, locked); - if (a) { - rgb_t color = render_lookup_color(G.config.themes[a->severity].color); - e->color[0] = color.r; - e->color[1] = color.g; - e->color[2] = color.b; - e->until = a->until; - e->pulse = G.config.themes[a->severity].pulse; - } - if (G.config.audio_enabled && (!locked || G.config.audio_locked) && - live_audio_output(o->name)) { - e->speed = G.config.audio_speed; - e->brightness = G.config.audio_brightness; - if (U.sample.available && now - U.sample.time < .3) { - e->audio_time = U.sample.time; - e->energy = U.sample.rms >= G.config.audio_gate - ? fmin(1, U.sample.rms * G.config.audio_gain) - : 0; - e->peak = U.sample.rms >= G.config.audio_gate - ? fmin(1, U.sample.peak * G.config.audio_gain) - : 0; - } - } -} -static void live_tick(double now) { - live_audio_tick(now); - for (int i = 0; i < G.output_count; i++) { - mgr_output_info_t *o = &G.outputs[i]; - if (o->report_fd < 0 || o->preview_until > now) - continue; - if (o->sent_revision != C.revision) { - dr_config_packet p = {.magic = DR_LIVE_MAGIC, - .kind = DR_CONFIG, - .revision = C.revision, - .config = G.config}; - if (o->color[0]) - snprintf(p.config.matrix_color, sizeof(p.config.matrix_color), "%s", o->color); - if (send(o->report_fd, &p, sizeof(p), MSG_DONTWAIT | MSG_NOSIGNAL) == sizeof(p)) - o->sent_revision = C.revision; - else - continue; /* control has priority over effects */ - } - if (o->render_phase == PHASE_BLACK || o->powered_off) - continue; - dr_effect_packet p = {.magic = DR_LIVE_MAGIC, .kind = DR_EFFECTS, .count = 1}; - live_effect(&p.effects[0], o, false, now); - if (!p.effects[0].until && !p.effects[0].audio_time) - p.count = 0; - if (memcmp(&p, &o->sent_effects, sizeof(p)) && - send(o->report_fd, &p, sizeof(p), MSG_DONTWAIT | MSG_NOSIGNAL) == sizeof(p)) - o->sent_effects = p; - } - if (A.locker >= 0) { - if (U.locker_revision != C.revision) { - dr_config_packet p = {.magic = DR_LIVE_MAGIC, - .kind = DR_CONFIG, - .revision = C.revision, - .config = G.config}; - if (send(A.locker, &p, sizeof(p), MSG_DONTWAIT | MSG_NOSIGNAL) == sizeof(p)) - U.locker_revision = C.revision; - else - return; - } - if (U.locker_black) - return; - dr_effect_packet p = {.magic = DR_LIVE_MAGIC, .kind = DR_EFFECTS, .count = G.output_count}; - for (int i = 0; i < G.output_count; i++) - live_effect(&p.effects[i], &G.outputs[i], true, now); - if (memcmp(&p, &U.locker_effects, sizeof(p)) && - send(A.locker, &p, sizeof(p), MSG_DONTWAIT | MSG_NOSIGNAL) == sizeof(p)) - U.locker_effects = p; - } -} -static void live_status(struct json_object *o, bool event) { - (void)event; - double now = mgr_monotime(); - int count = 0; - for (int i = 0; i < 64; i++) - count += U.alerts[i].until > now; - json_object_object_add(o, "alert_count", json_object_new_int(count)); - json_object_object_add(o, "alert_revision", json_object_new_int64(U.sequence)); - struct json_object *audio = json_object_new_object(); - json_object_object_add(audio, "enabled", json_object_new_boolean(G.config.audio_enabled)); - json_object_object_add(audio, "source", json_object_new_string(G.config.audio_source)); - json_object_object_add(audio, "state", - json_object_new_string(!G.config.audio_enabled ? "disabled" - : !live_audio_needed() ? "idle" - : U.audio_fd < 0 ? "unavailable" - : U.sample.available && now - U.sample.time < 2 - ? "monitoring" - : "waiting_for_source")); - json_object_object_add(o, "audio", audio); - struct json_object *effects = json_object_new_object(); - for (int i = 0; i < G.output_count; i++) { - const struct dr_alert *a = live_winner(G.outputs[i].name, now, api_locked()); - struct json_object *e = json_object_new_object(); - if (a) { - json_object_object_add(e, "source", json_object_new_string(a->source)); - json_object_object_add(e, "id", json_object_new_string(a->id)); - json_object_object_add(e, "severity", json_object_new_string(alert_names[a->severity])); - } - json_object_object_add( - e, "visible", - json_object_new_boolean(a && - (api_locked() ? !U.locker_black - : G.outputs[i].render_phase >= 0 && - G.outputs[i].render_phase != PHASE_BLACK && - !strcmp(G.outputs[i].state, "off") && - G.outputs[i].preview_until <= now))); - json_object_object_add(effects, G.outputs[i].name, e); - } - json_object_object_add(o, "alerts_by_output", effects); -} -static bool live_string(struct json_object *o, const char *key, size_t max, const char **out) { - struct json_object *v; - *out = dr_string(o, key); - return *out && **out && strlen(*out) <= max && json_object_object_get_ex(o, key, &v) && - json_object_get_string_len(v) == (int)strlen(*out); -} -static struct json_object *live_alert_request(const char *method, struct json_object *req) { - bool list = !strcmp(method, "list_alerts"), clear = !strcmp(method, "clear_alert"); - json_object_object_foreach(req, key, val) { - (void)val; - if (!strcmp(key, "version") || !strcmp(key, "method")) - continue; - if (list && (!strcmp(key, "offset") || !strcmp(key, "limit"))) - continue; - if (!list && (!strcmp(key, "source") || !strcmp(key, "id"))) - continue; - if (!list && !clear && - (!strcmp(key, "severity") || !strcmp(key, "seconds") || !strcmp(key, "output") || - !strcmp(key, "message"))) - continue; - return dr_error("unexpected alert field"); - } - double now = mgr_monotime(); - if (list) { - double offset = 0, limit = 16; - struct json_object *v; - if ((json_object_object_get_ex(req, "offset", &v) && - (!api_number(req, "offset", &offset) || offset < 0 || offset > 64 || - floor(offset) != offset)) || - (json_object_object_get_ex(req, "limit", &v) && - (!api_number(req, "limit", &limit) || limit < 1 || limit > 16 || - floor(limit) != limit))) - return dr_error("offset 0..64 and limit 1..16 required"); - struct json_object *o = json_object_new_object(), *arr = json_object_new_array(); - int seen = 0; - for (int i = 0; i < 64; i++) - if (U.alerts[i].until > now) { - struct dr_alert *a = &U.alerts[i]; - int index = seen++; - if (index < offset || index >= offset + limit) - continue; - struct json_object *v = json_object_new_object(); - json_object_object_add(v, "source", json_object_new_string(a->source)); - json_object_object_add(v, "id", json_object_new_string(a->id)); - json_object_object_add(v, "output", json_object_new_string(a->output)); - json_object_object_add(v, "severity", - json_object_new_string(alert_names[a->severity])); - json_object_object_add(v, "message", json_object_new_string(a->message)); - json_object_object_add(v, "remaining_seconds", - json_object_new_double(a->until - now)); - json_object_array_add(arr, v); - } - json_object_object_add(o, "alerts", arr); - json_object_object_add(o, "total", json_object_new_int(seen)); - json_object_object_add( - o, "next_offset", - json_object_new_int(offset + limit < seen ? (int)(offset + limit) : -1)); - return o; - } - const char *source, *id; - if (!live_string(req, "source", 63, &source) || !live_string(req, "id", 63, &id)) - return dr_error("source and id required (1..63 bytes)"); - int slot = -1, free_slot = -1; - for (int i = 0; i < 64; i++) { - if (U.alerts[i].until <= now) - free_slot = i; - if (!strcmp(U.alerts[i].source, source) && !strcmp(U.alerts[i].id, id)) - slot = i; - } - if (clear) { - if (slot >= 0) - U.alerts[slot].until = 0; - U.sequence++; - return json_object_new_object(); - } - const char *severity = dr_string(req, "severity"), *output = dr_string(req, "output"), - *message = dr_string(req, "message"); - int level = -1; - for (int i = 0; severity && i < 4; i++) - if (!strcmp(severity, alert_names[i])) - level = i; - double seconds; - struct json_object *v; - if (level < 0 || !api_number(req, "seconds", &seconds) || seconds < 1 || seconds > 86400) - return dr_error("severity and seconds 1..86400 required"); - if (json_object_object_get_ex(req, "output", &v) && (!output || !mgr_find_output(output))) - return dr_error("unknown physical output"); - if (json_object_object_get_ex(req, "message", &v) && - (!message || strlen(message) > 127 || - json_object_get_string_len(v) != (int)strlen(message))) - return dr_error("message must be <=127 bytes"); - if (slot < 0) - slot = free_slot; - if (slot < 0) - return dr_error("64 active alerts already exist"); - struct dr_alert *a = &U.alerts[slot]; - memset(a, 0, sizeof(*a)); - snprintf(a->source, sizeof(a->source), "%s", source); - snprintf(a->id, sizeof(a->id), "%s", id); - snprintf(a->output, sizeof(a->output), "%s", output ? output : ""); - snprintf(a->message, sizeof(a->message), "%s", message ? message : ""); - a->severity = level; - a->until = now + seconds; - a->sequence = ++U.sequence; - struct json_object *o = json_object_new_object(); - json_object_object_add(o, "accepted", json_object_new_boolean(true)); - return o; -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-live-render.h b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-live-render.h deleted file mode 100644 index ea7cca333a1f..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-live-render.h +++ /dev/null @@ -1,219 +0,0 @@ -static int render_effect_index(int x, int y) { - for (unsigned i = 0; i < S.effects.count; i++) { - dr_effect_t *e = &S.effects.effects[i]; - if (x >= e->x && (e->width == 0 || x - e->x < e->width) && y >= e->y && - (e->height == 0 || y - e->y < e->height)) - return i; - } - return -1; -} -static double render_column_speed(int x, int y) { - int i = render_effect_index(x, y); - return i < 0 ? 1 : 1 + S.effect_energy[i] * S.effects.effects[i].speed; -} -static rgb_t render_column_color(int x, int y, rgb_t base, float *brightness) { - *brightness = 1; - int i = render_effect_index(x, y); - if (i < 0) - return base; - const dr_effect_t *e = &S.effects.effects[i]; - float t = S.effect_mix[i]; - base.r += (e->color[0] - base.r) * t; - base.g += (e->color[1] - base.g) * t; - base.b += (e->color[2] - base.b) * t; - *brightness += S.effect_peak[i] * e->brightness; - if (t > .001 && e->pulse > 0) - *brightness *= 1 - e->pulse * t * (1 + sin(S.effect_tick * 3.141592653589793)) * .5; - return base; -} -static void render_effect_tick(void) { - double now = render_monotime(), - dt = S.effect_tick > 0 ? fmin(.1, now - S.effect_tick) : 1.0 / TARGET_FPS; - S.effect_tick = now; - bool remains = false; - for (unsigned i = 0; i < S.effects.count; i++) { - dr_effect_t *e = &S.effects.effects[i]; - float target = now < e->until ? 1 : 0; - S.effect_mix[i] += (target - S.effect_mix[i]) * (1 - exp(-dt / .12)); - bool fresh = now - e->audio_time < .3; - float energy = fresh ? e->energy : 0, peak = fresh ? e->peak : 0; - double ms = energy > S.effect_energy[i] ? S.audio_attack : S.audio_release; - if (ms <= 0) - ms = 100; - float blend = 1 - exp(-dt * 1000 / ms); - S.effect_energy[i] += (energy - S.effect_energy[i]) * blend; - S.effect_peak[i] += (peak - S.effect_peak[i]) * blend; - remains |= target > 0 || energy > 0 || peak > 0 || S.effect_mix[i] > .0001 || - S.effect_energy[i] > .0001 || S.effect_peak[i] > .0001; - } - if (!remains) - S.effects.count = 0; - if (now < S.palette_until && !S.adaptive_color && S.color_mode == COLOR_FIXED) { - float t = 1 - exp(-dt / .12); - for (int l = 0; l < S.num_depth_layers; l++) - for (int c = 0; c < S.layers[l].cols; c++) { - rgb_t *color = &S.layers[l].columns[c].color; - color->r += (S.base_color.r - color->r) * t; - color->g += (S.base_color.g - color->g) * t; - color->b += (S.base_color.b - color->b) * t; - } - } -} -static void render_apply_effects(const dr_effect_packet *packet) { - dr_effect_packet next = *packet; - float mix[MAX_OUTPUTS] = {0}, energy[MAX_OUTPUTS] = {0}, peak[MAX_OUTPUTS] = {0}; - if (!next.count && S.effects.count) { - next = S.effects; - for (unsigned i = 0; i < next.count; i++) { - next.effects[i].until = 0; - next.effects[i].audio_time = 0; - } - } - for (unsigned i = 0; i < next.count; i++) - for (unsigned j = 0; j < S.effects.count; j++) - if (!strcmp(next.effects[i].output, S.effects.effects[j].output)) { - mix[i] = S.effect_mix[j]; - energy[i] = S.effect_energy[j]; - peak[i] = S.effect_peak[j]; - if (next.effects[i].until <= render_monotime()) - memcpy(next.effects[i].color, S.effects.effects[j].color, - sizeof(next.effects[i].color)); - break; - } - S.effects = next; - memcpy(S.effect_mix, mix, sizeof(mix)); - memcpy(S.effect_energy, energy, sizeof(energy)); - memcpy(S.effect_peak, peak, sizeof(peak)); -} -static void render_layers_free(depth_layer_t *layers) { - for (int i = 0; i < MAX_DEPTH_LAYERS; i++) { - free(layers[i].columns); - free(layers[i].cells); - } -} -static bool render_apply_config(const dr_config_packet *packet, bool locking) { - if (!dr_config_valid(packet)) - return false; - if (packet->revision == S.applied_revision) - return true; - S.attempted_revision = packet->revision; - const dr_config_t *c = &packet->config; - __typeof__(S) old = S; - bool resources = - S.font_size != c->matrix_font_size || S.num_depth_layers != c->matrix_depth_layers; - S.font_size = c->matrix_font_size; - S.num_depth_layers = c->matrix_depth_layers; - S.min_speed = c->matrix_min_speed; - S.max_speed = c->matrix_max_speed; - S.avg_speed = c->matrix_avg_speed; - S.min_trail = c->matrix_min_trail; - S.max_trail = c->matrix_max_trail; - S.min_density = c->matrix_min_density; - S.max_density = c->matrix_max_density; - S.layer_near_density = c->matrix_layer_near_density; - S.layer_far_density = c->matrix_layer_far_density; - S.head_hold_frames = c->matrix_head_hold_frames; - S.head_mutation_chance = c->matrix_head_mutation_chance; - S.head_white_min = c->matrix_head_white_min; - S.head_white_max = c->matrix_head_white_max; - S.transparent_timeout = c->matrix_transparent_timeout; - S.black_rain_timeout = c->matrix_black_rain_timeout; - if (old.fade_frames > 0) - S.phase_frame = (int)((double)old.phase_frame * c->matrix_fade_frames / old.fade_frames); - S.fade_frames = c->matrix_fade_frames; - S.audio_attack = c->audio_attack; - S.audio_release = c->audio_release; - if (strcmp(S.live_color, c->matrix_color)) { - S.palette_until = render_monotime() + 1; - snprintf(S.live_color, sizeof(S.live_color), "%s", c->matrix_color); - S.adaptive_color = is_adaptive_color(c->matrix_color); - S.color_mode = !strcmp(c->matrix_color, "random_trail_named") ? COLOR_RANDOM - : !strcmp(c->matrix_color, "random_trail_hex") ? COLOR_RANDOM_HEX - : COLOR_FIXED; - if (S.adaptive_color) { - render_load_adaptive_snapshot(); - S.base_color = - locking || S.background_black ? S.adaptive_black_color : S.adaptive_desktop_color; - } else - S.base_color = render_lookup_color(c->matrix_color); - } - if (resources) { - memset(S.layers, 0, sizeof(S.layers)); - memset(&S.atlas, 0, sizeof(S.atlas)); - int font = (int)(S.font_size * LAYER_NEAR_FONT_SCALE + .5); - if (locking && font < 18) - font = 18; - while (glGetError() != GL_NO_ERROR) { - } - if (!render_build_atlas(font) || glGetError() != GL_NO_ERROR || - !render_init_matrix(S.surface_width, S.surface_height)) { - render_layers_free(S.layers); - if (S.atlas.texture) - glDeleteTextures(1, &S.atlas.texture); - S = old; - S.live_error = 1; - return false; - } - /* Map existing rain into the new grid; phase clocks and head positions survive. */ - for (int l = 0; l < S.num_depth_layers && l < old.num_depth_layers; l++) { - depth_layer_t *n = &S.layers[l], *o = &old.layers[l]; - for (int x = 0; x < n->cols; x++) { - int ox = (int)((double)x * n->cell_w / o->cell_w); - if (ox >= o->cols) - ox = o->cols - 1; - double scale = (double)o->cell_h / n->cell_h; - n->columns[x] = o->columns[ox]; - n->columns[x].head_pos *= scale; - n->columns[x].last_head_row = n->columns[x].head_pos; - for (int y = 0; y < n->rows; y++) { - int oy = (int)(y / scale); - if (oy >= o->rows) - oy = o->rows - 1; - n->cells[y * n->cols + x] = o->cells[oy * o->cols + ox]; - } - } - } - render_layers_free(old.layers); - if (old.atlas.texture) - glDeleteTextures(1, &old.atlas.texture); - } - for (int l = 0; l < S.num_depth_layers; l++) { - depth_layer_t *layer = &S.layers[l]; - render_get_layer_properties(l, S.num_depth_layers, &layer->font_scale, &layer->opacity, - &layer->speed_scale, &layer->density); - double ratio = old.avg_speed > 0 ? S.avg_speed / old.avg_speed : 1; - for (int x = 0; x < layer->cols; x++) { - render_column_t *col = &layer->columns[x]; - col->speed = fmin(S.max_speed * layer->speed_scale, - fmax(S.min_speed * layer->speed_scale, col->speed * ratio)); - col->trail_len = fmin(S.max_trail, fmax(S.min_trail, col->trail_len)); - } - } - S.applied_revision = packet->revision; - S.live_error = 0; - return true; -} -static void render_live_receive(void) { - if (S.report_fd < 0) - return; - union { - dr_config_packet config; - dr_effect_packet effects; - } packet; - for (int i = 0; i < 8; i++) { - ssize_t n = recv(S.report_fd, &packet, sizeof(packet), MSG_DONTWAIT | MSG_TRUNC); - if (n <= 0) { - if (n == 0) { - close(S.report_fd); - S.report_fd = -1; - if (S.ambient) S.stop = 1; - } - break; - } - if (n == sizeof(dr_config_packet) && dr_config_valid(&packet.config)) - render_apply_config(&packet.config, false); - else if (n == sizeof(dr_effect_packet) && dr_effect_valid(&packet.effects)) - render_apply_effects(&packet.effects); - } - render_report(); -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-live.h b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-live.h deleted file mode 100644 index b9ee8f53db6f..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-live.h +++ /dev/null @@ -1,76 +0,0 @@ -/* Private process protocol: fixed-size, bounded snapshots; never public JSON. */ -#ifndef DIGITALRAIN_LIVE_H -#define DIGITALRAIN_LIVE_H -#define DR_LIVE_MAGIC UINT32_C(0x44524c32) -enum { DR_CONFIG = 1, DR_EFFECTS = 2, DR_REPORT = 3 }; -typedef struct { - char output[64]; - int x, y, width, height; - double until, audio_time; - float color[3], pulse, energy, peak, speed, brightness; -} dr_effect_t; -typedef struct { - uint32_t magic, kind, revision, count; - dr_effect_t effects[MAX_OUTPUTS]; -} dr_effect_packet; -typedef struct { - uint32_t magic, kind, revision; - dr_config_t config; -} dr_config_packet; -typedef struct { - uint32_t magic, kind, revision; - int phase, power, error, font_size, depth_layers; -} dr_report_packet; -static bool dr_effect_valid(const dr_effect_packet *p) { - if (p->magic != DR_LIVE_MAGIC || p->kind != DR_EFFECTS || p->count > MAX_OUTPUTS) - return false; - for (unsigned i = 0; i < p->count; i++) { - const dr_effect_t *e = &p->effects[i]; - if (!memchr(e->output, 0, 64) || e->x < 0 || e->y < 0 || e->width < 0 || e->height < 0 || - !isfinite(e->until) || !isfinite(e->audio_time) || !isfinite(e->pulse) || - e->pulse < 0 || e->pulse > .25 || !isfinite(e->energy) || e->energy < 0 || - e->energy > 1 || !isfinite(e->peak) || e->peak < 0 || e->peak > 1 || - !isfinite(e->speed) || e->speed < 0 || e->speed > 2 || !isfinite(e->brightness) || - e->brightness < 0 || e->brightness > 1) - return false; - for (int j = 0; j < 3; j++) - if (!isfinite(e->color[j]) || e->color[j] < 0 || e->color[j] > 1) - return false; - } - return true; -} -static bool dr_config_valid(const dr_config_packet *p) { - if (p->magic != DR_LIVE_MAGIC || p->kind != DR_CONFIG) - return false; - const dr_config_t *c = &p->config; - for (size_t i = 0; i < SETTING_COUNT; i++) { - const struct api_setting *s = &api_settings[i]; - const char *v = (const char *)c + s->offset; - if (s->type >= 2) { - if (!memchr(v, 0, s->width) || (s->type == 2 && !api_color_valid(v))) - return false; - } else { - double n = s->type == 1 ? *(const double *)v : *(const int *)v; - if (!isfinite(n) || n < s->lo || n > s->hi) - return false; - } - } - if (cf_relationships(c) || c->output_count < 0 || c->output_count > 64 || - c->audio_output_count < 0 || c->audio_output_count > 8) - return false; - for (int i = 0; i < c->output_count; i++) - if (!memchr(c->outputs[i].name, 0, 64) || !memchr(c->outputs[i].color, 0, 32) || - (c->outputs[i].color[0] && !api_color_valid(c->outputs[i].color)) || - c->outputs[i].timeout < -1 || c->outputs[i].timeout > 86400 || - c->outputs[i].power_off < -1 || c->outputs[i].power_off > 1) - return false; - for (int i = 0; i < c->audio_output_count; i++) - if (!memchr(c->audio_outputs[i], 0, 64)) - return false; - for (int i = 0; i < 4; i++) - if (!memchr(c->themes[i].color, 0, 32) || !api_color_valid(c->themes[i].color) || - !isfinite(c->themes[i].pulse) || c->themes[i].pulse < 0 || c->themes[i].pulse > .25) - return false; - return true; -} -#endif diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-lock.c b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-lock.c deleted file mode 100644 index 3f3903d0f4a5..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-lock.c +++ /dev/null @@ -1,1146 +0,0 @@ -/* Native ext-session-lock client. The compositor, not a saver overlay, owns - * protection. Reuse the existing rain engine without linking PAM into it. */ -#define main digitalrain_unused_main -#include "digitalrain.c" -#undef main -#include "digitalrain-control.h" -#include "ext-session-lock-v1-client-protocol.h" -#include <xkbcommon/xkbcommon.h> -#include <xkbcommon/xkbcommon-compose.h> -#include <locale.h> -#include <sys/resource.h> -#include "../hyperspace/hyperspace-lock.h" - -#define LOCK_OUTPUTS 64 -struct lock_output { - uint32_t global; - struct wl_output *output; - struct ext_session_lock_surface_v1 *lock_surface; - __typeof__(S) render; - bool ready, dirty; - void *space, *composite; - char name[128]; -}; -static struct { - struct wl_display *display; - struct wl_compositor *compositor; - struct ext_session_lock_manager_v1 *manager; - struct ext_session_lock_v1 *lock; - struct wl_seat *seat; - struct wl_keyboard *keyboard; - struct wl_pointer *pointer; - struct xkb_context *xkb; - struct xkb_keymap *keymap; - struct xkb_state *keys; - struct xkb_compose_table *compose_table; - struct xkb_compose_state *compose; - struct lock_output outputs[LOCK_OUTPUTS]; - saver_target_t targets[MAX_OUTPUTS]; - int target_count; - int power_fds[MAX_OUTPUTS]; - bool power_tried, topology_dirty; - int listener, clients[DR_CLIENTS]; - bool watching[DR_CLIENTS]; - int auth_fd; - pid_t auth_pid; - bool confirmed, finished, failed, dirty, prompt, black, caps; - bool auth_byte; - double started, last_input, retry_after, next_frame, auth_started; - unsigned generation; - dr_config_t power_config; - dr_config_packet appearance; - dr_effect_packet effects; - char password[1024]; - size_t password_len; - char message[80]; - struct json_object *theme, *space_settings, *matrix_settings, *scenes; - unsigned theme_revision; - bool ambient_theme; - char background[24]; - double settings_due, fade_updated, opacity, ui_opacity; - time_t clock_minute; -} L; -static double bounded(double v, double lo, double hi); -static void refresh_appearance(void); -static bool theme_bool(const char *key); -static double theme_number(const char *key, double fallback, double lo, double hi); -static struct json_object *theme_value(const char *key); -static bool controls_on(const char *name, const char *desktop); -static volatile sig_atomic_t lock_stop; -static void stop_lock(int sig) { - (void)sig; - lock_stop = 1; -} -static void lock_changed(void) { - L.dirty = true; - ++L.generation; -} -static void restore_monitors(void) { - for (int i = 0; i < MAX_OUTPUTS; i++) - if (L.power_fds[i] >= 0) { - render_desktop_request(L.power_fds[i], "digitalrain/power_on", "{}"); - close(L.power_fds[i]); - L.power_fds[i] = -1; - } - L.power_tried = false; -} -static void power_monitors(void) { - if (L.power_tried || !L.confirmed || !L.black || L.prompt) - return; - L.power_tried = true; - for (int i = 0; i < G.output_count; i++) { - int power_off = L.power_config.matrix_power_off; - for (int j = 0; j < L.power_config.output_count; j++) - if (!strcmp(G.outputs[i].name, L.power_config.outputs[j].name) && L.power_config.outputs[j].power_off >= 0) power_off = L.power_config.outputs[j].power_off; - if (!power_off) - continue; - int fd = mgr_ipc_connect(); - if (fd < 0) - continue; - struct json_object *data = json_object_new_object(); - json_object_object_add(data, "output", json_object_new_string(G.outputs[i].name)); - bool ok = - render_desktop_request(fd, "digitalrain/power_off", - json_object_to_json_string_ext(data, JSON_C_TO_STRING_PLAIN)); - json_object_put(data); - if (ok) - L.power_fds[i] = fd; - else - close(fd); - } -} - -static struct json_object *lock_status(void) { - struct json_object *o = json_object_new_object(); - json_object_object_add(o, "state", json_object_new_string(L.confirmed ? "locked" : "locking")); - json_object_object_add(o, "pid", json_object_new_int(getpid())); - json_object_object_add(o, "phase", json_object_new_string(L.black ? "black" : "rain")); - json_object_object_add(o, "background", json_object_new_string(L.ambient_theme ? L.background : "digitalrain")); - json_object_object_add(o, "background_opacity", json_object_new_double(L.ambient_theme ? L.opacity : !L.black)); - json_object_object_add(o, "clock_visible", json_object_new_boolean(L.ui_opacity > 0 && theme_bool("show_clock"))); - json_object_object_add(o, "controls_opacity", json_object_new_double(L.ui_opacity)); - json_object_object_add(o, "prompt_visible", json_object_new_boolean(L.ui_opacity > 0 && (L.prompt || theme_bool("ui_on_interaction")))); - struct json_object *controls = json_object_new_array(); - for (int i=0;i<L.target_count;i++) - if (controls_on(L.targets[i].name,L.targets[i].desktop)) - json_object_array_add(controls,json_object_new_string(L.targets[i].name)); - json_object_object_add(o, "controls_outputs", controls); - json_object_object_add(o, "theme_revision", json_object_new_int(L.theme_revision)); - json_object_object_add(o, "desired_revision", json_object_new_int(L.appearance.revision)); - struct json_object *revisions = json_object_new_object(); - for (int i = 0; i < LOCK_OUTPUTS; i++) if (L.outputs[i].ready) { - struct json_object *v = json_object_new_object(); - json_object_object_add(v, "applied_revision", json_object_new_int(L.ambient_theme ? L.appearance.revision : L.outputs[i].render.applied_revision)); - json_object_object_add(v, "appearance_error", json_object_new_boolean(L.outputs[i].render.live_error)); - json_object_object_add(revisions, L.outputs[i].name, v); - } - json_object_object_add(o, "outputs", revisions); - json_object_object_add(o, "generation", json_object_new_int64(L.generation)); - return o; -} -static void input_activity(void) { - if (L.power_tried) - restore_monitors(); - L.last_input = render_monotime(); - L.fade_updated = L.last_input; - L.next_frame = L.last_input; - if (L.ambient_theme) { L.started=L.last_input;L.black=false; } - if (!L.prompt) { - L.prompt = true; - lock_changed(); - } - L.dirty = true; -} -static void erase_password(void) { - explicit_bzero(L.password, sizeof(L.password)); - L.password_len = 0; - if (L.compose) - xkb_compose_state_reset(L.compose); -} -static void authenticate(void) { - if (!L.confirmed || L.auth_pid || !L.password_len || render_monotime() < L.retry_after) - return; - int input[2], output[2]; - char helper[PATH_MAX]; - if (dr_sibling(helper, sizeof(helper), "digitalrain-auth") || pipe2(input, O_CLOEXEC)) - goto fail; - if (pipe2(output, O_CLOEXEC | O_NONBLOCK)) { - close(input[0]); - close(input[1]); - goto fail; - } - posix_spawn_file_actions_t actions; - posix_spawn_file_actions_init(&actions); - posix_spawn_file_actions_adddup2(&actions, input[0], STDIN_FILENO); - posix_spawn_file_actions_adddup2(&actions, output[1], STDOUT_FILENO); - posix_spawn_file_actions_addclosefrom_np(&actions, 3); - char *argv[] = {helper, NULL}; - int rc = posix_spawn(&L.auth_pid, helper, &actions, NULL, argv, environ); - posix_spawn_file_actions_destroy(&actions); - close(input[0]); - close(output[1]); - if (rc) { - close(input[1]); - close(output[0]); - L.auth_pid = 0; - goto fail; - } - /* Less than PIPE_BUF: one bounded write into a fresh pipe. */ - ssize_t n = write(input[1], L.password, L.password_len); - close(input[1]); - if (n != (ssize_t)L.password_len) - kill(L.auth_pid, SIGKILL); - erase_password(); - L.auth_fd = output[0]; - L.auth_started = render_monotime(); - L.auth_byte = false; - snprintf(L.message, sizeof(L.message), "Checking password..."); - lock_changed(); - return; -fail: - erase_password(); - snprintf(L.message, sizeof(L.message), "Authentication unavailable"); - L.retry_after = render_monotime() + 3; - lock_changed(); -} -static bool finish_auth(void) { - if (!L.auth_pid) - return false; - char buf[2]; - ssize_t n = read(L.auth_fd, buf, sizeof(buf)); - if (n == 1 && buf[0] == 'Y') - L.auth_byte = true; - else if (n > 0) { - L.auth_byte = false; - kill(L.auth_pid, SIGKILL); - } - if (render_monotime() - L.auth_started > 60) - kill(L.auth_pid, SIGKILL); - int status; - if (waitpid(L.auth_pid, &status, WNOHANG) != L.auth_pid) - return false; - /* The worker may write and exit between the nonblocking read and waitpid. */ - if (!L.auth_byte && n < 0 && errno == EAGAIN) { - n = read(L.auth_fd, buf, sizeof(buf)); - L.auth_byte = n == 1 && buf[0] == 'Y'; - } - close(L.auth_fd); - L.auth_fd = -1; - L.auth_pid = 0; - if (L.auth_byte && WIFEXITED(status) && WEXITSTATUS(status) == 0 && L.confirmed) { - ext_session_lock_v1_unlock_and_destroy(L.lock); - L.lock = NULL; - if (wl_display_roundtrip(L.display) < 0) { - L.failed = true; - return false; - } - struct json_object *o = json_object_new_object(); - json_object_object_add(o, "state", json_object_new_string("unlocked")); - for (int i = 0; i < DR_CLIENTS; ++i) - if (L.clients[i] >= 0 && L.watching[i]) - dr_send(L.clients[i], o); - json_object_put(o); - return true; - } - erase_password(); - snprintf(L.message, sizeof(L.message), "Authentication failed - try again"); - L.retry_after = render_monotime() + 2; - lock_changed(); - return false; -} - -static void keymap(void *data, struct wl_keyboard *kb, uint32_t format, int fd, uint32_t size) { - (void)data; - (void)kb; - if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 || !size || size > 1024 * 1024) { - close(fd); - return; - } - char *map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); - close(fd); - if (map == MAP_FAILED) - return; - if (!memchr(map, 0, size)) { - munmap(map, size); - return; - } - struct xkb_keymap *km = xkb_keymap_new_from_string(L.xkb, map, XKB_KEYMAP_FORMAT_TEXT_V1, 0); - munmap(map, size); - if (!km) - return; - struct xkb_state *ks = xkb_state_new(km); - if (!ks) { - xkb_keymap_unref(km); - return; - } - if (L.keys) - xkb_state_unref(L.keys); - if (L.keymap) - xkb_keymap_unref(L.keymap); - L.keys = ks; - L.keymap = km; -} -static void key_enter(void *d, struct wl_keyboard *k, uint32_t s, struct wl_surface *sf, - struct wl_array *keys) { - (void)d; - (void)k; - (void)s; - (void)sf; - (void)keys; -} -static void key_leave(void *d, struct wl_keyboard *k, uint32_t s, struct wl_surface *sf) { - (void)d; - (void)k; - (void)s; - (void)sf; - erase_password(); - L.dirty = true; -} -static void key(void *d, struct wl_keyboard *k, uint32_t serial, uint32_t time, uint32_t code, - uint32_t state) { - (void)d; - (void)k; - (void)serial; - (void)time; - if (state != WL_KEYBOARD_KEY_STATE_PRESSED || !L.keys) - return; - input_activity(); - if (L.auth_pid || render_monotime() < L.retry_after) - return; - xkb_keysym_t sym = xkb_state_key_get_one_sym(L.keys, code + 8); - if (sym == XKB_KEY_Return || sym == XKB_KEY_KP_Enter) { - authenticate(); - return; - } - if (sym == XKB_KEY_Escape) { - erase_password(); - L.prompt = false; - L.message[0] = 0; - lock_changed(); - return; - } - if (sym == XKB_KEY_BackSpace) { - if (L.password_len) { - size_t end = L.password_len; - --L.password_len; - while (L.password_len && ((unsigned char)L.password[L.password_len] & 0xc0) == 0x80) - --L.password_len; - explicit_bzero(L.password + L.password_len, end - L.password_len); - } - return; - } - if (xkb_state_mod_name_is_active(L.keys, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_EFFECTIVE) > 0) { - if (sym == XKB_KEY_u || sym == XKB_KEY_U) - erase_password(); - return; - } - char text[64] = {0}; - int len = 0; - if (L.compose && xkb_compose_state_feed(L.compose, sym) == XKB_COMPOSE_FEED_ACCEPTED) { - switch (xkb_compose_state_get_status(L.compose)) { - case XKB_COMPOSE_COMPOSING: - return; - case XKB_COMPOSE_CANCELLED: - xkb_compose_state_reset(L.compose); - return; - case XKB_COMPOSE_COMPOSED: - len = xkb_compose_state_get_utf8(L.compose, text, sizeof(text)); - xkb_compose_state_reset(L.compose); - break; - default: - len = xkb_state_key_get_utf8(L.keys, code + 8, text, sizeof(text)); - } - } else - len = xkb_state_key_get_utf8(L.keys, code + 8, text, sizeof(text)); - if (len > 0 && len < (int)sizeof(text) && (unsigned char)text[0] >= 32 && - (unsigned char)text[0] != 127 && L.password_len + len < sizeof(L.password)) { - memcpy(L.password + L.password_len, text, len); - L.password_len += len; - L.message[0] = 0; - } - explicit_bzero(text, sizeof(text)); -} -static void modifiers(void *d, struct wl_keyboard *k, uint32_t s, uint32_t depressed, - uint32_t latched, uint32_t locked, uint32_t group) { - (void)d; - (void)k; - (void)s; - if (!L.keys) - return; - xkb_state_update_mask(L.keys, depressed, latched, locked, 0, 0, group); - L.caps = xkb_state_mod_name_is_active(L.keys, XKB_MOD_NAME_CAPS, XKB_STATE_MODS_EFFECTIVE) > 0; - L.dirty = true; -} -static void repeat(void *d, struct wl_keyboard *k, int32_t rate, int32_t delay) { - (void)d; - (void)k; - (void)rate; - (void)delay; -} -static const struct wl_keyboard_listener keyboard_listener = {keymap, key_enter, key_leave, - key, modifiers, repeat}; -static void pointer_enter(void *d, struct wl_pointer *p, uint32_t s, struct wl_surface *sf, - wl_fixed_t x, wl_fixed_t y) { - (void)d; - (void)sf; - (void)x; - (void)y; - wl_pointer_set_cursor(p, s, NULL, 0, 0); -} -static void pointer_leave(void *d, struct wl_pointer *p, uint32_t s, struct wl_surface *sf) { - (void)d; - (void)p; - (void)s; - (void)sf; -} -static void pointer_motion(void *d, struct wl_pointer *p, uint32_t t, wl_fixed_t x, wl_fixed_t y) { - (void)d; - (void)p; - (void)t; - (void)x; - (void)y; - input_activity(); -} -static void pointer_button(void *d, struct wl_pointer *p, uint32_t s, uint32_t t, uint32_t b, - uint32_t st) { - (void)d; - (void)p; - (void)s; - (void)t; - (void)b; - (void)st; - input_activity(); -} -static void pointer_axis(void *d, struct wl_pointer *p, uint32_t t, uint32_t a, wl_fixed_t v) { - (void)d; - (void)p; - (void)t; - (void)a; - (void)v; - input_activity(); -} -static const struct wl_pointer_listener pointer_listener = {.enter = pointer_enter, - .leave = pointer_leave, - .motion = pointer_motion, - .button = pointer_button, - .axis = pointer_axis}; -static void seat_caps(void *d, struct wl_seat *seat, uint32_t caps) { - (void)d; - if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !L.keyboard) { - L.keyboard = wl_seat_get_keyboard(seat); - wl_keyboard_add_listener(L.keyboard, &keyboard_listener, NULL); - } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && L.keyboard) { - wl_keyboard_release(L.keyboard); - L.keyboard = NULL; - erase_password(); - } - if ((caps & WL_SEAT_CAPABILITY_POINTER) && !L.pointer) { - L.pointer = wl_seat_get_pointer(seat); - wl_pointer_add_listener(L.pointer, &pointer_listener, NULL); - } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && L.pointer) { - wl_pointer_release(L.pointer); - L.pointer = NULL; - } -} -static void seat_name(void *d, struct wl_seat *s, const char *n) { - (void)d; - (void)s; - (void)n; -} -static const struct wl_seat_listener seat_listener = {seat_caps, seat_name}; -static void output_geometry(void *d, struct wl_output *o, int32_t x, int32_t y, int32_t pw, - int32_t ph, int32_t sp, const char *m, const char *model, int32_t tr) { - (void)d; - (void)o; - (void)x; - (void)y; - (void)pw; - (void)ph; - (void)sp; - (void)m; - (void)model; - (void)tr; -} -static void output_mode(void *d, struct wl_output *o, uint32_t f, int32_t w, int32_t h, int32_t r) { - (void)d; - (void)o; - (void)f; - (void)w; - (void)h; - (void)r; -} -static void output_done(void *d, struct wl_output *o) { - (void)d; - (void)o; -} -static void output_scale(void *d, struct wl_output *o, int32_t s) { - (void)d; - (void)o; - (void)s; -} -static void output_name(void *d, struct wl_output *o, const char *name) { - (void)o; - snprintf(((struct lock_output *)d)->name, 128, "%s", name); -} -static void output_description(void *d, struct wl_output *o, const char *s) { - (void)d; - (void)o; - (void)s; -} -static const struct wl_output_listener output_listener = { - output_geometry, output_mode, output_done, output_scale, output_name, output_description}; - -static void configure(void *data, struct ext_session_lock_surface_v1 *surface, uint32_t serial, - uint32_t width, uint32_t height) { - struct lock_output *o = data; - if (!width || !height || width > 32768 || height > 32768) { - L.failed = true; - return; - } - ext_session_lock_surface_v1_ack_configure(surface, serial); - o->render.surface_width = width; - o->render.surface_height = height; - o->render.configured = true; - o->dirty = true; - /* Finish output reconfiguration while its display can deliver frames. */ - restore_monitors(); -} -static const struct ext_session_lock_surface_v1_listener surface_listener = {configure}; -static void create_surface(struct lock_output *o) { - o->render.surface = wl_compositor_create_surface(L.compositor); - o->lock_surface = ext_session_lock_v1_get_lock_surface(L.lock, o->render.surface, o->output); - ext_session_lock_surface_v1_add_listener(o->lock_surface, &surface_listener, o); -} -static void free_output(struct lock_output *o) { - if (o->ready) { - S = o->render; - eglMakeCurrent(S.egl_display, S.egl_surface, S.egl_surface, S.egl_context); - ambient_space_destroy(o->space); - ambient_lock_composite_destroy(o->composite); - for (int i = 0; i < S.num_depth_layers; i++) { - free(S.layers[i].columns); - free(S.layers[i].cells); - } - free(S.vertex_buf); - glDeleteTextures(1, &S.atlas.texture); - glDeleteBuffers(1, &S.vbo); - glDeleteProgram(S.shader_program); - FT_Done_Face(S.ft_face); - FT_Done_FreeType(S.ft_library); - eglMakeCurrent(S.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - eglDestroySurface(S.egl_display, S.egl_surface); - eglDestroyContext(S.egl_display, S.egl_context); - wl_egl_window_destroy(S.egl_window); - /* EGLDisplay is shared: never terminate it while another output lives. */ - } - if (o->lock_surface) - ext_session_lock_surface_v1_destroy(o->lock_surface); - if (o->render.surface) - wl_surface_destroy(o->render.surface); - if (o->output) - wl_output_destroy(o->output); - memset(o, 0, sizeof(*o)); -} -static void global(void *d, struct wl_registry *reg, uint32_t id, const char *iface, - uint32_t version) { - (void)d; - if (!strcmp(iface, wl_compositor_interface.name)) - L.compositor = - wl_registry_bind(reg, id, &wl_compositor_interface, version < 4 ? version : 4); - else if (!strcmp(iface, ext_session_lock_manager_v1_interface.name)) - L.manager = wl_registry_bind(reg, id, &ext_session_lock_manager_v1_interface, 1); - else if (!strcmp(iface, wl_seat_interface.name) && !L.seat) { - L.seat = wl_registry_bind(reg, id, &wl_seat_interface, version < 4 ? version : 4); - wl_seat_add_listener(L.seat, &seat_listener, NULL); - } else if (!strcmp(iface, wl_output_interface.name)) { - L.topology_dirty = true; - for (int i = 0; i < LOCK_OUTPUTS; i++) - if (!L.outputs[i].output) { - struct lock_output *o = &L.outputs[i]; - o->global = id; - o->output = - wl_registry_bind(reg, id, &wl_output_interface, version < 4 ? version : 4); - wl_output_add_listener(o->output, &output_listener, o); - if (L.lock) - create_surface(o); - return; - } - L.failed = true; - } -} -static void global_remove(void *d, struct wl_registry *reg, uint32_t id) { - (void)d; - (void)reg; - for (int i = 0; i < LOCK_OUTPUTS; i++) - if (L.outputs[i].global == id) - free_output(&L.outputs[i]); - L.topology_dirty = true; -} -static const struct wl_registry_listener registry_listener = {global, global_remove}; -static void locked(void *d, struct ext_session_lock_v1 *l) { - (void)d; - (void)l; - L.confirmed = true; - lock_changed(); -} -static void finished(void *d, struct ext_session_lock_v1 *l) { - (void)d; - L.finished = true; - if (!L.confirmed) - ext_session_lock_v1_destroy(l); - /* Never send unlock as a response to an unexpected finished event. */ - L.lock = NULL; -} -static const struct ext_session_lock_v1_listener lock_listener = {locked, finished}; -static double bounded(double v, double lo, double hi) { - return isfinite(v) ? fmin(hi, fmax(lo, v)) : lo; -} -static struct json_object *theme_value(const char *key) { - struct json_object *v=NULL; - if (L.theme) json_object_object_get_ex(L.theme,key,&v); - return v; -} -static double theme_number(const char *key,double fallback,double lo,double hi) { - struct json_object *v=theme_value(key); - return v ? bounded(json_object_get_double(v),lo,hi) : fallback; -} -static bool theme_bool(const char *key) { - return L.ambient_theme && json_object_get_boolean(theme_value(key)); -} -static bool controls_on(const char *name,const char *desktop) { - const char *selected=json_object_get_string(theme_value("output")); - if (!selected || !*selected || !strcmp(selected,"all")) return true; - for (int i=0;i<L.target_count;i++) - if (!strcmp(selected,L.targets[i].name) || !strcmp(selected,L.targets[i].desktop)) - return !strcmp(selected,name) || !strcmp(selected,desktop); - // Retain the saved choice, but never leave a disconnected monitor as the - // only place where a user can see authentication feedback. - if (L.target_count) return !strcmp(name,L.targets[0].name); - for (int i=0;i<LOCK_OUTPUTS;i++) - if (L.outputs[i].output && L.outputs[i].render.configured) - return !strcmp(desktop,L.outputs[i].name); - return false; -} -static void refresh_appearance(void) { - L.settings_due=render_monotime()+1; - struct json_object *status=mgr_ipc_request("ambient/status","{}"),*theme=NULL,*space=NULL,*matrix=NULL; - if (!status || !json_object_object_get_ex(status,"lock_screen",&theme) || - !json_object_object_get_ex(status,"hyperspace",&space) || - !json_object_object_get_ex(status,"matrix",&matrix)) { - if (status) json_object_put(status); - return; // The independent locker retains its last valid appearance. - } - const char *selected=dr_string(theme,"background"); - if (!selected) { json_object_put(status);return; } - if (!strcmp(selected,"inherit")) selected=dr_string(status,"backend"); - if (!selected || (strcmp(selected,"hyperspace") && strcmp(selected,"nebula") && strcmp(selected,"matrix") && - strcmp(selected,"digitalrain") && strcmp(selected,"black"))) { - json_object_put(status);return; - } - if (!strcmp(selected,"nebula") && !json_object_object_get_ex(status,"nebula",&space)) { - json_object_put(status);return; - } - bool backend_changed=!L.ambient_theme || strcmp(L.background,selected); - if (backend_changed && L.scenes) { json_object_put(L.scenes);L.scenes=NULL; } - bool changed=backend_changed || - !json_object_equal(L.theme,theme) || !json_object_equal(L.space_settings,space) || - !json_object_equal(L.matrix_settings,matrix); - if (changed) { - json_object_get(theme);json_object_get(space);json_object_get(matrix); - if (L.theme) json_object_put(L.theme); - if (L.space_settings) json_object_put(L.space_settings); - if (L.matrix_settings) json_object_put(L.matrix_settings); - L.theme=theme;L.space_settings=space;L.matrix_settings=matrix; - snprintf(L.background,sizeof(L.background),"%s",selected); - L.ambient_theme=true;++L.theme_revision;lock_changed(); - } - if (!L.scenes) { - struct json_object *scenes=NULL; - if (json_object_object_get_ex(status,!strcmp(selected,"nebula") ? "nebula_scenes" : "scenes",&scenes)) L.scenes=json_object_get(scenes); - } - json_object_put(status); -} -static dr_config_packet rain_appearance(void) { - dr_config_packet packet=L.appearance; - if (!L.ambient_theme) return packet; - packet.magic=DR_LIVE_MAGIC;packet.kind=DR_CONFIG; - packet.revision=0x80000000u+(L.theme_revision%0x7fffffffu); - packet.config=G.config; - json_object_object_foreach(L.matrix_settings,key,value) { - char full[128];snprintf(full,sizeof(full),"matrix.%s",key); - const struct api_setting *field=cf_setting(full); - if (field) cf_set(&packet.config,field,value,false); - } - // WCM permits changing related sliders in either order. - dr_config_t *c=&packet.config; - c->matrix_max_speed=fmax(c->matrix_min_speed,c->matrix_max_speed); - c->matrix_avg_speed=bounded(c->matrix_avg_speed,c->matrix_min_speed,c->matrix_max_speed); - c->matrix_max_trail=fmax(c->matrix_min_trail,c->matrix_max_trail); - c->matrix_max_density=fmax(c->matrix_min_density,c->matrix_max_density); - c->matrix_head_white_max=fmax(c->matrix_head_white_min,c->matrix_head_white_max); - struct json_object *monitor=NULL; - json_object_object_get_ex(L.matrix_settings,"MONITOR_COLOURS",&monitor); - if (!json_object_get_boolean(monitor) || theme_bool("colour_override")) - for (int i=0;i<c->output_count;i++) c->outputs[i].color[0]=0; - if (theme_bool("colour_override")) { - float r=0,g=0,b=0,a=1;const char *text=json_object_get_string(theme_value("colour")); - bool valid=text && sscanf(text,"%f %f %f %f",&r,&g,&b,&a)>=3; - if (text && text[0]=='#') { rgb_t colour=render_parse_hex(text);r=colour.r;g=colour.g;b=colour.b;valid=true; } - if (valid) - snprintf(c->matrix_color,sizeof(c->matrix_color),"#%02x%02x%02x", - (unsigned)(bounded(r,0,1)*255),(unsigned)(bounded(g,0,1)*255),(unsigned)(bounded(b,0,1)*255)); - } - return packet; -} -static struct json_object *space_appearance(struct lock_output *o,bool initial) { - struct json_object *settings=NULL; - json_object_deep_copy(L.space_settings,&settings,NULL); - if (!settings) return NULL; - bool nebula=!strcmp(L.background,"nebula"); - json_object_object_add(settings,"_backend",json_object_new_string(nebula ? "nebula" : "hyperspace")); - struct json_object *speed=NULL; - json_object_object_get_ex(settings,nebula ? "nebula_speed" : "hyperspace_speed",&speed); - json_object_object_add(settings,nebula ? "nebula_speed" : "hyperspace_speed",json_object_new_double( - bounded(json_object_get_double(speed)*theme_number("speed",1,0,3),0,5))); - json_object_object_add(settings,"hyperspace_surge",json_object_new_double(0)); - if (theme_bool("colour_override")) { - json_object_object_add(settings,nebula ? "nebula_color" : "hyperspace_color",json_object_get(theme_value("colour"))); - json_object_object_add(settings,nebula ? "nebula_cloud_colours" : "hyperspace_cloud_colours",json_object_new_double(0)); - } - struct json_object *scene=NULL; - (void)initial; - if (L.scenes && json_object_object_get_ex(L.scenes,o->name,&scene)) - json_object_object_add(settings,"_scene",json_object_get(scene)); - return settings; -} -static bool initialize_output(struct lock_output *o) { - S = o->render; - S.display = L.display; - S.compositor = L.compositor; - S.font_size = bounded(G.config.matrix_font_size, 6, 72); - S.num_depth_layers = bounded(G.config.matrix_depth_layers, 1, 8); - S.min_speed = bounded(G.config.matrix_min_speed, .001, 100); - S.max_speed = bounded(G.config.matrix_max_speed, S.min_speed, 100); - S.avg_speed = bounded(G.config.matrix_avg_speed, S.min_speed, S.max_speed); - S.min_trail = bounded(G.config.matrix_min_trail, 1, 1000); - S.max_trail = bounded(G.config.matrix_max_trail, S.min_trail, 1000); - S.min_density = bounded(G.config.matrix_min_density, 0, 1); - S.max_density = bounded(G.config.matrix_max_density, S.min_density, 1); - S.layer_near_density = bounded(G.config.matrix_layer_near_density, 0, 2); - S.layer_far_density = bounded(G.config.matrix_layer_far_density, 0, 2); - S.head_hold_frames = bounded(G.config.matrix_head_hold_frames, 0, 120); - S.head_mutation_chance = bounded(G.config.matrix_head_mutation_chance, 0, 1); - S.head_white_min = bounded(G.config.matrix_head_white_min, 0, 1); - S.head_white_max = bounded(G.config.matrix_head_white_max, S.head_white_min, 1); - S.base_color = (rgb_t){0, 1, 0}; - S.fade_opacity = 1; - if (G.config.matrix_color[0] == '#' || strlen(G.config.matrix_color) == 6) - S.base_color = render_parse_hex(G.config.matrix_color); - for (size_t i = 0; i < RENDER_NUM_COLORS; i++) - if (!strcmp(G.config.matrix_color, RENDER_COLOR_TABLE[i].name)) - S.base_color = RENDER_COLOR_TABLE[i].color; - if (!strcmp(G.config.matrix_color, "random_trail_named")) - S.color_mode = 1; - if (!strcmp(G.config.matrix_color, "random_trail_hex")) - S.color_mode = 2; - /* Adaptive lock rain uses its black-background palette, never desktop capture. */ - if (is_adaptive_color(G.config.matrix_color)) { - snprintf(S.target_name, sizeof(S.target_name), "%s", o->name); - for (int i = 0; i < L.target_count; i++) - if (!strcmp(L.targets[i].desktop, o->name)) { - snprintf(S.target_name, sizeof(S.target_name), "%s", L.targets[i].name); - break; - } - S.adaptive_black_color = S.base_color; - render_load_adaptive_snapshot(); - S.base_color = S.adaptive_black_color; - } - if (!render_init_egl() || !render_init_shaders()) - return false; - render_set_projection(S.surface_width, S.surface_height); - int font = fmax(18, S.font_size * LAYER_NEAR_FONT_SCALE); - if (!render_init_freetype(font) || !render_build_atlas(font)) - return false; - render_init_matrix(S.surface_width, S.surface_height); - o->render = S; - o->composite=ambient_lock_composite_create(); - o->ready = true; - o->dirty = false; - return true; -} -static void text_line(const char *text, int cx, int y, float scale) { - int advance = S.atlas.cell_w; - float x = cx - (strlen(text) * advance * scale) / 2; - for (const unsigned char *p = (const unsigned char *)text; *p; p++, x += advance * scale) { - if (*p == ' ') - continue; - const glyph_info_t *g = render_find_glyph(*p); - if (!g) - continue; - float left = x + g->bearing_x * scale, top = y - g->bearing_y * scale; - render_push_quad(left, top, left + g->width * scale, top + g->height * scale, g->u0, g->v0, - g->u1, g->v1, 1, 1, 1, 1); - } - render_flush_quads(); -} -static void prompt_at(int x, int y, int width, int height) { - int cx = x + width / 2, cy = y + height / 2; - if (theme_bool("show_clock")) cy += fmin(height*.15,80); - int boxw = fmin(width, 650), boxh = fmin(height*.5, 180); - glEnable(GL_SCISSOR_TEST); - glScissor(cx - boxw / 2, S.surface_height - (cy + boxh / 2), boxw, boxh); - glClearColor(0, 0, 0, .85); - glClear(GL_COLOR_BUFFER_BIT); - glDisable(GL_SCISSOR_TEST); - glEnable(GL_BLEND); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - glUseProgram(S.shader_program); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, S.atlas.texture); - glUniform1i(S.u_atlas, 0); - float scale = fmin(1.0, (float)width / 650); - text_line("Locked", cx, cy - 45*scale, scale); - char masked[64] = "Password: "; - size_t count = 0; - for (size_t i = 0; i < L.password_len; i++) - if (((unsigned char)L.password[i] & 0xc0) != 0x80) - count++; - memset(masked + 10, '*', count < 32 ? count : 32); - masked[10 + (count < 32 ? count : 32)] = 0; - text_line(masked, cx, cy, scale); - text_line(L.message[0] ? L.message : (L.caps ? "Caps Lock is on" : "Enter to unlock"), cx, - cy + 40*scale, scale); -} -static void clock_at(int x,int y,int width,int height) { - if (!theme_bool("show_clock")) return; - glEnable(GL_BLEND);glBlendFunc(GL_ONE,GL_ONE_MINUS_SRC_ALPHA);glUseProgram(S.shader_program); - glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D,S.atlas.texture);glUniform1i(S.u_atlas,0); - time_t now=time(NULL);struct tm local;localtime_r(&now,&local); - char clock[80];strftime(clock,sizeof(clock),"%H:%M",&local); - int cy=y+height/2-((L.prompt || theme_bool("ui_on_interaction")) ? fmin(height/3,180) : 20),cx=x+width/2; - float scale=fmin(1.8,(float)width/400); - text_line(clock,cx,cy,scale); - if (theme_bool("show_date")) { - strftime(clock,sizeof(clock),"%a, %b %d",&local);text_line(clock,cx,cy+40*scale,.65*scale); - } -} -static bool draw_output(struct lock_output *o, bool animate) { - if (!o->render.configured) - return true; - if (!o->ready && !initialize_output(o)) - return false; - S = o->render; - if (!eglMakeCurrent(S.egl_display, S.egl_surface, S.egl_surface, S.egl_context)) - return false; - dr_config_packet appearance=rain_appearance(); - if (appearance.revision && appearance.revision != S.attempted_revision) { - unsigned before = S.applied_revision; - render_apply_config(&appearance, true); - if (before != S.applied_revision || S.live_error) ++L.generation; - } - dr_effect_packet effects = {.magic=DR_LIVE_MAGIC, .kind=DR_EFFECTS}; - for (unsigned i = 0; i < L.effects.count; i++) for (int j = 0; j < L.target_count; j++) - if (!strcmp(L.effects.effects[i].output, L.targets[j].name) && !strcmp(L.targets[j].desktop, o->name)) { - if (L.effects.effects[i].until > render_monotime() || L.effects.effects[i].audio_time > 0) effects.effects[effects.count++] = L.effects.effects[i]; - break; - } - render_apply_effects(&effects); - if (o->dirty) { - wl_egl_window_resize(S.egl_window, S.surface_width, S.surface_height, 0, 0); - render_set_projection(S.surface_width, S.surface_height); - render_init_matrix(S.surface_width, S.surface_height); - } - glViewport(0, 0, S.surface_width, S.surface_height); - if (!ambient_lock_composite_begin(o->composite,S.surface_width,S.surface_height)) return false; - glDisable(GL_SCISSOR_TEST); - glClearColor(0, 0, 0, 1); - glClear(GL_COLOR_BUFFER_BIT); - if (animate && L.ambient_theme && (!strcmp(L.background,"hyperspace") || !strcmp(L.background,"nebula"))) { - struct json_object *settings=space_appearance(o,!o->space); - if (!settings) return false; - if (!o->space) o->space=ambient_space_create(settings); - bool ok=o->space && ambient_space_update(o->space,settings) && - ambient_space_draw(o->space,S.surface_width,S.surface_height,theme_number("speed",1,0,3)>0); - json_object_put(settings);if (!ok) return false; - } else if (animate && (!L.ambient_theme || strcmp(L.background,"black"))) { - render_simulation_speed=theme_number("speed",1,0,3); - if (render_simulation_speed>0) render_tick_matrix(); - render_frame(); - } - float opacity=L.ambient_theme ? L.opacity*theme_number("brightness",.65,0,2) : 1; - if (!ambient_lock_composite_end(o->composite,S.surface_width,S.surface_height,opacity)) return false; - if (L.ui_opacity > 0) { - if (!ambient_lock_composite_begin(o->composite,S.surface_width,S.surface_height)) return false; - glDisable(GL_SCISSOR_TEST);glClearColor(0,0,0,0);glClear(GL_COLOR_BUFFER_BIT); - bool known = false; - for (int i = 0; i < L.target_count; i++) - if (!strcmp(L.targets[i].desktop, o->name)) { - saver_target_t *t = &L.targets[i]; - if (t->x + t->width <= S.surface_width && t->y + t->height <= S.surface_height) { - known = true; - if (!controls_on(t->name,t->desktop)) continue; - clock_at(t->x,t->y,t->width,t->height); - if (L.prompt || theme_bool("ui_on_interaction")) prompt_at(t->x,t->y,t->width,t->height); - } - } - if (!known && controls_on(o->name,o->name)) { - clock_at(0,0,S.surface_width,S.surface_height); - if (L.prompt || theme_bool("ui_on_interaction")) prompt_at(0,0,S.surface_width,S.surface_height); - } - if (!ambient_lock_composite_overlay(o->composite,S.surface_width,S.surface_height,L.ui_opacity)) return false; - } - bool ok = eglSwapBuffers(S.egl_display, S.egl_surface); - o->render = S; - o->dirty = false; - return ok; -} - -int main(int argc, char **argv) { - if (argc > 1) { - if (!strcmp(argv[1], "--help")) { - puts("digitalrain-lock: native Wayland/PAM lock screen; no password arguments"); - return 0; - } - return 2; - } - struct rlimit core = {0, 0}; - setrlimit(RLIMIT_CORE, &core); - prctl(PR_SET_DUMPABLE, 0); - mlock(L.password, sizeof(L.password)); - signal(SIGPIPE, SIG_IGN); - signal(SIGTERM, stop_lock); - signal(SIGINT, stop_lock); - /* Saver signals cannot unlock or dismiss a locker. */ - signal(SIGUSR1, SIG_IGN); - signal(SIGUSR2, SIG_IGN); - for (int i = 0; i < DR_CLIENTS; i++) - L.clients[i] = -1; - for (int i = 0; i < MAX_OUTPUTS; i++) - L.power_fds[i] = -1; - L.auth_fd = -1; - char path[PATH_MAX]; - if (dr_path(path, sizeof(path), "locker.instance")) - return 1; - int instance = open(path, O_CREAT | O_RDWR | O_CLOEXEC | O_NOFOLLOW, 0600); - if (instance < 0 || flock(instance, LOCK_EX | LOCK_NB)) - return 1; - L.listener = dr_socket("lock.sock", true); - if (L.listener < 0) - return 1; - pthread_mutex_init(&G.lock, NULL); - mgr_set_defaults(); - if (!mgr_load_config()) { fprintf(stderr, "%s\n", C.error); return 1; } - L.power_config = G.config; - L.opacity=1; - refresh_appearance(); - srand(time(NULL) ^ getpid()); - L.xkb = xkb_context_new(0); - if (!L.xkb) - return 1; - const char *locale = setlocale(LC_CTYPE, ""); - L.compose_table = xkb_compose_table_new_from_locale(L.xkb, locale ? locale : "C", 0); - if (L.compose_table) - L.compose = xkb_compose_state_new(L.compose_table, 0); - struct json_object *topology = mgr_ipc_request("window-rules/list-outputs", "{}"); - L.target_count = parse_targets(topology, L.targets); - if (topology) - json_object_put(topology); - L.display = wl_display_connect(NULL); - if (!L.display) - return 1; - struct wl_registry *registry = wl_display_get_registry(L.display); - wl_registry_add_listener(registry, ®istry_listener, NULL); - if (wl_display_roundtrip(L.display) < 0 || !L.compositor || !L.manager || L.failed) { - fprintf(stderr, "Digital Rain: session-lock protocol unavailable\n"); - return 1; - } - L.lock = ext_session_lock_manager_v1_lock(L.manager); - ext_session_lock_v1_add_listener(L.lock, &lock_listener, NULL); - for (int i = 0; i < LOCK_OUTPUTS; i++) - if (L.outputs[i].output) - create_surface(&L.outputs[i]); - L.started = L.last_input = render_monotime(); - L.fade_updated=L.started; - L.dirty = true; - unsigned broadcast = 0; - bool unlocked = false; - while (!lock_stop && !L.finished && !L.failed) { - if (wl_display_dispatch_pending(L.display) < 0) - break; - if (L.topology_dirty) { - restore_monitors(); - struct json_object *o = mgr_ipc_request("window-rules/list-outputs", "{}"); - int n = parse_targets(o, L.targets); - if (o) - json_object_put(o); - if (n >= 0) { - L.target_count = n; - G.output_count = n; - for (int i = 0; i < n; i++) - mgr_init_output(&G.outputs[i], L.targets[i].name, render_monotime()); - G.config_mtime = 0; - mgr_load_config(); - } - L.topology_dirty = false; - } - double now = render_monotime(); - if (now>=L.settings_due) refresh_appearance(); - double target=1; - if (L.ambient_theme) { - double delay=theme_number("black_delay",600,0,86400); - if (delay>0 && now-L.started>=delay) target=0; - double step=fmax(0,now-L.fade_updated)/theme_number("fade_duration",2,.1,30); - double old=L.opacity; - L.opacity=target>old ? fmin(target,old+step) : fmax(target,old-step); - bool black=L.opacity<=0; - if (L.black!=black) { L.black=black;lock_changed(); } - if (old!=L.opacity) L.dirty=true; - } else if (!L.black && G.config.matrix_black_rain_timeout > 0 && - now - L.started >= G.config.matrix_black_rain_timeout) { - L.black = true; - lock_changed(); - } - double hide_delay=theme_number("ui_hide_delay",15,0,600); - if (L.prompt && !L.auth_pid && hide_delay>0 && now - L.last_input >= hide_delay) { - L.prompt = false; - erase_password(); - L.message[0] = 0; - lock_changed(); - } - double ui_target=L.prompt || (L.ambient_theme && !L.black && !theme_bool("ui_on_interaction")) ? 1 : 0; - double old_ui=L.ui_opacity; - double ui_step=fmax(0,now-L.fade_updated)/theme_number("ui_fade_duration",.25,.05,3); - L.ui_opacity=L.ambient_theme ? (ui_target>old_ui ? fmin(ui_target,old_ui+ui_step) : fmax(ui_target,old_ui-ui_step)) : ui_target; - if (old_ui!=L.ui_opacity) { - L.dirty=true; - if (old_ui==0 || L.ui_opacity==0 || L.ui_opacity==1) lock_changed(); - } - L.fade_updated=now; - time_t minute=time(NULL)/60; - if (minute!=L.clock_minute) { L.clock_minute=minute;if (L.ui_opacity>0 && theme_bool("show_clock")) L.dirty=true; } - bool moving=!L.ambient_theme || (strcmp(L.background,"black") && theme_number("speed",1,0,3)>0); - bool fading=L.ambient_theme && L.opacity!=target; - bool ui_fading=L.ui_opacity!=ui_target; - bool frame = ((!L.black && (moving || fading)) || ui_fading) && now >= L.next_frame; - for (int i = 0; i < LOCK_OUTPUTS; i++) - if (L.outputs[i].output && (frame || L.dirty || L.outputs[i].dirty)) - if (!draw_output(&L.outputs[i], !L.black)) - L.failed = true; - if (frame) - L.next_frame = now + 1.0 / TARGET_FPS; - L.dirty = false; - power_monitors(); - if (broadcast != L.generation) { - struct json_object *o = lock_status(); - for (int i = 0; i < DR_CLIENTS; i++) - if (L.clients[i] >= 0 && L.watching[i] && !dr_send(L.clients[i], o)) { - close(L.clients[i]); - L.clients[i] = -1; - } - json_object_put(o); - broadcast = L.generation; - } - if (L.failed) - break; - if (wl_display_prepare_read(L.display)) - continue; - struct pollfd fds[3 + DR_CLIENTS] = {{.fd = wl_display_get_fd(L.display), .events = POLLIN}, - {.fd = L.listener, .events = POLLIN}, - {.fd = L.auth_fd, .events = POLLIN}}; - for (int i = 0; i < DR_CLIENTS; i++) - fds[3 + i] = (struct pollfd){.fd = L.clients[i], .events = POLLIN}; - if (wl_display_flush(L.display) < 0) { - if (errno == EAGAIN) - fds[0].events |= POLLOUT; - else { - wl_display_cancel_read(L.display); - break; - } - } - double deadline = ((!L.black && (moving || fading)) || ui_fading) ? L.next_frame : now+1; - deadline=fmin(deadline,L.settings_due); - if (L.prompt && !L.auth_pid && hide_delay>0) - deadline = fmin(deadline, L.last_input + hide_delay); - if (L.auth_pid) - deadline = fmin(deadline, now + .05); - int timeout = (int)fmax(0, ceil((deadline - render_monotime()) * 1000)); - int rc = poll(fds, 3 + DR_CLIENTS, timeout); - if (rc > 0 && (fds[0].revents & POLLIN)) { - if (wl_display_read_events(L.display) < 0) - break; - } else - wl_display_cancel_read(L.display); - if (rc < 0 && errno != EINTR) - break; - if (fds[0].revents & (POLLHUP | POLLERR | POLLNVAL)) - break; - if (fds[1].revents & POLLIN) { - int fd = dr_accept(L.listener), slot = -1; - for (int i = 0; i < DR_CLIENTS; i++) - if (L.clients[i] < 0) { - slot = i; - break; - } - if (fd >= 0) { - if (slot >= 0) { - L.clients[slot] = fd; - L.watching[slot] = false; - } else - close(fd); - } - } - for (int i = 0; i < DR_CLIENTS; i++) - if (L.clients[i] >= 0 && fds[3 + i].revents) { - uint32_t magic = 0; - if (recv(L.clients[i], &magic, sizeof(magic), MSG_PEEK | MSG_DONTWAIT) == sizeof(magic) && magic == DR_LIVE_MAGIC) { - union { dr_config_packet config; dr_effect_packet effects; } packet; - ssize_t n = recv(L.clients[i], &packet, sizeof(packet), MSG_DONTWAIT | MSG_TRUNC); - if (n == sizeof(dr_config_packet) && dr_config_valid(&packet.config)) { - L.appearance = packet.config; - G.config = packet.config.config; - ++L.theme_revision; - mgr_apply_output_config(); - ++L.generation; - if (!L.black || L.prompt) L.dirty = true; - } else if (n == sizeof(dr_effect_packet) && dr_effect_valid(&packet.effects)) L.effects = packet.effects; - continue; - } - struct json_object *req = dr_receive(L.clients[i]); - const char *method = dr_string(req, "method"); - struct json_object *reply = NULL; - if (method && (!strcmp(method, "status") || !strcmp(method, "watch") || - !strcmp(method, "wake"))) { - if (!strcmp(method, "watch")) - L.watching[i] = true; - if (!strcmp(method, "wake")) - input_activity(); - reply = lock_status(); - } else - reply = dr_error("unsupported locker operation"); - if (!req || !dr_send(L.clients[i], reply)) { - close(L.clients[i]); - L.clients[i] = -1; - } - if (req) - json_object_put(req); - json_object_put(reply); - } - if (L.auth_pid && finish_auth()) { - unlocked = true; - break; - } - } - erase_password(); - restore_monitors(); - if (L.auth_pid) { - kill(L.auth_pid, SIGKILL); - waitpid(L.auth_pid, NULL, 0); - } - /* Disconnecting without authenticated unlock leaves compositor protection. */ - wl_display_disconnect(L.display); - if (!dr_path(path, sizeof(path), "lock.sock")) - unlink(path); - close(L.listener); - close(instance); - return unlocked ? 0 : 1; -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-mcp.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-mcp.py deleted file mode 100755 index 2a91fef37076..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-mcp.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env python3 -"""Optional stdio MCP adapter. No daemon, network listener, or password channel.""" -import json -import os -import socket -import sys -from pathlib import Path - -MAX_PACKET = 16384 -VERSIONS = ('2025-11-25', '2025-06-18', '2025-03-26', '2024-11-05') -OUTPUT = {'type': 'string', 'description': 'Physical connector name; omit for all monitors.'} -SETTINGS = {'type': 'object', 'description': 'section.KEY values validated against digitalrain://schema.'} -ALERT_ID = {'type': 'string', 'minLength': 1, 'maxLength': 63} -TOOLS = { - 'reload': ('Validate and reload the selected TOML configuration.', {}, []), - 'alert': ('Set or renew a temporary visual alert without activating rain or waking black screens.', {'source': ALERT_ID, 'id': ALERT_ID, 'severity': {'type': 'string', 'enum': ['info', 'success', 'warning', 'error']}, 'seconds': {'type': 'number', 'minimum': 1, 'maximum': 86400}, 'output': OUTPUT, 'message': {'type': 'string', 'maxLength': 127}}, ['source', 'id', 'severity', 'seconds']), - 'clear_alert': ('Clear an alert by producer and ID; safe to repeat.', {'source': ALERT_ID, 'id': ALERT_ID}, ['source', 'id']), - 'list_alerts': ('List active alerts in bounded pages.', {'offset': {'type': 'integer', 'minimum': 0, 'maximum': 64}, 'limit': {'type': 'integer', 'minimum': 1, 'maximum': 16}}, []), - 'status': ('Read monitor phases, focus, deadlines, inhibitors and lock state.', {}, []), - 'explain': ('Explain current monitor behavior.', {}, []), - 'activate': ('Start rain on the selected monitors.', {'output': OUTPUT}, []), - 'black': ('Cover selected monitors with solid black.', {'output': OUTPUT}, []), - 'wake': ('Wake monitors or show the lock prompt; never unlocks the session.', {'output': OUTPUT}, []), - 'lock': ('Request a session-wide native Digital Rain lock; query lock_status for confirmation.', {}, []), - 'lock_status': ('Read confirmed or pending lock state.', {}, []), - 'inhibit': ('Temporarily inhibit saver activation.', {'output': OUTPUT, 'seconds': {'type': 'number', 'exclusiveMinimum': 0, 'maximum': 86400}, 'reason': {'type': 'string', 'maxLength': 127}}, ['seconds', 'reason']), - 'uninhibit': ('Release a timed inhibitor.', {'id': {'type': 'integer', 'minimum': 1}}, ['id']), - 'preview': ('Preview validated settings temporarily; does not save configuration.', {'output': OUTPUT, 'settings': SETTINGS, 'seconds': {'type': 'number', 'exclusiveMinimum': 0, 'maximum': 600}}, ['settings']), - 'settings': ('Apply validated settings; save=true also writes the selected user-owned config.', {'settings': SETTINGS, 'save': {'type': 'boolean', 'default': False}}, ['settings']), -} - - -def request(method, arguments=None): - base = Path(os.environ['XDG_RUNTIME_DIR']) - st = base.lstat() - if not base.is_dir() or base.is_symlink() or st.st_uid != os.getuid() or st.st_mode & 0o077: - raise ValueError('Private user runtime directory required') - payload = json.dumps({'version': 1, 'method': method, **(arguments or {})}, allow_nan=False).encode() - if len(payload) > MAX_PACKET: - raise ValueError('Request too large') - with socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) as conn: - conn.settimeout(10) - conn.connect(str(base / 'digitalrain/control.sock')) - conn.sendall(payload) - raw, _, flags, _ = conn.recvmsg(MAX_PACKET) - if flags & socket.MSG_TRUNC: - raise ValueError('Oversized manager reply') - return json.loads(raw) - - -def handle(method, params): - if method == 'initialize': - offered = params.get('protocolVersion') - return {'protocolVersion': offered if offered in VERSIONS else VERSIONS[0], - 'serverInfo': {'name': 'digitalrain', 'version': '1.2.0'}, - 'capabilities': {'tools': {}, 'resources': {}}} - if method == 'ping': - return {} - if method == 'tools/list': - return {'tools': [{'name': name, 'description': description, - 'inputSchema': {'type': 'object', 'properties': properties, 'required': required, 'additionalProperties': False}, - 'annotations': {'readOnlyHint': name in ('status', 'explain', 'lock_status', 'list_alerts'), 'openWorldHint': False}} - for name, (description, properties, required) in TOOLS.items()]} - if method == 'tools/call': - name, arguments = params.get('name'), params.get('arguments', {}) - if name not in TOOLS or not isinstance(arguments, dict): - raise ValueError('Unknown tool or invalid arguments') - _, properties, required = TOOLS[name] - if set(arguments) - set(properties) or set(required) - set(arguments): - raise ValueError('Unknown or missing arguments') - try: - result = request(name, arguments) - except (OSError, ValueError, KeyError) as error: - result = {'error': str(error)} - return {'content': [{'type': 'text', 'text': json.dumps(result)}], 'isError': 'error' in result} - if method == 'resources/list': - return {'resources': [{'uri': f'digitalrain://{name}', 'name': name, 'mimeType': 'application/json'} for name in ('status', 'schema')]} - if method == 'resources/read': - uri = params.get('uri') - if uri not in ('digitalrain://status', 'digitalrain://schema'): - raise ValueError('Unknown resource') - return {'contents': [{'uri': uri, 'mimeType': 'application/json', 'text': json.dumps(request(uri.split('://')[1]))}]} - raise LookupError('Method not found') - - -def main(): - initialized = False - while True: - line = sys.stdin.buffer.readline(65537) - if not line: - return - if len(line) > 65536: - return # terminate an oversized stream instead of buffering it - ident = None - try: - msg = json.loads(line) - if not isinstance(msg, dict) or msg.get('jsonrpc') != '2.0' or not isinstance(msg.get('method'), str): - raise ValueError('Invalid JSON-RPC request') - if 'id' not in msg: - continue - ident = msg['id'] - params = msg.get('params', {}) - if not isinstance(params, dict): - raise ValueError('params must be an object') - if not initialized and msg['method'] not in ('initialize', 'ping'): - raise ValueError('Initialize first') - result = handle(msg['method'], params) - initialized |= msg['method'] == 'initialize' - response = {'jsonrpc': '2.0', 'id': ident, 'result': result} - except (ValueError, LookupError, OSError) as error: - response = {'jsonrpc': '2.0', 'id': ident, 'error': {'code': -32601 if isinstance(error, LookupError) else -32602, 'message': str(error)}} - print(json.dumps(response, separators=(',', ':'), allow_nan=False), flush=True) - - -if __name__ == '__main__': - main() diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-power.cpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-power.cpp deleted file mode 100644 index bf1073617bdf..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-power.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include <map> -#include <memory> -#include <wayfire/core.hpp> -#include <wayfire/output.hpp> -#include <wayfire/output-layout.hpp> -#include <wayfire/output-stitch.hpp> -#include <wayfire/plugin.hpp> -#include <wayfire/util.hpp> -#include <wayfire/nonstd/wlroots-full.hpp> -#include <wayfire/plugins/common/shared-core-data.hpp> -#include <wayfire/plugins/ipc/ipc-method-repository.hpp> - -namespace -{ -/* Power belongs to the renderer's IPC connection. A crash, disconnect or - * plugin unload releases it, and multiple clients cannot wake each other's - * monitor. Track the actual connector so hotplug cannot restore a replacement. */ -class digitalrain_power_plugin_t : public wf::plugin_interface_t -{ - using client_t = wf::ipc::client_interface_t; - struct lease_t - { - std::string name; - wlr_output *handle; - wf::wl_listener_wrapper destroyed; - lease_t(std::string name, wlr_output *handle) : name(name), handle(handle) - { - destroyed.set_callback([this] (void*) - { - this->handle = nullptr; - destroyed.disconnect(); - }); - destroyed.connect(&handle->events.destroy); - } - }; - wf::shared_data::ref_ptr_t<wf::ipc::method_repository_t> repo; - std::map<client_t*, std::unique_ptr<lease_t>> leases; - - bool owned(wlr_output *handle) const - { - for (auto& [client, lease] : leases) - if (lease->handle == handle) return true; - return false; - } - - void restore(client_t *client) - { - auto it = leases.find(client); - if (it == leases.end()) return; - auto lease = std::move(it->second); - leases.erase(it); - if (lease->handle && !owned(lease->handle)) - wf::get_core().output_layout->set_output_power(lease->name, true); - } - - wf::signal::connection_t<wf::ipc::client_disconnected_signal> disconnected = - [this] (auto ev) { restore(ev->client); }; - - public: - void init() override - { - repo->connect(&disconnected); - repo->register_method("digitalrain/power_on", [this] (wf::json_t, client_t *client) - { - restore(client); - return wf::ipc::json_ok(); - }); - repo->register_method("digitalrain/power_off", [this] (wf::json_t data, client_t *client) - { - if (!client || !data.has_member("output") || !data["output"].is_string()) - return wf::ipc::json_error("An IPC connection and physical output name are required"); - auto name = data["output"].as_string(); - auto layout = wf::get_core().output_layout.get(); - wlr_output *handle = nullptr; - for (auto& [head, state] : layout->get_current_configuration()) - if (name == head->name) handle = head; - if (!handle) return wf::ipc::json_error("Output not found"); - auto output = layout->find_output(handle); - if (output) if (auto group=output->get_data<wf::output_stitch_layout_t>()) - { - // The first physical head can be the backing output for a - // stitched desktop. It still has its own power policy. - bool physical=false; - for (const auto& zone:group->zones) physical |= zone.name==name; - if (!physical) - return wf::ipc::json_error("Select a physical monitor, not the combined desktop"); - } - if (leases.count(client) && leases.at(client)->handle == handle) - return wf::ipc::json_ok(); - bool already_owned = owned(handle); - if (!already_owned && !handle->enabled) - return wf::ipc::json_error("Output is already powered off by another owner"); - restore(client); - if (!already_owned && !layout->set_output_power(name, false)) - return wf::ipc::json_error("Output power control failed"); - leases[client] = std::make_unique<lease_t>(name, handle); - return wf::ipc::json_ok(); - }); - } - - void fini() override - { - repo->unregister_method("digitalrain/power_off"); - repo->unregister_method("digitalrain/power_on"); - disconnected.disconnect(); - while (!leases.empty()) restore(leases.begin()->first); - } -}; -} -DECLARE_WAYFIRE_PLUGIN(digitalrain_power_plugin_t); diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-power.xml b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-power.xml deleted file mode 100644 index dd28c0c51111..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-power.xml +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version='1.0' encoding='utf-8'?> -<wayfire> - <plugin name="digitalrain-power"> - <_short>Monitor power control</_short> - <_long>Optional per-monitor power control after idle black transitions.</_long> - <category>Utility</category> - </plugin> -</wayfire> diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-sampler.cpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-sampler.cpp deleted file mode 100644 index 832a08f1abb1..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-sampler.cpp +++ /dev/null @@ -1,162 +0,0 @@ -#include "digitalrain-targets.hpp" -#include <algorithm> -#include <array> -#include <chrono> -#include <cmath> -#include <map> -#include <set> -#include <string> -#include <drm_fourcc.h> -#include <wayfire/core.hpp> -#include <wayfire/output-layout.hpp> -#include <wayfire/output.hpp> -#include <wayfire/plugin.hpp> -#include <wayfire/render-manager.hpp> -#include <wayfire/nonstd/wlroots-full.hpp> -#include <wayfire/plugins/common/shared-core-data.hpp> -#include <wayfire/plugins/ipc/ipc-method-repository.hpp> - -/* A small on-demand summary of the actual scene below SCREENSAVER. No - * screenshot protocol, wallpaper paths, pixel grids or continuous render hook. */ -namespace { -class digitalrain_sampler_plugin_t : public wf::plugin_interface_t -{ - using clock = std::chrono::steady_clock; - wf::shared_data::ref_ptr_t<wf::ipc::method_repository_t> repo; - struct sample_t - { - clock::time_point when; - wf::geometry_t geometry; - double luminance; - }; - std::map<std::string, sample_t> cache; - - static double linear(double c) - { - return c <= 0.04045 ? c / 12.92 : std::pow((c + 0.055) / 1.055, 2.4); - } - - wf::json_t sample() - { - wf::json_t response; - response["version"] = 1; - response["outputs"] = wf::json_t{}; - std::set<std::string> present; - for (auto output : wf::get_core().output_layout->get_outputs()) - { - wf::auxilliary_buffer_t desktop; - bool composed = false; - for (const auto& target_region : digitalrain_targets(output)) { - const std::string name = target_region.name; - present.insert(name); - wf::json_t result; - const auto geometry = target_region.geometry; - const auto now = clock::now(); - if (output->is_inhibited() || geometry.width <= 0 || geometry.height <= 0) - { - cache.erase(name); - result["error"] = "output unavailable or locked"; - response["outputs"][name] = result; - continue; - } - - auto cached = cache.find(name); - if (cached != cache.end() && cached->second.geometry == geometry && - now - cached->second.when < std::chrono::seconds(1)) - { - result["luminance"] = cached->second.luminance; - result["cached"] = true; - response["outputs"][name] = result; - continue; - } - - const double scale = 64.0 / std::max(geometry.width, geometry.height); - const int width = std::max(1, int(std::ceil(geometry.width * scale))); - const int height = std::max(1, int(std::ceil(geometry.height * scale))); - wf::auxilliary_buffer_t buffer; - // Custom scene effects can use output pixel sizes internally. Compose - // at the output's native size, then downsample on the GPU; only the - // tiny summary is read back. Both buffers are released after sampling. - const wf::dimensions_t native{output->handle->width, output->handle->height}; - if (!composed && desktop.allocate(native) != wf::buffer_reallocation_result_t::FAILED) { - wf::render_target_t target{desktop}; - target.geometry = output->get_layout_geometry(); - target.scale = output->handle->scale; - target.wl_transform = output->handle->transform; - composed = output->render->render_desktop(target); - } - bool ok = composed && buffer.allocate({width, height}) != wf::buffer_reallocation_result_t::FAILED; - if (ok) { - // Stitched members are untransformed at scale 1. Ordinary outputs - // retain the original full-buffer sampling for all transforms. - auto source = output->get_data<wf::output_stitch_layout_t>() ? geometry : - wf::geometry_t{0, 0, double(native.width), double(native.height)}; - buffer.get_renderbuffer().blit(desktop, source, {0, 0, double(width), double(height)}); - } - - // ABGR8888 is RGBA byte order on little-endian systems. Reading words - // keeps the channel extraction independent of host byte order. - std::array<uint32_t, 64 * 64> pixels{}; - auto texture = ok ? buffer.get_texture() : nullptr; - if (texture) - { - wlr_texture_read_pixels_options options{}; - options.data = pixels.data(); - options.format = DRM_FORMAT_ABGR8888; - options.stride = width * sizeof(uint32_t); - ok = wlr_texture_read_pixels(texture, &options); - } else - { - ok = false; - } - - if (ok) - { - double luminance = 0.0; - for (int i = 0; i < width * height; ++i) - { - const auto pixel = pixels[i]; - luminance += 0.2126 * linear((pixel & 255) / 255.0) + - 0.7152 * linear(((pixel >> 8) & 255) / 255.0) + - 0.0722 * linear(((pixel >> 16) & 255) / 255.0); - } - - luminance /= width * height; - cache[name] = {now, geometry, luminance}; - result["luminance"] = luminance; - result["cached"] = false; - result["width"] = width; - result["height"] = height; - } else - { - cache.erase(name); - result["error"] = "desktop sample failed"; - } - - response["outputs"][name] = result; - } - } - - for (auto it = cache.begin(); it != cache.end();) - { - if (!present.count(it->first)) { it = cache.erase(it); } else { ++it; } - } - - return response; - } - - public: - void init() override - { - repo->register_method("digitalrain/sample_desktop", [this] (wf::json_t) { return sample(); }); - } - - void fini() override - { - repo->unregister_method("digitalrain/sample_desktop"); - cache.clear(); - } -}; - -} // Keep implementation symbols local when an old helper is still resident. -DECLARE_WAYFIRE_PLUGIN(digitalrain_sampler_plugin_t); diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-sampler.xml b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-sampler.xml deleted file mode 100644 index af5f6165f78b..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-sampler.xml +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version='1.0' encoding='utf-8'?> -<wayfire> - <plugin name="digitalrain-sampler"> - <_short>Adaptive colour sampling</_short> - <_long>Sample desktop contrast for adaptive Digital Rain colours.</_long> - <category>Utility</category> - </plugin> -</wayfire> diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-targets.hpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-targets.hpp deleted file mode 100644 index 9378692daefb..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain-targets.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once -#include <wayfire/output.hpp> -#include <wayfire/output-stitch.hpp> -#include <vector> - -inline std::vector<wf::output_stitch_zone_t> digitalrain_targets(wf::output_t *output) -{ - if (auto stitch = output->get_data<wf::output_stitch_layout_t>()) - if (!stitch->zones.empty()) return stitch->zones; - return {{output->to_string(), output->get_relative_geometry()}}; -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain.c b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain.c deleted file mode 100644 index fb6d536797fd..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain.c +++ /dev/null @@ -1,4205 +0,0 @@ -/* - * digitalrain -- combined OLED screensaver: manager + renderer in one binary. - * - * No flags → manager mode (idle detection, Wayfire focus IPC, Wayland outputs) - * --render → renderer mode (Wayland layer-shell Matrix rain) - * - * The manager fork/execs itself with --render --output <name> [--color ...] - * [--font-size ...] [--min-speed ...] [--max-speed ...] etc. using /proc/self/exe. - * - * Build: make -C ~/.local/src digitalrain - * Requires: json-c, pthreads, wayland-client, wayland-egl, EGL, GLESv2, freetype2 - */ - -#define _GNU_SOURCE -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <stdbool.h> -#include <unistd.h> -#include <errno.h> -#include <signal.h> -#include <time.h> -#include <math.h> -#include <fcntl.h> -#include <pthread.h> -#include <sys/socket.h> -#include <sys/time.h> -#include <sys/file.h> -#include <sys/stat.h> -#include <sys/un.h> -#include <sys/types.h> -#include <sys/wait.h> -#include <sys/mman.h> -#include <sys/prctl.h> -#include <stdarg.h> -#include <stdint.h> -#include <poll.h> -#include <dirent.h> - -#include <json-c/json.h> -#include <wayland-client.h> -#include <wayland-egl.h> -#include <EGL/egl.h> -#include <EGL/eglext.h> -#include <GLES2/gl2.h> -#include <ft2build.h> -#include FT_FREETYPE_H - -#include "wlr-layer-shell-unstable-v1-client-protocol.h" -#include "xdg-output-unstable-v1-client-protocol.h" -#include "ext-idle-notify-v1-client-protocol.h" -#include "digitalrain-control.h" - -/* A saver target is a physical region of a Wayland desktop output. */ -#define MAX_OUTPUTS 8 -#define MAX_OUTPUT_NAME 64 -typedef struct { - char name[MAX_OUTPUT_NAME], desktop[MAX_OUTPUT_NAME]; - int x, y, width, height; - int layout_x, layout_y; - bool region; -} saver_target_t; - -static struct json_object *mgr_ipc_request(const char *method, const char *data_json); - -static bool target_geometry(struct json_object *object, saver_target_t *target) { - struct json_object *g, *x, *y, *w, *h; - if (!json_object_object_get_ex(object, "geometry", &g) || - !json_object_object_get_ex(g, "x", &x) || - !json_object_object_get_ex(g, "y", &y) || - !json_object_object_get_ex(g, "width", &w) || - !json_object_object_get_ex(g, "height", &h)) return false; - target->x = json_object_get_int(x); target->y = json_object_get_int(y); - target->width = json_object_get_int(w); target->height = json_object_get_int(h); - return target->width > 0 && target->height > 0; -} - -/* -1 means discovery failed: retain the previous topology in that case. */ -static int parse_targets(struct json_object *outputs, saver_target_t targets[MAX_OUTPUTS]) { - if (!outputs || !json_object_is_type(outputs, json_type_array)) return -1; - int count = 0; - for (size_t i = 0; i < json_object_array_length(outputs); ++i) { - struct json_object *output = json_object_array_get_idx(outputs, i), *name, *members; - saver_target_t desktop = {0}; - if (!json_object_object_get_ex(output, "name", &name) || - !json_object_is_type(name, json_type_string) || !target_geometry(output, &desktop)) return -1; - snprintf(desktop.name, sizeof(desktop.name), "%s", json_object_get_string(name)); - snprintf(desktop.desktop, sizeof(desktop.desktop), "%s", desktop.name); - desktop.layout_x = desktop.x; desktop.layout_y = desktop.y; - desktop.x = desktop.y = 0; - if (json_object_object_get_ex(output, "members", &members) && - json_object_is_type(members, json_type_array) && json_object_array_length(members)) { - for (size_t j = 0; j < json_object_array_length(members); ++j) { - struct json_object *member = json_object_array_get_idx(members, j); - saver_target_t target = desktop; - if (!json_object_object_get_ex(member, "name", &name) || - !json_object_is_type(name, json_type_string) || !target_geometry(member, &target)) return -1; - if (target.x < 0 || target.y < 0 || target.width > desktop.width - target.x || - target.height > desktop.height - target.y || count == MAX_OUTPUTS) return -1; - snprintf(target.name, sizeof(target.name), "%s", json_object_get_string(name)); - target.region = true; - targets[count++] = target; - } - } else { - if (count == MAX_OUTPUTS) return -1; - targets[count++] = desktop; - } - } - return count; -} - -/* =================================================================== - * RENDERER MODE (Wayland layer-shell Matrix rain) - * =================================================================== */ - -/* ── Tuning constants ─────────────────────────────────────────────── */ - -#define TARGET_FPS 30 -#define FRAME_INTERVAL_US (1000000 / TARGET_FPS) - -/* These are now defaults; actual values come from CLI flags */ -#define DEFAULT_MIN_DENSITY 0.25 -#define DEFAULT_MAX_DENSITY 0.45 -#define DEFAULT_MIN_TRAIL 4 -#define DEFAULT_MAX_TRAIL 28 -#define DEFAULT_MIN_SPEED 0.4 -#define DEFAULT_MAX_SPEED 2.0 -#define DEFAULT_AVG_SPEED 0.8 -#define DEFAULT_FONT_SIZE 12 -#define MIN_FONT_SIZE 6 -#define MAX_FONT_SIZE 72 -#define DEFAULT_HEAD_HOLD_FRAMES 5 -#define DEFAULT_HEAD_MUTATION_CHANCE 0.15 -#define DEFAULT_HEAD_WHITE_MIN 0.75 -#define DEFAULT_HEAD_WHITE_MAX 1.00 -#define MUTATION_CHANCE 0.07 -#define RESPAWN_DELAY_MAX 40 -#define FADE_LEVELS 6 -#define HEAD_GLOW_EXTRA 2 - -#define DEFAULT_DEPTH_LAYERS 4 -#define MIN_DEPTH_LAYERS 1 -#define MAX_DEPTH_LAYERS 8 - -/* Character sets */ -static const uint32_t KATAKANA_START = 0xFF66; -static const uint32_t KATAKANA_END = 0xFF9D; -#define NUM_KATAKANA (KATAKANA_END - KATAKANA_START + 1) - -static const char *LATIN_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; -static const char *DIGIT_CHARS = "0123456789"; -static const char *SYMBOL_CHARS = "!@#$%^&*()-=+[]{}|;:<>?/~"; - -/* ── Color presets ────────────────────────────────────────────────── */ - -typedef struct { float r, g, b; } rgb_t; - -static const struct { const char *name; rgb_t color; } RENDER_COLOR_TABLE[] = { - { "green", { 0.0f, 1.0f, 0.0f } }, - { "red", { 1.0f, 0.2f, 0.2f } }, - { "blue", { 0.3f, 0.5f, 1.0f } }, - { "white", { 1.0f, 1.0f, 1.0f } }, - { "yellow", { 1.0f, 1.0f, 0.0f } }, - { "cyan", { 0.0f, 1.0f, 1.0f } }, - { "magenta", { 1.0f, 0.0f, 1.0f } }, -}; -#define RENDER_NUM_COLORS (sizeof(RENDER_COLOR_TABLE) / sizeof(RENDER_COLOR_TABLE[0])) - -#include "digitalrain-config.h" -#include "digitalrain-live.h" - -/* ── Glyph atlas types ────────────────────────────────────────────── */ - -#define ATLAS_MAX_GLYPHS 256 - -typedef struct { - uint32_t codepoint; - float u0, v0, u1, v1; /* UV coords in atlas */ - int width, height; /* glyph bitmap size */ - int bearing_x, bearing_y; - int advance; /* horizontal advance in pixels (26.6 fixed >> 6) */ -} glyph_info_t; - -typedef struct { - GLuint texture; - int tex_width, tex_height; - glyph_info_t glyphs[ATLAS_MAX_GLYPHS]; - int glyph_count; - int cell_w, cell_h; /* max advance and line height for grid calc */ -} glyph_atlas_t; - -/* ── Data types (renderer) ────────────────────────────────────────── */ - -typedef struct { - uint32_t codepoint; - int age; - bool mirrored; /* horizontally flip the glyph */ - float white_mix; /* white fraction used while the glyph is near the head */ -} render_cell_t; - -typedef struct { - bool active; - double speed; - double head_pos; - int trail_len; - int respawn_timer; - float x_offset; /* horizontal jitter in pixels */ - bool skip; /* permanently inactive column for gaps */ - int flash_timer; /* when > 0, column is flashing */ - float flash_brightness; /* brightness boost during flash */ - rgb_t color; /* per-trail color */ - int last_head_row; - int head_hold_frames; -} render_column_t; - -typedef struct { - int cols, rows; - int cell_w, cell_h; - double opacity; - double speed_scale; - double density; - double font_scale; - render_column_t *columns; - render_cell_t *cells; -} depth_layer_t; - -/* Depth layer extreme values for interpolation */ -#define LAYER_FAR_FONT_SCALE 0.5 -#define LAYER_FAR_OPACITY 0.2 -#define LAYER_FAR_SPEED_SCALE 0.4 -#define LAYER_FAR_DENSITY 0.35 - -#define LAYER_NEAR_FONT_SCALE 1.25 -#define LAYER_NEAR_OPACITY 1.0 -#define LAYER_NEAR_SPEED_SCALE 1.4 -#define LAYER_NEAR_DENSITY 0.15 - -/* ── Vertex type for batched quad rendering ───────────────────────── */ - -typedef struct { - float x, y; - float u, v; - float r, g, b, a; -} vertex_t; - -/* Max quads per draw call (each quad = 6 vertices) */ -#define MAX_QUADS (256 * 256) -#define VERTS_PER_QUAD 6 - -/* ── Render phase lifecycle ────────────────────────────────────────── */ - -typedef enum { - PHASE_FADE_TO_BLACK, /* desktop fades to black overlay */ - PHASE_FADE_IN, /* matrix rain fades in on black */ - PHASE_RUNNING, /* normal matrix rain */ - PHASE_FADE_RAIN_TO_BLACK, /* timed rain fade, keeping the black overlay */ - PHASE_BLACK, /* retain a static black surface until activity */ - PHASE_FADE_OUT, /* matrix rain fades out to black */ - PHASE_FADE_TO_DESKTOP /* black fades to reveal desktop */ -} render_phase_t; - -/* ── Renderer global state ────────────────────────────────────────── */ - -static struct { - /* Wayland globals */ - struct wl_display *display; - struct wl_registry *registry; - struct wl_compositor *compositor; - struct zwlr_layer_shell_v1 *layer_shell; - struct zxdg_output_manager_v1 *xdg_output_manager; - - /* Target output */ - struct wl_output *target_output; - char target_name[128]; - saver_target_t target; - bool output_found; - int output_width; - int output_height; - int output_scale; - - /* Surface */ - struct wl_surface *surface; - struct zwlr_layer_surface_v1 *layer_surface; - uint32_t configure_serial; - bool configured; - bool surface_dirty; - int surface_width; - int surface_height; - - /* EGL */ - EGLDisplay egl_display; - EGLContext egl_context; - EGLSurface egl_surface; - struct wl_egl_window *egl_window; - - /* OpenGL */ - GLuint shader_program; - GLint u_projection; - GLint u_atlas; - GLint a_pos; - GLint a_uv; - GLint a_color; - GLuint vbo; - vertex_t *vertex_buf; - int vertex_count; - - /* FreeType */ - FT_Library ft_library; - FT_Face ft_face; - - /* Glyph atlas (one at largest font size) */ - glyph_atlas_t atlas; - - /* Matrix state */ - int num_depth_layers; - double simulation_credit; - depth_layer_t layers[MAX_DEPTH_LAYERS]; - rgb_t base_color; - bool adaptive_color; - rgb_t adaptive_desktop_color; - rgb_t adaptive_black_color; - int color_mode; /* 0=fixed, 1=random_trail_named, 2=random_trail_hex */ - int font_size; - - /* Configurable ranges (set via CLI flags) */ - double min_speed; - double max_speed; - double avg_speed; - int min_trail; - int max_trail; - double min_density; - double max_density; - double layer_near_density; - double layer_far_density; - int head_hold_frames; - double head_mutation_chance; - double head_white_min; - double head_white_max; - - /* Run control */ - volatile sig_atomic_t stop; - volatile sig_atomic_t fade_out_requested; - volatile sig_atomic_t adaptive_reload; - bool closed; - - /* Phase-based transition state */ - render_phase_t phase; - int phase_frame; - float surface_alpha; /* alpha for glClearColor (0=transparent, 1=opaque) */ - float fade_opacity; /* rain opacity multiplier (0=invisible, 1=full) */ - int fade_frames; /* configurable transition duration */ - double transparent_timeout; - double black_rain_timeout; - double black_start_time; - double render_start_time; - int blacken_frames; - bool background_black; - bool ambient; /* background client; lifetime belongs to its controller */ - bool transparent; /* keep the desktop visible behind the rain */ - bool hide_desktop; /* leave only wallpaper behind transparent rain */ - int desktop_fd; /* held IPC connection; close restores desktop */ - bool desktop_resuming; - bool power_off; /* opt-in: replace final solid black with DPMS */ - int power_fd; /* compositor restores power on disconnect */ - bool power_attempted; - int report_fd, reported_phase, reported_power; - volatile sig_atomic_t black_requested; - unsigned applied_revision, attempted_revision, reported_revision; - int live_error, reported_error; - dr_effect_packet effects; - float effect_mix[MAX_OUTPUTS], effect_energy[MAX_OUTPUTS], effect_peak[MAX_OUTPUTS]; - double effect_tick, palette_until; - double audio_attack, audio_release; - char live_color[32]; -} S; - -static double render_column_speed(int x, int y); -static rgb_t render_column_color(int x, int y, rgb_t base, float *brightness); -static void render_effect_tick(void); -static void render_live_receive(void); - -#define DEFAULT_FADE_FRAMES 30 /* 1 second at 30fps */ - -/* ── Renderer helpers ─────────────────────────────────────────────── */ - -static inline double render_randf(void) { return (double)rand() / RAND_MAX; } -static double render_monotime(void) { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return ts.tv_sec + ts.tv_nsec / 1e9; -} -static inline int render_randi(int lo, int hi) { return lo + rand() % (hi - lo + 1); } - -static inline float render_head_white_mix(void) { - return (float)(S.head_white_min + - (S.head_white_max - S.head_white_min) * render_randf()); -} - -/* Generate a random speed biased toward avg, clamped to [lo, hi]. - Uses sum-of-3-uniforms (approx gaussian) for bell-curve distribution. */ -static inline double render_rand_speed(double lo, double hi, double avg) { - double r = (render_randf() + render_randf() + render_randf()) / 3.0; - /* r is ~gaussian centered on 0.5; map to [lo, hi] biased toward avg */ - double v = (r < 0.5) - ? lo + (avg - lo) * (r / 0.5) - : avg + (hi - avg) * ((r - 0.5) / 0.5); - if (v < lo) v = lo; - if (v > hi) v = hi; - return v; -} - -static render_cell_t *render_cell_at_layer(depth_layer_t *layer, int row, int col) { - return &layer->cells[row * layer->cols + col]; -} - -static uint32_t render_random_codepoint(void) { - int choice = rand() % 100; - if (choice < 40) { - return KATAKANA_START + (rand() % NUM_KATAKANA); - } else if (choice < 70) { - return (uint32_t)LATIN_CHARS[rand() % (int)strlen(LATIN_CHARS)]; - } else if (choice < 85) { - return (uint32_t)DIGIT_CHARS[rand() % (int)strlen(DIGIT_CHARS)]; - } else { - return (uint32_t)SYMBOL_CHARS[rand() % (int)strlen(SYMBOL_CHARS)]; - } -} - -static rgb_t render_parse_hex(const char *hex) { - rgb_t c = { 0.0f, 0.0f, 0.0f }; - const char *p = hex; - if (*p == '#') p++; - unsigned int val = 0; - if (sscanf(p, "%06x", &val) == 1) { - c.r = ((val >> 16) & 0xFF) / 255.0f; - c.g = ((val >> 8) & 0xFF) / 255.0f; - c.b = (val & 0xFF) / 255.0f; - } - return c; -} - -static rgb_t render_lookup_color(const char *name) { - /* Hex color: #RRGGBB or RRGGBB */ - if (name[0] == '#' || (strlen(name) == 6 && strspn(name, "0123456789abcdefABCDEF") == 6)) - return render_parse_hex(name); - for (size_t i = 0; i < RENDER_NUM_COLORS; i++) { - if (strcasecmp(name, RENDER_COLOR_TABLE[i].name) == 0) - return RENDER_COLOR_TABLE[i].color; - } - /* Per-output random: pick one color at startup */ - if (strcasecmp(name, "random_named") == 0) - return RENDER_COLOR_TABLE[rand() % RENDER_NUM_COLORS].color; - if (strcasecmp(name, "random_hex") == 0) - return (rgb_t){ - (float)(rand() % 256) / 255.0f, - (float)(rand() % 256) / 255.0f, - (float)(rand() % 256) / 255.0f - }; - /* Per-trail random modes: base_color is fallback, actual color set per-trail */ - if (strcasecmp(name, "random_trail_named") == 0 || - strcasecmp(name, "random_trail_hex") == 0) - return RENDER_COLOR_TABLE[0].color; - return RENDER_COLOR_TABLE[0].color; -} - -enum { COLOR_FIXED = 0, COLOR_RANDOM = 1, COLOR_RANDOM_HEX = 2 }; - -static rgb_t render_random_color(void) { - return RENDER_COLOR_TABLE[rand() % RENDER_NUM_COLORS].color; -} - -static rgb_t render_random_hex_color(void) { - return (rgb_t){ - (float)(rand() % 256) / 255.0f, - (float)(rand() % 256) / 255.0f, - (float)(rand() % 256) / 255.0f - }; -} - -static rgb_t render_trail_color(void) { - switch (S.color_mode) { - case COLOR_RANDOM: return render_random_color(); - case COLOR_RANDOM_HEX: return render_random_hex_color(); - default: return S.base_color; - } -} - -/* ── FreeType / Glyph Atlas ───────────────────────────────────────── */ - -/* Build list of all codepoints we need */ -static int render_build_codepoint_list(uint32_t *out, int max) { - int n = 0; - /* Katakana */ - for (uint32_t cp = KATAKANA_START; cp <= KATAKANA_END && n < max; cp++) - out[n++] = cp; - /* Latin */ - for (const char *p = LATIN_CHARS; *p && n < max; p++) - out[n++] = (uint32_t)*p; - /* Digits */ - for (const char *p = DIGIT_CHARS; *p && n < max; p++) - out[n++] = (uint32_t)*p; - /* Symbols */ - for (const char *p = SYMBOL_CHARS; *p && n < max; p++) - out[n++] = (uint32_t)*p; - return n; -} - -static bool render_init_freetype(int font_pixel_size) { - if (FT_Init_FreeType(&S.ft_library)) { - fprintf(stderr, "Failed to init FreeType\n"); - return false; - } - - /* Try fonts in order of preference */ - static const char *font_paths[] = { - "/usr/share/fonts/noto-cjk/NotoSansMonoCJKjp-Regular.otf", - "/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc", - "/usr/share/fonts/google-noto-sans-cjk-fonts/NotoSansCJK-Regular.ttc", - "/usr/share/fonts/google-noto-cjk/NotoSansMonoCJKjp-Regular.otf", - "/usr/share/fonts/noto/NotoSansMono-Regular.ttf", - "/usr/share/fonts/liberation-mono/LiberationMono-Regular.ttf", - "/usr/share/fonts/liberation/LiberationMono-Regular.ttf", - "/usr/share/fonts/TTF/LiberationMono-Regular.ttf", - "/usr/share/fonts/dejavu/DejaVuSansMono.ttf", - "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf", - NULL - }; - - bool loaded = false; - for (int i = 0; font_paths[i]; i++) { - if (FT_New_Face(S.ft_library, font_paths[i], 0, &S.ft_face) == 0) { - fprintf(stderr, "Loaded font: %s\n", font_paths[i]); - loaded = true; - break; - } - } - - if (!loaded) { - fprintf(stderr, "No suitable font found, trying fc-match\n"); - /* Last resort: use fontconfig via popen */ - FILE *fp = popen("fc-match -f '%{file}' 'monospace'", "r"); - if (fp) { - char path[512]; - if (fgets(path, sizeof(path), fp)) { - char *nl = strchr(path, '\n'); - if (nl) *nl = '\0'; - if (FT_New_Face(S.ft_library, path, 0, &S.ft_face) == 0) { - fprintf(stderr, "Loaded font via fc-match: %s\n", path); - loaded = true; - } - } - pclose(fp); - } - } - - if (!loaded) { - fprintf(stderr, "Could not load any font\n"); - FT_Done_FreeType(S.ft_library); - return false; - } - - FT_Set_Pixel_Sizes(S.ft_face, 0, font_pixel_size); - return true; -} - -static bool render_build_atlas(int font_pixel_size) { - uint32_t codepoints[ATLAS_MAX_GLYPHS]; - int cp_count = render_build_codepoint_list(codepoints, ATLAS_MAX_GLYPHS); - - /* First pass: render all glyphs to measure total atlas size */ - FT_Set_Pixel_Sizes(S.ft_face, 0, font_pixel_size); - - /* Estimate atlas dimensions: pack in rows */ - int row_height = 0; - int atlas_w = 0, atlas_h = 0; - int padding = 2; - int max_row_w = 2048; /* max texture width */ - - /* Temporary storage for bitmaps */ - typedef struct { - uint32_t cp; - int w, h, bearing_x, bearing_y, advance; - unsigned char *bitmap; - } tmp_glyph_t; - - tmp_glyph_t *tmp_glyphs = calloc(cp_count, sizeof(tmp_glyph_t)); - if (!tmp_glyphs) return false; - int valid_count = 0; - int max_cell_w = 0, max_cell_h = 0; - - for (int i = 0; i < cp_count; i++) { - if (FT_Load_Char(S.ft_face, codepoints[i], FT_LOAD_RENDER)) { - continue; /* skip glyphs that fail to render */ - } - FT_GlyphSlot g = S.ft_face->glyph; - int gw = g->bitmap.width; - int gh = g->bitmap.rows; - - tmp_glyph_t *tg = &tmp_glyphs[valid_count]; - tg->cp = codepoints[i]; - tg->w = gw; - tg->h = gh; - tg->bearing_x = g->bitmap_left; - tg->bearing_y = g->bitmap_top; - tg->advance = (int)(g->advance.x >> 6); - - if (tg->advance > max_cell_w) max_cell_w = tg->advance; - if (gh > max_cell_h) max_cell_h = gh; - - /* Copy bitmap data */ - tg->bitmap = malloc(gw * gh); - if (!tg->bitmap && gw * gh > 0) { - for (int i = 0; i < valid_count; i++) free(tmp_glyphs[i].bitmap); - free(tmp_glyphs); return false; - } - if (tg->bitmap && gw > 0 && gh > 0) { - for (int r = 0; r < gh; r++) - memcpy(tg->bitmap + r * gw, - g->bitmap.buffer + r * g->bitmap.pitch, gw); - } - - valid_count++; - } - - /* Calculate atlas layout */ - int cx = 0, cy = 0; - row_height = 0; - for (int i = 0; i < valid_count; i++) { - tmp_glyph_t *tg = &tmp_glyphs[i]; - int gw = tg->w + padding; - int gh = tg->h + padding; - - if (cx + gw > max_row_w) { - cy += row_height; - cx = 0; - row_height = 0; - } - if (gh > row_height) row_height = gh; - cx += gw; - if (cx > atlas_w) atlas_w = cx; - } - atlas_h = cy + row_height; - - /* Round up to power of 2 */ - int pot_w = 1; while (pot_w < atlas_w) pot_w <<= 1; - int pot_h = 1; while (pot_h < atlas_h) pot_h <<= 1; - atlas_w = pot_w; - atlas_h = pot_h; - - /* Allocate atlas pixel data (single channel) */ - unsigned char *atlas_data = calloc(atlas_w * atlas_h, 1); - if (!atlas_data) { for (int i = 0; i < valid_count; i++) free(tmp_glyphs[i].bitmap); free(tmp_glyphs); return false; } - - /* Second pass: blit glyphs and record UVs */ - cx = 0; cy = 0; row_height = 0; - S.atlas.glyph_count = 0; - - for (int i = 0; i < valid_count; i++) { - tmp_glyph_t *tg = &tmp_glyphs[i]; - int gw = tg->w; - int gh = tg->h; - - if (cx + gw + padding > atlas_w) { - cy += row_height; - cx = 0; - row_height = 0; - } - if (gh + padding > row_height) row_height = gh + padding; - - /* Blit */ - if (tg->bitmap && gw > 0 && gh > 0) { - for (int r = 0; r < gh; r++) { - memcpy(atlas_data + (cy + r) * atlas_w + cx, - tg->bitmap + r * gw, gw); - } - } - - /* Record glyph info */ - glyph_info_t *gi = &S.atlas.glyphs[S.atlas.glyph_count++]; - gi->codepoint = tg->cp; - gi->u0 = (float)cx / atlas_w; - gi->v0 = (float)cy / atlas_h; - gi->u1 = (float)(cx + gw) / atlas_w; - gi->v1 = (float)(cy + gh) / atlas_h; - gi->width = gw; - gi->height = gh; - gi->bearing_x = tg->bearing_x; - gi->bearing_y = tg->bearing_y; - gi->advance = tg->advance; - - cx += gw + padding; - - free(tg->bitmap); - } - free(tmp_glyphs); - - S.atlas.tex_width = atlas_w; - S.atlas.tex_height = atlas_h; - /* Use font metrics for consistent cell size */ - S.atlas.cell_w = max_cell_w > 0 ? max_cell_w : font_pixel_size; - /* Use ascender + descender for proper line height */ - int line_height = (int)((S.ft_face->size->metrics.height + 63) >> 6); - S.atlas.cell_h = line_height > 0 ? line_height : (int)(font_pixel_size * 1.4); - if (max_cell_h > S.atlas.cell_h) S.atlas.cell_h = max_cell_h; - - /* Create GL texture */ - glGenTextures(1, &S.atlas.texture); - glBindTexture(GL_TEXTURE_2D, S.atlas.texture); - 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); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, atlas_w, atlas_h, - 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, atlas_data); - - free(atlas_data); - - fprintf(stderr, "Atlas: %dx%d, %d glyphs, cell=%dx%d\n", - atlas_w, atlas_h, S.atlas.glyph_count, - S.atlas.cell_w, S.atlas.cell_h); - - return true; -} - -static const glyph_info_t *render_find_glyph(uint32_t codepoint) { - for (int i = 0; i < S.atlas.glyph_count; i++) { - if (S.atlas.glyphs[i].codepoint == codepoint) - return &S.atlas.glyphs[i]; - } - /* Fallback to '?' or first glyph */ - for (int i = 0; i < S.atlas.glyph_count; i++) { - if (S.atlas.glyphs[i].codepoint == '?') - return &S.atlas.glyphs[i]; - } - return S.atlas.glyph_count > 0 ? &S.atlas.glyphs[0] : NULL; -} - -/* ── EGL setup ────────────────────────────────────────────────────── */ - -static bool render_init_egl(void) { - S.egl_display = eglGetDisplay((EGLNativeDisplayType)S.display); - if (S.egl_display == EGL_NO_DISPLAY) { - /* Try platform extension */ - PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = - (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT"); - if (eglGetPlatformDisplayEXT) { - S.egl_display = eglGetPlatformDisplayEXT( - EGL_PLATFORM_WAYLAND_KHR, S.display, NULL); - } - } - if (S.egl_display == EGL_NO_DISPLAY) { - fprintf(stderr, "Failed to get EGL display\n"); - return false; - } - - EGLint major, minor; - if (!eglInitialize(S.egl_display, &major, &minor)) { - fprintf(stderr, "Failed to initialize EGL\n"); - return false; - } - fprintf(stderr, "EGL %d.%d initialized\n", major, minor); - - if (!eglBindAPI(EGL_OPENGL_ES_API)) { - fprintf(stderr, "Failed to bind OpenGL ES API\n"); - return false; - } - - EGLint config_attribs[] = { - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, 8, - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_NONE - }; - - EGLConfig config; - EGLint num_configs; - if (!eglChooseConfig(S.egl_display, config_attribs, &config, 1, &num_configs) || - num_configs == 0) { - fprintf(stderr, "Failed to choose EGL config\n"); - return false; - } - - EGLint context_attribs[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - }; - - S.egl_context = eglCreateContext(S.egl_display, config, EGL_NO_CONTEXT, - context_attribs); - if (S.egl_context == EGL_NO_CONTEXT) { - fprintf(stderr, "Failed to create EGL context\n"); - return false; - } - - /* Create EGL window surface */ - S.egl_window = wl_egl_window_create(S.surface, - S.surface_width, S.surface_height); - if (!S.egl_window) { - fprintf(stderr, "Failed to create wl_egl_window\n"); - return false; - } - - S.egl_surface = eglCreateWindowSurface(S.egl_display, config, - (EGLNativeWindowType)S.egl_window, NULL); - if (S.egl_surface == EGL_NO_SURFACE) { - fprintf(stderr, "Failed to create EGL surface\n"); - return false; - } - - if (!eglMakeCurrent(S.egl_display, S.egl_surface, S.egl_surface, S.egl_context)) { - fprintf(stderr, "eglMakeCurrent failed\n"); - return false; - } - - /* Set swap interval to 0 for manual frame pacing */ - eglSwapInterval(S.egl_display, 0); - - fprintf(stderr, "EGL surface created: %dx%d\n", S.surface_width, S.surface_height); - return true; -} - -/* ── Shader compilation ───────────────────────────────────────────── */ - -static const char *vertex_shader_src = - "attribute vec2 a_pos;\n" - "attribute vec2 a_uv;\n" - "attribute vec4 a_color;\n" - "varying vec2 v_uv;\n" - "varying vec4 v_color;\n" - "uniform mat4 u_projection;\n" - "void main() {\n" - " gl_Position = u_projection * vec4(a_pos, 0.0, 1.0);\n" - " v_uv = a_uv;\n" - " v_color = a_color;\n" - "}\n"; - -static const char *fragment_shader_src = - "precision mediump float;\n" - "varying vec2 v_uv;\n" - "varying vec4 v_color;\n" - "uniform sampler2D u_atlas;\n" - "void main() {\n" - " float a = texture2D(u_atlas, v_uv).r;\n" - " if (a < 0.01) discard;\n" - " float scanline = 1.0 - 0.08 * mod(gl_FragCoord.y, 2.0);\n" - " a *= clamp(v_color.a, 0.0, 1.0);\n" - " gl_FragColor = vec4(v_color.rgb * scanline * a, a);\n" - "}\n"; - -static GLuint render_compile_shader(GLenum type, const char *src) { - GLuint shader = glCreateShader(type); - glShaderSource(shader, 1, &src, NULL); - glCompileShader(shader); - - GLint compiled; - glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); - if (!compiled) { - char log[512]; - glGetShaderInfoLog(shader, sizeof(log), NULL, log); - fprintf(stderr, "Shader compile error: %s\n", log); - glDeleteShader(shader); - return 0; - } - return shader; -} - -static bool render_init_shaders(void) { - GLuint vs = render_compile_shader(GL_VERTEX_SHADER, vertex_shader_src); - GLuint fs = render_compile_shader(GL_FRAGMENT_SHADER, fragment_shader_src); - if (!vs || !fs) return false; - - S.shader_program = glCreateProgram(); - glAttachShader(S.shader_program, vs); - glAttachShader(S.shader_program, fs); - glLinkProgram(S.shader_program); - - GLint linked; - glGetProgramiv(S.shader_program, GL_LINK_STATUS, &linked); - if (!linked) { - char log[512]; - glGetProgramInfoLog(S.shader_program, sizeof(log), NULL, log); - fprintf(stderr, "Shader link error: %s\n", log); - return false; - } - - glDeleteShader(vs); - glDeleteShader(fs); - - S.u_projection = glGetUniformLocation(S.shader_program, "u_projection"); - S.u_atlas = glGetUniformLocation(S.shader_program, "u_atlas"); - S.a_pos = glGetAttribLocation(S.shader_program, "a_pos"); - S.a_uv = glGetAttribLocation(S.shader_program, "a_uv"); - S.a_color = glGetAttribLocation(S.shader_program, "a_color"); - - /* Create VBO */ - glGenBuffers(1, &S.vbo); - - /* Allocate CPU-side vertex buffer */ - S.vertex_buf = malloc(MAX_QUADS * VERTS_PER_QUAD * sizeof(vertex_t)); - S.vertex_count = 0; - - fprintf(stderr, "Shaders compiled and linked\n"); - return true; -} - -static void render_set_projection(int width, int height) { - /* Orthographic projection: (0,0) top-left, (w,h) bottom-right */ - float proj[16] = { - 2.0f / width, 0.0f, 0.0f, 0.0f, - 0.0f, -2.0f / height, 0.0f, 0.0f, - 0.0f, 0.0f, -1.0f, 0.0f, - -1.0f, 1.0f, 0.0f, 1.0f, - }; - glUseProgram(S.shader_program); - glUniformMatrix4fv(S.u_projection, 1, GL_FALSE, proj); -} - -/* ── Vertex buffer helpers ────────────────────────────────────────── */ - -static void render_push_quad(float x0, float y0, float x1, float y1, - float u0, float v0, float u1, float v1, - float r, float g, float b, float a) { - if (S.vertex_count + VERTS_PER_QUAD > MAX_QUADS * VERTS_PER_QUAD) - return; - - vertex_t *v = &S.vertex_buf[S.vertex_count]; - - /* Triangle 1: top-left, top-right, bottom-left */ - v[0] = (vertex_t){ x0, y0, u0, v0, r, g, b, a }; - v[1] = (vertex_t){ x1, y0, u1, v0, r, g, b, a }; - v[2] = (vertex_t){ x0, y1, u0, v1, r, g, b, a }; - - /* Triangle 2: top-right, bottom-right, bottom-left */ - v[3] = (vertex_t){ x1, y0, u1, v0, r, g, b, a }; - v[4] = (vertex_t){ x1, y1, u1, v1, r, g, b, a }; - v[5] = (vertex_t){ x0, y1, u0, v1, r, g, b, a }; - - S.vertex_count += VERTS_PER_QUAD; -} - -static void render_flush_quads(void) { - if (S.vertex_count == 0) return; - - glBindBuffer(GL_ARRAY_BUFFER, S.vbo); - glBufferData(GL_ARRAY_BUFFER, S.vertex_count * sizeof(vertex_t), - S.vertex_buf, GL_STREAM_DRAW); - - glEnableVertexAttribArray(S.a_pos); - glEnableVertexAttribArray(S.a_uv); - glEnableVertexAttribArray(S.a_color); - - glVertexAttribPointer(S.a_pos, 2, GL_FLOAT, GL_FALSE, sizeof(vertex_t), - (void *)offsetof(vertex_t, x)); - glVertexAttribPointer(S.a_uv, 2, GL_FLOAT, GL_FALSE, sizeof(vertex_t), - (void *)offsetof(vertex_t, u)); - glVertexAttribPointer(S.a_color, 4, GL_FLOAT, GL_FALSE, sizeof(vertex_t), - (void *)offsetof(vertex_t, r)); - - glDrawArrays(GL_TRIANGLES, 0, S.vertex_count); - - glDisableVertexAttribArray(S.a_pos); - glDisableVertexAttribArray(S.a_uv); - glDisableVertexAttribArray(S.a_color); - - S.vertex_count = 0; -} - -/* ── Matrix engine ────────────────────────────────────────────────── */ - -static void render_tick_layer(depth_layer_t *layer); - -static void render_get_layer_properties(int layer_idx, int num_layers, - double *font_scale, double *opacity, - double *speed_scale, double *density) { - if (num_layers == 1) { - *font_scale = 1.0; - *opacity = 1.0; - *speed_scale = 1.0; - /* density from config, use midpoint of min/max */ - *density = (S.min_density + S.max_density) * 0.5; - return; - } - - double t = (double)layer_idx / (num_layers - 1); - *font_scale = LAYER_FAR_FONT_SCALE + t * (LAYER_NEAR_FONT_SCALE - LAYER_FAR_FONT_SCALE); - *opacity = LAYER_FAR_OPACITY + t * (LAYER_NEAR_OPACITY - LAYER_FAR_OPACITY); - *speed_scale = LAYER_FAR_SPEED_SCALE + t * (LAYER_NEAR_SPEED_SCALE - LAYER_FAR_SPEED_SCALE); - *density = S.layer_far_density + t * (S.layer_near_density - S.layer_far_density); -} - -static bool render_init_layer(depth_layer_t *layer, int width, int height, - int layer_idx) { - render_get_layer_properties(layer_idx, S.num_depth_layers, - &layer->font_scale, &layer->opacity, - &layer->speed_scale, &layer->density); - - /* Cell size: scale the atlas cell size by font_scale */ - layer->cell_w = (int)(S.atlas.cell_w * layer->font_scale + 0.5); - layer->cell_h = (int)(S.atlas.cell_h * layer->font_scale + 0.5); - if (layer->cell_w < 4) layer->cell_w = 4; - if (layer->cell_h < 6) layer->cell_h = 6; - - layer->cols = width / layer->cell_w; - layer->rows = height / layer->cell_h; - if (layer->cols < 1) layer->cols = 1; - if (layer->rows < 1) layer->rows = 1; - - free(layer->columns); - free(layer->cells); - layer->columns = calloc(layer->cols, sizeof(render_column_t)); - layer->cells = calloc(layer->rows * layer->cols, sizeof(render_cell_t)); - - if (!layer->columns || !layer->cells) return false; - - double min_spd = S.min_speed * layer->speed_scale; - double max_spd = S.max_speed * layer->speed_scale; - double avg_spd = S.avg_speed * layer->speed_scale; - - for (int c = 0; c < layer->cols; c++) { - render_column_t *col = &layer->columns[c]; - col->x_offset = (render_randf() - 0.5f) * layer->cell_w * 0.5f; - col->skip = (render_randf() < 0.15); - col->last_head_row = -1; - col->head_hold_frames = 0; - if (render_randf() < layer->density) { - col->active = true; - col->color = render_trail_color(); - col->speed = render_rand_speed(min_spd, max_spd, avg_spd); - /* Spread start positions across the full screen height - so columns appear mid-fall from the start */ - col->head_pos = render_randf() * layer->rows * 2 - layer->rows; - col->trail_len = render_randi(S.min_trail, S.max_trail); - } else { - col->active = false; - col->respawn_timer = render_randi(1, RESPAWN_DELAY_MAX); - } - } - - /* Pre-populate cells for columns that are already mid-screen */ - for (int i = 0; i < layer->rows * layer->cols; i++) { - layer->cells[i].codepoint = render_random_codepoint(); - layer->cells[i].age = 9999; - layer->cells[i].mirrored = (render_randf() < 0.20); - layer->cells[i].white_mix = (float)S.head_white_max; - } - for (int c = 0; c < layer->cols; c++) { - render_column_t *col = &layer->columns[c]; - if (!col->active || col->skip) continue; - int head = (int)col->head_pos; - /* Fill trail behind the head with aged characters */ - for (int t = 0; t < col->trail_len && head - t >= 0; t++) { - int r = head - t; - if (r < layer->rows) { - render_cell_t *cell = render_cell_at_layer(layer, r, c); - cell->codepoint = render_random_codepoint(); - cell->age = t; - cell->mirrored = (render_randf() < 0.20); - cell->white_mix = render_head_white_mix(); - } - } - } - return true; -} - -static bool render_init_matrix(int width, int height) { - S.surface_width = width; - S.surface_height = height; - - for (int l = 0; l < S.num_depth_layers; l++) { - if (!render_init_layer(&S.layers[l], width, height, l)) return false; - } - - /* Pre-run the simulation so first frame looks like it's been - running for a while — no visible startup sweep */ - int warmup = 60 + rand() % 120; /* 2-6 seconds at 30fps */ - for (int f = 0; f < warmup; f++) { - for (int l = 0; l < S.num_depth_layers; l++) { - render_tick_layer(&S.layers[l]); - } - } - return true; -} - -static void render_tick_layer(depth_layer_t *layer) { - double min_spd = S.min_speed * layer->speed_scale; - double max_spd = S.max_speed * layer->speed_scale; - double avg_spd = S.avg_speed * layer->speed_scale; - - for (int c = 0; c < layer->cols; c++) { - render_column_t *col = &layer->columns[c]; - if (col->skip) continue; - - if (!col->active) { - if (--col->respawn_timer <= 0) { - col->active = true; - col->color = render_trail_color(); - col->speed = render_rand_speed(min_spd, max_spd, avg_spd); - col->head_pos = -1.0; - col->trail_len = render_randi(S.min_trail, S.max_trail); - col->last_head_row = -1; - col->head_hold_frames = 0; - } - continue; - } - - col->head_pos += col->speed * render_column_speed(c * layer->cell_w, (int)fmax(0, fmin(S.surface_height - 1, col->head_pos * layer->cell_h))); - int head_row = (int)col->head_pos; - bool head_visible = head_row >= 0 && head_row < layer->rows; - - if (head_visible) { - render_cell_t *cell = render_cell_at_layer(layer, head_row, c); - if (head_row != col->last_head_row) { - cell->codepoint = render_random_codepoint(); - cell->mirrored = (render_randf() < 0.20); - cell->white_mix = render_head_white_mix(); - col->last_head_row = head_row; - col->head_hold_frames = 0; - } else { - col->head_hold_frames++; - if (col->head_hold_frames >= S.head_hold_frames && - render_randf() < S.head_mutation_chance) { - cell->codepoint = render_random_codepoint(); - cell->mirrored = (render_randf() < 0.20); - cell->white_mix = render_head_white_mix(); - col->head_hold_frames = 0; - } - } - cell->age = 0; - } - - /* Flash columns: ~0.3% chance per frame of starting a flash */ - if (col->flash_timer > 0) { - col->flash_timer--; - } else if (render_randf() < 0.003) { - col->flash_timer = 6 + rand() % 5; /* 6-10 frames */ - col->flash_brightness = 1.8f; - } - - for (int r = 0; r < layer->rows; r++) { - render_cell_t *cell = render_cell_at_layer(layer, r, c); - if (head_visible && r == head_row) - continue; - if (cell->age < 9999) { - cell->age++; - } - if (cell->age > 0 && cell->age < col->trail_len && render_randf() < MUTATION_CHANCE) { - cell->codepoint = render_random_codepoint(); - } - } - - if (head_row > layer->rows + col->trail_len) { - col->active = false; - col->respawn_timer = render_randi(1, RESPAWN_DELAY_MAX); - col->last_head_row = -1; - col->head_hold_frames = 0; - for (int r = 0; r < layer->rows; r++) { - render_cell_at_layer(layer, r, c)->age = 9999; - } - } - } -} - -static double render_simulation_speed = 1; -static void render_tick_matrix(void) { - render_effect_tick(); - S.simulation_credit += render_simulation_speed; - while (S.simulation_credit >= 1) { - for (int l = 0; l < S.num_depth_layers; l++) render_tick_layer(&S.layers[l]); - S.simulation_credit -= 1; - } -} - -/* ── Rendering ────────────────────────────────────────────────────── */ - -static void render_frame(void) { - int w = S.surface_width; - int h = S.surface_height; - - glViewport(0, 0, w, h); - /* glClearColor/glClear is handled by the phase logic in the main loop */ - - glEnable(GL_BLEND); - glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); - - glUseProgram(S.shader_program); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, S.atlas.texture); - glUniform1i(S.u_atlas, 0); - - float fade = S.fade_opacity; - - /* Render layers back-to-front (0=far, N-1=near) */ - for (int l = 0; l < S.num_depth_layers; l++) { - depth_layer_t *layer = &S.layers[l]; - /* Depth and trail age control intensity, not surface transparency. - * Otherwise windows remain visible through the bodies of the glyphs, - * making the rain appear behind their text and images. Only glyph - * coverage and the overall transition affect stroke alpha. */ - float intensity = (float)layer->opacity; - float scale = (float)layer->font_scale; - - S.vertex_count = 0; - - for (int c = 0; c < layer->cols; c++) { - render_column_t *col = &layer->columns[c]; - if (col->skip) continue; - - for (int r = 0; r < layer->rows; r++) { - render_cell_t *cell = render_cell_at_layer(layer, r, c); - - if (cell->age >= 9999) continue; - - float brightness; - bool is_head = false; - - if (cell->age == 0 && col->active) { - is_head = true; - brightness = 1.0f; - } else if (cell->age < col->trail_len) { - float t = 1.0f - (float)cell->age / col->trail_len; - brightness = t * t; - if (brightness < 0.05f) continue; - } else { - continue; - } - - /* Flash column boost */ - if (col->flash_timer > 0) { - float flash_t = (float)col->flash_timer / 10.0f; - float boost = 1.0f + (col->flash_brightness - 1.0f) * flash_t; - brightness *= boost; - if (brightness > 1.0f && !is_head) brightness = 1.0f; - } - - const glyph_info_t *gi = render_find_glyph(cell->codepoint); - if (!gi) continue; - - /* Position the glyph within the cell */ - float cell_x = (float)(c * layer->cell_w) + layer->columns[c].x_offset; - float cell_y = (float)(r * layer->cell_h); - - /* Scale glyph dimensions by layer font_scale relative to atlas */ - float gw = gi->width * scale; - float gh = gi->height * scale; - float bx = gi->bearing_x * scale; - float by = gi->bearing_y * scale; - - /* Center glyph in cell: x based on bearing, y based on bearing */ - float x0 = cell_x + bx; - float y0 = cell_y + (layer->cell_h - by); - float x1 = x0 + gw; - float y1 = y0 + gh; - - /* UV coordinates — possibly mirrored horizontally */ - float qu0 = cell->mirrored ? gi->u1 : gi->u0; - float qu1 = cell->mirrored ? gi->u0 : gi->u1; - - /* Determine color: white-to-trail gradient for head and near-head */ - float live_brightness; - rgb_t tc = render_column_color(c * layer->cell_w, r * layer->cell_h, col->color, &live_brightness); - float cr, cg, cb; - /* Keep the monitor's adaptive hue and contrast at the head too. */ - float white_mix = S.adaptive_color ? 0.0f : cell->white_mix; - if (white_mix < 0.0f) white_mix = 0.0f; - if (white_mix > 1.0f) white_mix = 1.0f; - if (is_head) { - cr = white_mix + (1.0f - white_mix) * tc.r; - cg = white_mix + (1.0f - white_mix) * tc.g; - cb = white_mix + (1.0f - white_mix) * tc.b; - } else if (cell->age == 1) { - white_mix *= 0.7f; - cr = white_mix + (1.0f - white_mix) * tc.r; - cg = white_mix + (1.0f - white_mix) * tc.g; - cb = white_mix + (1.0f - white_mix) * tc.b; - } else if (cell->age == 2) { - white_mix *= 0.4f; - cr = white_mix + (1.0f - white_mix) * tc.r; - cg = white_mix + (1.0f - white_mix) * tc.g; - cb = white_mix + (1.0f - white_mix) * tc.b; - } else { - /* pure trail color */ - cr = tc.r; cg = tc.g; cb = tc.b; - } - - if (is_head) { - /* Enhanced "type-in" glow: 50% larger pad, stronger alpha */ - float glow_pad = HEAD_GLOW_EXTRA * scale * 1.5f; - if (glow_pad < 1.5f) glow_pad = 1.5f; - float ga = 0.6f * intensity * fade; - render_push_quad(x0 - glow_pad, y0 - glow_pad, - x1 + glow_pad, y1 + glow_pad, - qu0, gi->v0, qu1, gi->v1, - cr, cg, cb, ga); - - /* Solid glyph body; antialiased edges and phase fades. */ - render_push_quad(x0, y0, x1, y1, - qu0, gi->v0, qu1, gi->v1, - fminf(1, cr * intensity * live_brightness), fminf(1, cg * intensity * live_brightness), fminf(1, cb * intensity * live_brightness), fade); - } else { - float b = fminf(1, brightness * intensity * live_brightness); - render_push_quad(x0, y0, x1, y1, - qu0, gi->v0, qu1, gi->v1, - cr * b, cg * b, cb * b, fade); - } - } - } - - render_flush_quads(); - } -} - -/* ── Wayland output tracking ──────────────────────────────────────── */ - -struct render_output_info { - struct wl_output *output; - struct zxdg_output_v1 *xdg_output; - char name[128]; - int width, height; - int scale; - struct wl_list link; -}; - -static struct wl_list render_output_list; - -static void render_xdg_output_handle_name(void *data, - struct zxdg_output_v1 *xdg_output, const char *name) { - (void)xdg_output; - struct render_output_info *info = data; - snprintf(info->name, sizeof(info->name), "%s", name); - - if (strcmp(name, S.target.desktop) == 0) { - S.target_output = info->output; - S.output_found = true; - S.output_width = info->width; - S.output_height = info->height; - S.output_scale = info->scale > 0 ? info->scale : 1; - } -} - -static void render_xdg_output_handle_logical_position(void *data, - struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) { - (void)data; (void)xdg_output; (void)x; (void)y; -} - -static void render_xdg_output_handle_logical_size(void *data, - struct zxdg_output_v1 *xdg_output, int32_t w, int32_t h) { - (void)xdg_output; - struct render_output_info *info = data; - info->width = w; - info->height = h; - if (strcmp(info->name, S.target.desktop) == 0) { - S.output_width = w; - S.output_height = h; - } -} - -static void render_xdg_output_handle_done(void *data, - struct zxdg_output_v1 *xdg_output) { - (void)data; (void)xdg_output; -} - -static void render_xdg_output_handle_description(void *data, - struct zxdg_output_v1 *xdg_output, const char *desc) { - (void)data; (void)xdg_output; (void)desc; -} - -static const struct zxdg_output_v1_listener render_xdg_output_listener = { - .logical_position = render_xdg_output_handle_logical_position, - .logical_size = render_xdg_output_handle_logical_size, - .done = render_xdg_output_handle_done, - .name = render_xdg_output_handle_name, - .description = render_xdg_output_handle_description, -}; - -static void render_output_handle_geometry(void *data, struct wl_output *output, - int32_t x, int32_t y, int32_t pw, int32_t ph, int32_t subpixel, - const char *make, const char *model, int32_t transform) { - (void)data; (void)output; (void)x; (void)y; - (void)pw; (void)ph; (void)subpixel; - (void)make; (void)model; (void)transform; -} - -static void render_output_handle_mode(void *data, struct wl_output *output, - uint32_t flags, int32_t width, int32_t height, int32_t refresh) { - (void)output; (void)refresh; - struct render_output_info *info = data; - if (flags & WL_OUTPUT_MODE_CURRENT) { - info->width = width; - info->height = height; - } -} - -static void render_output_handle_scale(void *data, struct wl_output *output, - int32_t factor) { - (void)output; - struct render_output_info *info = data; - info->scale = factor; -} - -static void render_output_handle_done(void *data, struct wl_output *output) { - (void)data; (void)output; -} - -static void render_output_handle_name(void *data, struct wl_output *output, - const char *name) { - (void)output; - struct render_output_info *info = data; - snprintf(info->name, sizeof(info->name), "%s", name); - - if (strcmp(name, S.target.desktop) == 0) { - S.target_output = info->output; - S.output_found = true; - S.output_width = info->width; - S.output_height = info->height; - S.output_scale = info->scale > 0 ? info->scale : 1; - } -} - -static void render_output_handle_description(void *data, struct wl_output *output, - const char *desc) { - (void)data; (void)output; (void)desc; -} - -static const struct wl_output_listener render_output_listener = { - .geometry = render_output_handle_geometry, - .mode = render_output_handle_mode, - .done = render_output_handle_done, - .scale = render_output_handle_scale, - .name = render_output_handle_name, - .description = render_output_handle_description, -}; - -/* ── Layer surface events ─────────────────────────────────────────── */ - -static void render_layer_surface_configure(void *data, - struct zwlr_layer_surface_v1 *surface, - uint32_t serial, uint32_t width, uint32_t height) { - (void)data; - S.configure_serial = serial; - S.surface_width = width ? (int)width : S.output_width; - S.surface_height = height ? (int)height : S.output_height; - S.configured = true; - S.surface_dirty = true; - - zwlr_layer_surface_v1_ack_configure(surface, serial); -} - -static void render_layer_surface_closed(void *data, - struct zwlr_layer_surface_v1 *surface) { - (void)data; (void)surface; - S.closed = true; -} - -static const struct zwlr_layer_surface_v1_listener render_layer_surface_listener = { - .configure = render_layer_surface_configure, - .closed = render_layer_surface_closed, -}; - -/* ── Registry ─────────────────────────────────────────────────────── */ - -static void render_registry_handle_global(void *data, struct wl_registry *registry, - uint32_t name, const char *interface, uint32_t version) { - (void)data; - - if (strcmp(interface, wl_compositor_interface.name) == 0) { - S.compositor = wl_registry_bind(registry, name, - &wl_compositor_interface, 4); - } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { - S.layer_shell = wl_registry_bind(registry, name, - &zwlr_layer_shell_v1_interface, version < 3 ? version : 3); - } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { - S.xdg_output_manager = wl_registry_bind(registry, name, - &zxdg_output_manager_v1_interface, version < 3 ? version : 3); - } else if (strcmp(interface, wl_output_interface.name) == 0) { - struct render_output_info *info = calloc(1, sizeof(*info)); - info->output = wl_registry_bind(registry, name, - &wl_output_interface, version < 4 ? version : 4); - info->scale = 1; - wl_output_add_listener(info->output, &render_output_listener, info); - wl_list_insert(&render_output_list, &info->link); - } -} - -static void render_registry_handle_global_remove(void *data, - struct wl_registry *registry, uint32_t name) { - (void)data; (void)registry; (void)name; -} - -static const struct wl_registry_listener render_registry_listener = { - .global = render_registry_handle_global, - .global_remove = render_registry_handle_global_remove, -}; - -/* ── Renderer signal handling ─────────────────────────────────────── */ - -static void render_sig_handler(int sig) { - (void)sig; - S.stop = 1; -} - -static void render_sigusr1_handler(int sig) { - (void)sig; - S.fade_out_requested = 1; -} - -static void render_black_handler(int sig) { (void)sig; S.black_requested = 1; } - -static void render_report(void) { - int power = S.power_fd >= 0; - if (S.report_fd < 0 || (S.reported_phase == (int)S.phase && S.reported_power == power && - S.reported_revision == S.applied_revision && S.reported_error == S.live_error)) return; - dr_report_packet p = {.magic=DR_LIVE_MAGIC, .kind=DR_REPORT, .revision=S.applied_revision, - .phase=S.phase, .power=power, .error=S.live_error, .font_size=S.font_size, .depth_layers=S.num_depth_layers}; - if (send(S.report_fd, &p, sizeof(p), MSG_DONTWAIT | MSG_NOSIGNAL) == sizeof(p)) { - S.reported_phase = S.phase; S.reported_power = power; S.reported_revision = S.applied_revision; S.reported_error = S.live_error; - } -} - -static void render_adaptive_handler(int sig) { - (void)sig; - S.adaptive_reload = 1; -} - -/* A black surface needs no animation. Atomically unblock wake signals while - * waiting so an activity signal cannot be lost between checking and sleeping. */ -static void render_dispatch_events(void) { - render_live_receive(); - sigset_t wake_signals, previous_mask; - sigemptyset(&wake_signals); - sigaddset(&wake_signals, SIGINT); - sigaddset(&wake_signals, SIGTERM); - sigaddset(&wake_signals, SIGUSR1); - sigaddset(&wake_signals, SIGRTMIN + 1); - if (S.adaptive_color) sigaddset(&wake_signals, SIGRTMIN); - sigprocmask(SIG_BLOCK, &wake_signals, &previous_mask); - - if (wl_display_prepare_read(S.display) == 0) { - struct pollfd pfds[2] = {{.fd=wl_display_get_fd(S.display), .events=POLLIN}, {.fd=S.report_fd, .events=POLLIN}}; -#define pfd pfds[0] - if (wl_display_flush(S.display) < 0) { - if (errno == EAGAIN) pfd.events |= POLLOUT; - else S.stop = 1; - } - bool wait = S.phase == PHASE_BLACK && !S.surface_dirty && - !S.stop && !S.closed && !S.fade_out_requested && !S.adaptive_reload && !S.black_requested; - struct timespec immediate = {0}; - int ready = ppoll(pfds, 2, wait ? NULL : &immediate, &previous_mask); - int poll_error = errno; - if (ready > 0 && (pfd.revents & POLLIN)) { - if (wl_display_read_events(S.display) < 0) S.stop = 1; - } else { - wl_display_cancel_read(S.display); - } - if ((ready < 0 && poll_error != EINTR) || - (ready > 0 && (pfd.revents & (POLLERR | POLLHUP | POLLNVAL)))) - S.stop = 1; - } - sigprocmask(SIG_SETMASK, &previous_mask, NULL); - if (wl_display_dispatch_pending(S.display) < 0) S.stop = 1; - render_live_receive(); -#undef pfd -} - -static void render_note_black_background(void) { - if (!S.background_black) { - S.background_black = true; - S.black_start_time = render_monotime(); - } -} - -static bool is_adaptive_color(const char *name) { - return !strcasecmp(name, "adaptive") || !strcasecmp(name, "adaptive-light") || - !strcasecmp(name, "adaptive-dark"); -} - -static void render_load_adaptive_snapshot(void) { - S.adaptive_reload = 0; - const char *runtime = getenv("XDG_RUNTIME_DIR"); - if (!runtime || !runtime[0]) return; - char path[512]; - snprintf(path, sizeof(path), "%s/digitalrain/palette.json", runtime); - FILE *f = fopen(path, "r"); - if (!f) return; - fseek(f, 0, SEEK_END); - long size = ftell(f); - rewind(f); - if (size <= 0 || size > 65536) { fclose(f); return; } - char *text = malloc((size_t)size + 1); - if (!text) { fclose(f); return; } - if (fread(text, 1, (size_t)size, f) != (size_t)size) { - free(text); fclose(f); return; - } - text[size] = '\0'; - fclose(f); - struct json_object *root = json_tokener_parse(text); - free(text); - struct json_object *value = NULL; - if (!root || !json_object_object_get_ex(root, S.target_name, &value)) { - if (root) json_object_put(root); - return; - } - struct json_object *desktop = NULL, *black = NULL; - if (json_object_object_get_ex(value, "color", &desktop) && - json_object_object_get_ex(value, "black-color", &black) && - json_object_is_type(desktop, json_type_string) && - json_object_is_type(black, json_type_string)) { - S.adaptive_desktop_color = render_parse_hex(json_object_get_string(desktop)); - S.adaptive_black_color = render_parse_hex(json_object_get_string(black)); - } - json_object_put(root); -} - -static void ensure_wayland_display(void) { - const char *display = getenv("WAYLAND_DISPLAY"); - const char *runtime_dir = getenv("XDG_RUNTIME_DIR"); - - if ((display && display[0] != '\0') || !runtime_dir || runtime_dir[0] == '\0') - return; - - DIR *dir = opendir(runtime_dir); - if (!dir) - return; - - struct dirent *ent; - while ((ent = readdir(dir)) != NULL) { - if (strncmp(ent->d_name, "wayland-", 8) != 0) - continue; - - char path[512]; - struct stat st; - snprintf(path, sizeof(path), "%s/%s", runtime_dir, ent->d_name); - if (stat(path, &st) == 0 && S_ISSOCK(st.st_mode)) { - setenv("WAYLAND_DISPLAY", ent->d_name, 0); - break; - } - } - - closedir(dir); -} - -/* ── Renderer main ────────────────────────────────────────────────── */ - -/* Reuse the manager's Wayfire transport, retaining this one connection until - * wake-up. The compositor releases the hide even if this process is killed. */ -static int mgr_ipc_connect(void); -static int mgr_ipc_send_method(int fd, const char *method, const char *data_json); -static int mgr_ipc_recv(int fd, char **out_payload); - -static bool render_desktop_request(int fd, const char *method, const char *data) { - if (mgr_ipc_send_method(fd, method, data) < 0) return false; - char *text = NULL; - if (mgr_ipc_recv(fd, &text) < 0) return false; - struct json_object *response = json_tokener_parse(text), *result = NULL; - free(text); - bool ok = response && json_object_object_get_ex(response, "result", &result) && - json_object_is_type(result, json_type_string) && - strcmp(json_object_get_string(result), "ok") == 0; - if (response) json_object_put(response); - return ok; -} - -static void render_restore_power(void) { - if (S.power_fd < 0) return; - /* Wait for the restore before drawing: an off output may not send frames. */ - render_desktop_request(S.power_fd, "digitalrain/power_on", "{}"); - close(S.power_fd); - S.power_fd = -1; - S.surface_dirty = true; -} - -static void render_power_off(void) { - if (!S.power_off || S.power_attempted || S.fade_out_requested || S.stop) return; - S.power_attempted = true; - int fd = mgr_ipc_connect(); - if (fd >= 0) { - struct json_object *data = json_object_new_object(); - json_object_object_add(data, "output", json_object_new_string(S.target_name)); - bool ok = render_desktop_request(fd, "digitalrain/power_off", - json_object_to_json_string_ext(data, JSON_C_TO_STRING_PLAIN)); - json_object_put(data); - if (ok) { - S.power_fd = fd; - fprintf(stderr, "[%s] Monitor powered off until activity\n", S.target_name); - return; - } - close(fd); - } - fprintf(stderr, "[%s] Monitor power-off unavailable; enable digitalrain-power in Wayfire; retaining black output\n", - S.target_name); -} - -static void render_release_desktop(void) { - if (S.desktop_fd >= 0) { - close(S.desktop_fd); - S.desktop_fd = -1; - } -} - -static void render_resume_desktop(void) { - if (S.desktop_fd < 0 || S.desktop_resuming) return; - S.desktop_resuming = true; - if (!render_desktop_request(S.desktop_fd, "digitalrain/show_desktop", "{}")) - render_release_desktop(); -} - -static void render_hide_desktop(void) { - if (!S.transparent || !S.hide_desktop) return; - int fd = mgr_ipc_connect(); - if (fd < 0) goto unavailable; - struct timeval timeout = {.tv_sec = 2}; - setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); - setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); - struct json_object *data = json_object_new_object(); - json_object_object_add(data, "output", json_object_new_string(S.target_name)); - json_object_object_add(data, "fade_ms", json_object_new_int(S.fade_frames * 1000 / TARGET_FPS)); - bool ok = render_desktop_request(fd, "digitalrain/hide_desktop", - json_object_to_json_string_ext(data, JSON_C_TO_STRING_PLAIN)); - json_object_put(data); - if (ok) { - S.desktop_fd = fd; - return; - } - close(fd); -unavailable: - fprintf(stderr, "HIDE_DESKTOP unavailable: enable Wayfire's digitalrain-desktop plugin; continuing with desktop visible\n"); -} - -#include "digitalrain-live-render.h" - -static int renderer_main(int argc, char **argv) { - S.report_fd = S.reported_phase = S.reported_power = -1; - signal(SIGRTMIN + 1, render_black_handler); - /* Defaults */ - S.target_name[0] = '\0'; - S.font_size = DEFAULT_FONT_SIZE; - S.base_color = (rgb_t){ 0.0f, 1.0f, 0.0f }; - S.output_scale = 1; - S.min_speed = DEFAULT_MIN_SPEED; - S.max_speed = DEFAULT_MAX_SPEED; - S.avg_speed = DEFAULT_AVG_SPEED; - S.min_trail = DEFAULT_MIN_TRAIL; - S.max_trail = DEFAULT_MAX_TRAIL; - S.min_density = DEFAULT_MIN_DENSITY; - S.max_density = DEFAULT_MAX_DENSITY; - S.layer_near_density = LAYER_NEAR_DENSITY; - S.layer_far_density = LAYER_FAR_DENSITY; - S.head_hold_frames = DEFAULT_HEAD_HOLD_FRAMES; - S.head_mutation_chance = DEFAULT_HEAD_MUTATION_CHANCE; - S.head_white_min = DEFAULT_HEAD_WHITE_MIN; - S.head_white_max = DEFAULT_HEAD_WHITE_MAX; - S.num_depth_layers = DEFAULT_DEPTH_LAYERS; - S.fade_frames = DEFAULT_FADE_FRAMES; - S.transparent_timeout = 0.0; - S.black_rain_timeout = 0.0; - S.transparent = false; - S.hide_desktop = false; - S.desktop_fd = -1; - S.power_fd = -1; - S.desktop_resuming = false; - S.adaptive_reload = 0; - - const char *color_name = "green"; - - /* Parse args (skip --render which was already consumed) */ - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "--render") == 0) { - continue; /* already handled */ - } else if (strcmp(argv[i], "--ambient") == 0) { - S.ambient = true; - } else if (strcmp(argv[i], "--report-fd") == 0 && i + 1 < argc) { - S.report_fd = atoi(argv[++i]); - } else if (strcmp(argv[i], "--output") == 0 && i + 1 < argc) { - snprintf(S.target_name, sizeof(S.target_name), "%s", argv[++i]); - } else if (strcmp(argv[i], "--color") == 0 && i + 1 < argc) { - color_name = argv[++i]; - } else if (strcmp(argv[i], "--font-size") == 0 && i + 1 < argc) { - S.font_size = atoi(argv[++i]); - if (S.font_size < MIN_FONT_SIZE) S.font_size = MIN_FONT_SIZE; - if (S.font_size > MAX_FONT_SIZE) S.font_size = MAX_FONT_SIZE; - } else if (strcmp(argv[i], "--min-speed") == 0 && i + 1 < argc) { - S.min_speed = atof(argv[++i]); - } else if (strcmp(argv[i], "--max-speed") == 0 && i + 1 < argc) { - S.max_speed = atof(argv[++i]); - } else if (strcmp(argv[i], "--avg-speed") == 0 && i + 1 < argc) { - S.avg_speed = atof(argv[++i]); - } else if (strcmp(argv[i], "--min-trail") == 0 && i + 1 < argc) { - S.min_trail = atoi(argv[++i]); - } else if (strcmp(argv[i], "--max-trail") == 0 && i + 1 < argc) { - S.max_trail = atoi(argv[++i]); - } else if (strcmp(argv[i], "--min-density") == 0 && i + 1 < argc) { - S.min_density = atof(argv[++i]); - } else if (strcmp(argv[i], "--max-density") == 0 && i + 1 < argc) { - S.max_density = atof(argv[++i]); - } else if (strcmp(argv[i], "--speed") == 0 && i + 1 < argc) { - double mult = atof(argv[++i]); - if (mult < 0.1) mult = 0.1; - if (mult > 5.0) mult = 5.0; - S.min_speed *= mult; - S.max_speed *= mult; - } else if (strcmp(argv[i], "--depth-layers") == 0 && i + 1 < argc) { - S.num_depth_layers = atoi(argv[++i]); - if (S.num_depth_layers < MIN_DEPTH_LAYERS) - S.num_depth_layers = MIN_DEPTH_LAYERS; - if (S.num_depth_layers > MAX_DEPTH_LAYERS) - S.num_depth_layers = MAX_DEPTH_LAYERS; - } else if (strcmp(argv[i], "--layer-near-density") == 0 && i + 1 < argc) { - S.layer_near_density = atof(argv[++i]); - } else if (strcmp(argv[i], "--layer-far-density") == 0 && i + 1 < argc) { - S.layer_far_density = atof(argv[++i]); - } else if (strcmp(argv[i], "--head-hold-frames") == 0 && i + 1 < argc) { - S.head_hold_frames = atoi(argv[++i]); - if (S.head_hold_frames < 0) S.head_hold_frames = 0; - if (S.head_hold_frames > 120) S.head_hold_frames = 120; - } else if (strcmp(argv[i], "--head-mutation-chance") == 0 && i + 1 < argc) { - S.head_mutation_chance = atof(argv[++i]); - if (S.head_mutation_chance < 0.0) S.head_mutation_chance = 0.0; - if (S.head_mutation_chance > 1.0) S.head_mutation_chance = 1.0; - } else if (strcmp(argv[i], "--head-white-min") == 0 && i + 1 < argc) { - S.head_white_min = atof(argv[++i]); - } else if (strcmp(argv[i], "--head-white-max") == 0 && i + 1 < argc) { - S.head_white_max = atof(argv[++i]); - } else if (strcmp(argv[i], "--fade-frames") == 0 && i + 1 < argc) { - S.fade_frames = atoi(argv[++i]); - if (S.fade_frames < 1) S.fade_frames = 1; - if (S.fade_frames > 300) S.fade_frames = 300; - } else if (strcmp(argv[i], "--transparent") == 0) { - S.transparent = true; - } else if (strcmp(argv[i], "--hide-desktop") == 0) { - S.hide_desktop = true; - } else if (strcmp(argv[i], "--power-off") == 0 && i + 1 < argc) { - S.power_off = atoi(argv[++i]) != 0; - } else if (strcmp(argv[i], "--transparent-timeout") == 0 && i + 1 < argc) { - S.transparent_timeout = atof(argv[++i]); - if (S.transparent_timeout < 0.0) S.transparent_timeout = 0.0; - } else if (strcmp(argv[i], "--black-rain-timeout") == 0 && i + 1 < argc) { - S.black_rain_timeout = atof(argv[++i]); - if (S.black_rain_timeout < 0.0) S.black_rain_timeout = 0.0; - } else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { - fprintf(stderr, - "Usage: %s --render --output <name> [--color <color>] " - "[--font-size <size>]\n" - " [--min-speed <val>] [--max-speed <val>] " - "[--min-trail <val>] [--max-trail <val>]\n" - " [--min-density <val>] [--max-density <val>]\n" - " [--head-hold-frames <frames>] " - "[--head-mutation-chance <0..1>]\n" - " [--head-white-min <0..1>] [--head-white-max <0..1>]\n" - " [--speed <multiplier>] [--depth-layers <N>]\n" - " [--transparent]\n" - " [--hide-desktop]\n" - " [--transparent-timeout <seconds>]\n" - " [--black-rain-timeout <seconds>]\n" - " [--power-off <0|1>] (after the black rain timer)\n" - "\n" - "Colors: green, red, blue, white, yellow, cyan, magenta, adaptive, random\n" - "--speed scales both min/max speed (e.g. --speed 2.0 = double speed)\n" - "--depth-layers: number of parallax layers (1-%d, default %d)\n" - "--transparent: keep the desktop visible behind the matrix rain\n" - "--hide-desktop: with --transparent, hide apps/panels and show wallpaper only (Wayfire)\n" - "--black-rain-timeout: fade out rain after N seconds on black; 0 disables\n" - "Default font-size: %d\n" - "Speed range: %.1f - %.1f Trail range: %d - %d " - "Density range: %.2f - %.2f\n", - argv[0], - MAX_DEPTH_LAYERS, DEFAULT_DEPTH_LAYERS, - DEFAULT_FONT_SIZE, - DEFAULT_MIN_SPEED, DEFAULT_MAX_SPEED, - DEFAULT_MIN_TRAIL, DEFAULT_MAX_TRAIL, - DEFAULT_MIN_DENSITY, DEFAULT_MAX_DENSITY); - return 0; - } else { - fprintf(stderr, "Unknown option: %s\n", argv[i]); - return 1; - } - } - - /* Clamp values */ - if (S.min_speed < 0.001) S.min_speed = 0.001; - if (S.max_speed < S.min_speed) S.max_speed = S.min_speed; - if (S.min_trail < 1) S.min_trail = 1; - if (S.max_trail < S.min_trail) S.max_trail = S.min_trail; - if (S.min_density < 0) S.min_density = 0; - if (S.min_density > 1.0) S.min_density = 1.0; - if (S.max_density < S.min_density) S.max_density = S.min_density; - if (S.max_density > 1.0) S.max_density = 1.0; - if (S.head_white_min < 0.0) S.head_white_min = 0.0; - if (S.head_white_min > 1.0) S.head_white_min = 1.0; - if (S.head_white_max < 0.0) S.head_white_max = 0.0; - if (S.head_white_max > 1.0) S.head_white_max = 1.0; - if (S.head_white_max < S.head_white_min) { - double tmp = S.head_white_min; - S.head_white_min = S.head_white_max; - S.head_white_max = tmp; - } - - if (S.target_name[0] == '\0') { - fprintf(stderr, "Error: --output is required in render mode\n"); - return 1; - } - - srand((unsigned)time(NULL) ^ (unsigned)getpid()); - - S.base_color = render_lookup_color(color_name); - bool adaptive_color = is_adaptive_color(color_name); - S.adaptive_color = adaptive_color; - if (adaptive_color) { - S.base_color = (rgb_t){ 1.0f, 1.0f, 1.0f }; - S.adaptive_desktop_color = S.adaptive_black_color = S.base_color; - render_load_adaptive_snapshot(); - S.base_color = S.transparent ? S.adaptive_desktop_color : S.adaptive_black_color; - } - if (strcasecmp(color_name, "random_trail_named") == 0) - S.color_mode = COLOR_RANDOM; - else if (strcasecmp(color_name, "random_trail_hex") == 0) - S.color_mode = COLOR_RANDOM_HEX; - else - S.color_mode = COLOR_FIXED; - - /* Signal handlers */ - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = render_sig_handler; - sigemptyset(&sa.sa_mask); - sigaction(SIGINT, &sa, NULL); - sigaction(SIGTERM, &sa, NULL); - - struct sigaction sa_usr1; - memset(&sa_usr1, 0, sizeof(sa_usr1)); - sa_usr1.sa_handler = render_sigusr1_handler; - sigemptyset(&sa_usr1.sa_mask); - sigaction(SIGUSR1, &sa_usr1, NULL); - - { - struct sigaction sa_adaptive; - memset(&sa_adaptive, 0, sizeof(sa_adaptive)); - sigemptyset(&sa_adaptive.sa_mask); - sa_adaptive.sa_handler = render_adaptive_handler; - sigaction(SIGRTMIN, &sa_adaptive, NULL); - } - - signal(SIGPIPE, SIG_IGN); - - snprintf(S.target.desktop, sizeof(S.target.desktop), "%s", S.target_name); - saver_target_t targets[MAX_OUTPUTS]; - struct json_object *outputs = mgr_ipc_request("window-rules/list-outputs", "{}"); - int count = parse_targets(outputs, targets); - for (int i = 0; i < count; ++i) - if (!strcmp(targets[i].name, S.target_name)) S.target = targets[i]; - if (outputs) json_object_put(outputs); - - /* Connect to Wayland */ - ensure_wayland_display(); - S.display = wl_display_connect(NULL); - if (!S.display) { - fprintf(stderr, "Cannot connect to Wayland display\n"); - return 1; - } - - wl_list_init(&render_output_list); - - S.registry = wl_display_get_registry(S.display); - wl_registry_add_listener(S.registry, &render_registry_listener, NULL); - - wl_display_roundtrip(S.display); - - if (S.xdg_output_manager) { - struct render_output_info *info; - wl_list_for_each(info, &render_output_list, link) { - info->xdg_output = zxdg_output_manager_v1_get_xdg_output( - S.xdg_output_manager, info->output); - zxdg_output_v1_add_listener(info->xdg_output, - &render_xdg_output_listener, info); - } - } - - wl_display_roundtrip(S.display); - - if (!S.output_found || !S.target_output) { - fprintf(stderr, "Output '%s' not found. Available outputs:\n", S.target_name); - struct render_output_info *info; - wl_list_for_each(info, &render_output_list, link) { - fprintf(stderr, " %s (%dx%d)\n", info->name, info->width, info->height); - } - wl_display_disconnect(S.display); - return 1; - } - - if (!S.compositor || !S.layer_shell) { - fprintf(stderr, "Missing required Wayland interfaces " - "(compositor=%p layer_shell=%p)\n", - (void*)S.compositor, (void*)S.layer_shell); - wl_display_disconnect(S.display); - return 1; - } - - fprintf(stderr, "Target output: %s (%dx%d, scale=%d, depth_layers=%d, head_hold=%d, head_mutation=%.2f, head_white=%.2f-%.2f, speed=%.1f-%.1f, trail=%d-%d, density=%.2f-%.2f)\n", - S.target_name, S.output_width, S.output_height, S.output_scale, - S.num_depth_layers, S.head_hold_frames, S.head_mutation_chance, - S.head_white_min, S.head_white_max, - S.min_speed, S.max_speed, S.min_trail, S.max_trail, - S.min_density, S.max_density); - - /* Create surface */ - S.surface = wl_compositor_create_surface(S.compositor); - - S.layer_surface = zwlr_layer_shell_v1_get_layer_surface( - S.layer_shell, S.surface, S.target_output, - S.ambient ? ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND : ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, - S.ambient ? "ambient-matrix" : "screensaver"); - - if (S.target.region) { - zwlr_layer_surface_v1_set_anchor(S.layer_surface, - ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT); - zwlr_layer_surface_v1_set_size(S.layer_surface, S.target.width, S.target.height); - zwlr_layer_surface_v1_set_margin(S.layer_surface, S.target.y, 0, 0, S.target.x); - } else { - zwlr_layer_surface_v1_set_anchor(S.layer_surface, - ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | - ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM | - ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | - ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT); - zwlr_layer_surface_v1_set_size(S.layer_surface, 0, 0); - } - zwlr_layer_surface_v1_set_exclusive_zone(S.layer_surface, -1); - zwlr_layer_surface_v1_set_keyboard_interactivity(S.layer_surface, 0); - - /* Keep the visual overlay from receiving pointer/touch input. */ - struct wl_region *empty_input = wl_compositor_create_region(S.compositor); - wl_surface_set_input_region(S.surface, empty_input); - wl_region_destroy(empty_input); - - zwlr_layer_surface_v1_add_listener(S.layer_surface, - &render_layer_surface_listener, NULL); - - wl_surface_commit(S.surface); - wl_display_roundtrip(S.display); - - if (!S.configured) { - fprintf(stderr, "Layer surface was not configured\n"); - wl_display_disconnect(S.display); - return 1; - } - - fprintf(stderr, "Surface configured: %dx%d\n", S.surface_width, S.surface_height); - - /* Initialize EGL */ - if (!render_init_egl()) { - fprintf(stderr, "Failed to initialize EGL\n"); - wl_display_disconnect(S.display); - return 1; - } - - /* Initialize shaders */ - if (!render_init_shaders()) { - fprintf(stderr, "Failed to initialize shaders\n"); - wl_display_disconnect(S.display); - return 1; - } - - render_set_projection(S.surface_width, S.surface_height); - - /* Initialize FreeType and build glyph atlas */ - int atlas_font_size = (int)(S.font_size * LAYER_NEAR_FONT_SCALE + 0.5); - if (atlas_font_size < S.font_size) atlas_font_size = S.font_size; - - if (!render_init_freetype(atlas_font_size)) { - fprintf(stderr, "Failed to initialize FreeType\n"); - wl_display_disconnect(S.display); - return 1; - } - - if (!render_build_atlas(atlas_font_size)) { - fprintf(stderr, "Failed to build glyph atlas\n"); - wl_display_disconnect(S.display); - return 1; - } - - fprintf(stderr, "Initializing %d depth layers (font_size=%d, atlas_size=%d)\n", - S.num_depth_layers, S.font_size, atlas_font_size); - - render_init_matrix(S.surface_width, S.surface_height); - - for (int l = 0; l < S.num_depth_layers; l++) { - depth_layer_t *dl = &S.layers[l]; - fprintf(stderr, "Layer %d: font_scale=%.2f cell=%dx%d grid=%dx%d opacity=%.2f speed=%.2fx density=%.0f%%\n", - l, dl->font_scale, dl->cell_w, dl->cell_h, - dl->cols, dl->rows, dl->opacity, dl->speed_scale, - dl->density * 100.0); - } - fprintf(stderr, "Starting matrix rain with depth layers (OpenGL ES 2.0)\n"); - - /* Initialize phase state. Transparent mode skips the black overlay. */ - S.phase = S.transparent ? PHASE_FADE_IN : PHASE_FADE_TO_BLACK; - S.phase_frame = 0; - S.surface_alpha = 0.0f; - S.fade_opacity = 0.0f; - S.render_start_time = render_monotime(); - S.blacken_frames = 0; - S.background_black = false; - - render_hide_desktop(); - - /* Main render loop */ - struct timespec ts_start, ts_end; - while (!S.stop && !S.closed) { - clock_gettime(CLOCK_MONOTONIC, &ts_start); - - render_dispatch_events(); - - if (S.stop || S.closed) break; - if (S.black_requested) { - S.black_requested = 0; S.phase = PHASE_BLACK; S.transparent = false; - S.surface_dirty = true; S.fade_opacity = 0; - } - if (S.fade_out_requested) { - render_restore_power(); - render_resume_desktop(); - } - if (S.fade_out_requested && - (S.phase == PHASE_BLACK || S.phase == PHASE_FADE_RAIN_TO_BLACK)) { - if (S.phase == PHASE_BLACK) { - S.phase = PHASE_FADE_TO_DESKTOP; - S.phase_frame = S.fade_frames; - } else { - /* Continue fading from the current rain opacity on activity. */ - S.phase = PHASE_FADE_OUT; - } - S.fade_out_requested = 0; - } - - if (S.adaptive_color && S.adaptive_reload) - render_load_adaptive_snapshot(); - - if (S.phase == PHASE_BLACK && !S.surface_dirty) continue; - - /* Keep configure events pending while asleep; swap may wait for a - * frame callback that a powered-off standalone output cannot deliver. */ - if (S.phase == PHASE_BLACK && S.power_fd >= 0) { - S.surface_dirty = false; - continue; - } - - if (S.adaptive_color && S.phase != PHASE_BLACK) { - /* Match the palette to this renderer's own background transition. */ - float t = S.transparent ? 0.0f : - (S.blacken_frames > 0 ? 1.0f - (float)S.blacken_frames / S.fade_frames : 1.0f); - S.base_color = (rgb_t){ - S.adaptive_desktop_color.r * (1.0f - t) + S.adaptive_black_color.r * t, - S.adaptive_desktop_color.g * (1.0f - t) + S.adaptive_black_color.g * t, - S.adaptive_desktop_color.b * (1.0f - t) + S.adaptive_black_color.b * t, - }; - for (int l = 0; l < S.num_depth_layers; l++) { - for (int c = 0; c < S.layers[l].cols; c++) - S.layers[l].columns[c].color = S.base_color; - } - } - - if (S.surface_dirty) { - wl_egl_window_resize(S.egl_window, S.surface_width, S.surface_height, 0, 0); - render_set_projection(S.surface_width, S.surface_height); - S.surface_dirty = false; - } - glViewport(0, 0, S.surface_width, S.surface_height); - float background_alpha; - - switch (S.phase) { - case PHASE_FADE_TO_BLACK: - S.phase_frame++; - S.surface_alpha = (float)S.phase_frame / S.fade_frames; - if (S.surface_alpha >= 1.0f) { - S.surface_alpha = 1.0f; - render_note_black_background(); - S.phase = PHASE_FADE_IN; - S.phase_frame = 0; - } - /* Clear with transitioning alpha, don't render rain */ - glClearColor(0.0f, 0.0f, 0.0f, - S.transparent ? 0.0f : S.surface_alpha); - glClear(GL_COLOR_BUFFER_BIT); - break; - - case PHASE_FADE_IN: - S.phase_frame++; - S.fade_opacity = (float)S.phase_frame / S.fade_frames; - if (S.fade_opacity >= 1.0f) { - S.fade_opacity = 1.0f; - S.phase = PHASE_RUNNING; - } - glClearColor(0.0f, 0.0f, 0.0f, S.transparent ? 0.0f : 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - render_tick_matrix(); - render_frame(); - break; - - case PHASE_RUNNING: - if (S.transparent && S.transparent_timeout > 0.0 && - render_monotime() - S.render_start_time >= S.transparent_timeout) { - S.transparent = false; - S.blacken_frames = S.fade_frames; - S.background_black = false; - } - if (S.fade_out_requested) { - S.phase = PHASE_FADE_OUT; - S.phase_frame = S.fade_frames; - S.fade_out_requested = 0; - } - background_alpha = S.transparent ? 0.0f : - (S.blacken_frames > 0 ? - 1.0f - (float)S.blacken_frames / S.fade_frames : 1.0f); - if (background_alpha >= 1.0f) render_note_black_background(); - if (S.phase == PHASE_RUNNING && S.background_black && - S.black_rain_timeout > 0.0 && - render_monotime() - S.black_start_time >= S.black_rain_timeout) { - S.phase = PHASE_FADE_RAIN_TO_BLACK; - S.phase_frame = S.fade_frames; - } - glClearColor(0.0f, 0.0f, 0.0f, background_alpha); - glClear(GL_COLOR_BUFFER_BIT); - render_tick_matrix(); - render_frame(); - if (S.blacken_frames > 0) { - S.blacken_frames--; - } - break; - - case PHASE_FADE_RAIN_TO_BLACK: - case PHASE_FADE_OUT: - S.phase_frame--; - S.fade_opacity = (float)S.phase_frame / S.fade_frames; - if (S.fade_opacity <= 0.0f) { - S.fade_opacity = 0.0f; - S.phase = S.phase == PHASE_FADE_RAIN_TO_BLACK ? - PHASE_BLACK : PHASE_FADE_TO_DESKTOP; - S.phase_frame = S.fade_frames; - if (S.phase == PHASE_BLACK) - fprintf(stderr, "[%s] Rain stopped; retaining black output\n", S.target_name); - } - glClearColor(0.0f, 0.0f, 0.0f, S.transparent ? 0.0f : 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - render_tick_matrix(); - render_frame(); - break; - - case PHASE_BLACK: - /* Repaint only on configure; otherwise keep the committed buffer. */ - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - break; - - case PHASE_FADE_TO_DESKTOP: - S.phase_frame--; - S.surface_alpha = (float)S.phase_frame / S.fade_frames; - if (S.surface_alpha <= 0.0f) { - S.surface_alpha = 0.0f; - S.stop = 1; /* exit */ - } - glClearColor(0.0f, 0.0f, 0.0f, - S.transparent ? 0.0f : S.surface_alpha); - glClear(GL_COLOR_BUFFER_BIT); - /* Don't render rain — just fading black overlay */ - break; - } - - eglSwapBuffers(S.egl_display, S.egl_surface); - render_report(); - if (S.phase == PHASE_BLACK) render_power_off(); - render_report(); - - clock_gettime(CLOCK_MONOTONIC, &ts_end); - long elapsed_us = (ts_end.tv_sec - ts_start.tv_sec) * 1000000L + - (ts_end.tv_nsec - ts_start.tv_nsec) / 1000; - long sleep_us = FRAME_INTERVAL_US - elapsed_us; - if (sleep_us > 0) { - usleep((useconds_t)sleep_us); - } - } - - fprintf(stderr, "Shutting down\n"); - render_restore_power(); - render_release_desktop(); - - /* Cleanup */ - free(S.vertex_buf); - if (S.vbo) glDeleteBuffers(1, &S.vbo); - if (S.shader_program) glDeleteProgram(S.shader_program); - if (S.atlas.texture) glDeleteTextures(1, &S.atlas.texture); - - if (S.egl_display != EGL_NO_DISPLAY) { - eglMakeCurrent(S.egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - if (S.egl_surface != EGL_NO_SURFACE) - eglDestroySurface(S.egl_display, S.egl_surface); - if (S.egl_context != EGL_NO_CONTEXT) - eglDestroyContext(S.egl_display, S.egl_context); - eglTerminate(S.egl_display); - } - if (S.egl_window) wl_egl_window_destroy(S.egl_window); - - if (S.ft_face) FT_Done_Face(S.ft_face); - if (S.ft_library) FT_Done_FreeType(S.ft_library); - - if (S.layer_surface) zwlr_layer_surface_v1_destroy(S.layer_surface); - if (S.surface) wl_surface_destroy(S.surface); - for (int l = 0; l < S.num_depth_layers; l++) { - free(S.layers[l].columns); - free(S.layers[l].cells); - } - - struct render_output_info *info, *tmp; - wl_list_for_each_safe(info, tmp, &render_output_list, link) { - if (info->xdg_output) zxdg_output_v1_destroy(info->xdg_output); - wl_output_release(info->output); - wl_list_remove(&info->link); - free(info); - } - - if (S.xdg_output_manager) zxdg_output_manager_v1_destroy(S.xdg_output_manager); - if (S.registry) wl_registry_destroy(S.registry); - wl_display_disconnect(S.display); - - return 0; -} - -/* =================================================================== - * MANAGER MODE (Wayfire IPC idle blanking) - * =================================================================== */ - -/* ── Constants ─────────────────────────────────────────────────────── */ - -#define MAX_PATH_LEN 512 -#define MAX_CMD_LEN 1024 -#define MAX_PIDS 4 -#define WF_IPC_HEADER_LEN 4 -#define WF_IPC_MAX_PAYLOAD (16 * 1024 * 1024) -#define CURSOR_POLL_INTERVAL 0.25 - -static const char *MGR_COLORS[] = { - "green", "red", "blue", "white", "yellow", "cyan", "magenta" -}; -#define MGR_NUM_COLORS 7 - -/* ── Data structures ───────────────────────────────────────────────── */ - -typedef struct { - char name[MAX_OUTPUT_NAME]; - saver_target_t target; - double last_input; - char state[8]; - pid_t pids[MAX_PIDS]; - int pid_count; - int timeout_override; - int power_off_override; - int video_inhibited; - int report_fd, render_phase; - bool powered_off; - double preview_until; - unsigned sent_revision, applied_revision; - int live_error, applied_font, applied_depth; - dr_effect_packet sent_effects; - char color[32]; - bool palette_ready; - double adaptive_hue, adaptive_hue_time, adaptive_luminance; - /* per-output overrides; negative = use global */ - double min_speed, max_speed; - int min_trail, max_trail; - double min_density, max_density; -} mgr_output_info_t; - -typedef struct { - dr_config_t config; - int inhibitor_warned, manual_activation; - double last_adaptive_sample; - bool sampler_warned; - char config_file[MAX_PATH_LEN]; - double config_mtime; - /* State */ - mgr_output_info_t outputs[MAX_OUTPUTS]; - int output_count; - int refresh_outputs; - char focused_output[MAX_OUTPUT_NAME]; - double last_blank_time; - int seat_idle; - int wayfire_cursor_hidden; - int wayfire_cursor_warned; - volatile sig_atomic_t stop; - volatile sig_atomic_t activate_all; /* SIGUSR1: blank all outputs now */ - volatile sig_atomic_t lock_now; /* SIGUSR2: request native session lock */ - volatile sig_atomic_t adaptive_update; /* SIGRTMIN: request a fresh desktop sample */ - - /* Sync */ - pthread_mutex_t lock; - - /* Logging */ - FILE *log_fp; -} mgr_state_t; - -static mgr_state_t G; - -/* ── Logging ───────────────────────────────────────────────────────── */ - -static void mgr_log_msg(const char *level, const char *fmt, ...) { - time_t now = time(NULL); - struct tm tm; - char tbuf[64]; - localtime_r(&now, &tm); - strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %H:%M:%S", &tm); - - va_list ap; - - va_start(ap, fmt); - fprintf(stdout, "%s %s ", tbuf, level); - vfprintf(stdout, fmt, ap); - fputc('\n', stdout); - fflush(stdout); - va_end(ap); - - if (G.log_fp) { - va_start(ap, fmt); - fprintf(G.log_fp, "%s %s ", tbuf, level); - vfprintf(G.log_fp, fmt, ap); - fputc('\n', G.log_fp); - fflush(G.log_fp); - va_end(ap); - } -} - -#define LOG_INFO(...) mgr_log_msg("INFO", __VA_ARGS__) -#define LOG_WARNING(...) mgr_log_msg("WARNING", __VA_ARGS__) -#define LOG_ERROR(...) mgr_log_msg("ERROR", __VA_ARGS__) - -/* ── Utility ───────────────────────────────────────────────────────── */ - -static double mgr_monotime(void) { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return ts.tv_sec + ts.tv_nsec / 1e9; -} - -static int mgr_pid_alive(pid_t pid) { - if (pid <= 0) return 0; - return kill(pid, 0) == 0; -} - -static void mgr_mkdirs(const char *path) { - char tmp[MAX_PATH_LEN]; - snprintf(tmp, sizeof(tmp), "%s", path); - for (char *p = tmp + 1; *p; p++) { - if (*p == '/') { - *p = '\0'; - mkdir(tmp, 0755); - *p = '/'; - } - } - mkdir(tmp, 0755); -} - -static void mgr_runtime_path(char *dst, size_t dstsz, const char *name) { - const char *runtime_dir = getenv("XDG_RUNTIME_DIR"); - - if (runtime_dir && runtime_dir[0] != '\0') { - char dir[MAX_PATH_LEN]; - snprintf(dir, sizeof(dir), "%s/digitalrain", runtime_dir); - mgr_mkdirs(dir); - snprintf(dst, dstsz, "%s/%s", dir, name); - return; - } - - snprintf(dst, dstsz, "/tmp/digitalrain-%lu-%s", (unsigned long)getuid(), name); -} - -static void mgr_expand_tilde(const char *src, char *dst, size_t dstsz) { - if (src[0] == '~' && (src[1] == '/' || src[1] == '\0')) { - const char *home = getenv("HOME"); - if (home) - snprintf(dst, dstsz, "%s%s", home, src + 1); - else - snprintf(dst, dstsz, "%s", src); - } else { - snprintf(dst, dstsz, "%s", src); - } -} - -static const char *mgr_random_color(void) { - return MGR_COLORS[rand() % MGR_NUM_COLORS]; -} - -/* ── Wayfire IPC (direct socket) ──────────────────────────────────── */ - -static int mgr_wayfire_ipc_path(char *dst, size_t dstsz) { - const char *sock = getenv("WAYFIRE_SOCKET"); - if (!sock || sock[0] == '\0') - sock = getenv("_WAYFIRE_SOCKET"); - if (sock && sock[0] != '\0') { - snprintf(dst, dstsz, "%s", sock); - return 0; - } - - ensure_wayland_display(); - - const char *runtime_dir = getenv("XDG_RUNTIME_DIR"); - const char *display = getenv("WAYLAND_DISPLAY"); - if (runtime_dir && runtime_dir[0] != '\0' && - display && display[0] != '\0') { - snprintf(dst, dstsz, "%s/wayfire-%s-.socket", runtime_dir, display); - struct stat st; - if (stat(dst, &st) == 0 && S_ISSOCK(st.st_mode)) - return 0; - } - - if (!runtime_dir || runtime_dir[0] == '\0') - return -1; - - DIR *dir = opendir(runtime_dir); - if (!dir) - return -1; - - struct dirent *ent; - while ((ent = readdir(dir)) != NULL) { - const char *name = ent->d_name; - size_t len = strlen(name); - if (strncmp(name, "wayfire-", 8) != 0 || len < 15 || - strcmp(name + len - 7, ".socket") != 0) - continue; - - char candidate[MAX_PATH_LEN]; - struct stat st; - snprintf(candidate, sizeof(candidate), "%s/%s", runtime_dir, name); - if (stat(candidate, &st) == 0 && S_ISSOCK(st.st_mode)) { - snprintf(dst, dstsz, "%s", candidate); - closedir(dir); - return 0; - } - } - - closedir(dir); - return -1; -} - -static int mgr_ipc_connect(void) { - char sock[MAX_PATH_LEN]; - if (mgr_wayfire_ipc_path(sock, sizeof(sock)) < 0) { - LOG_ERROR("Wayfire IPC socket not found"); - return -1; - } - - if (strlen(sock) >= sizeof(((struct sockaddr_un *)0)->sun_path)) { - LOG_ERROR("Wayfire IPC socket path too long: %s", sock); - return -1; - } - - int fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (fd < 0) return -1; - - struct sockaddr_un addr; - memset(&addr, 0, sizeof(addr)); - addr.sun_family = AF_UNIX; - snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", sock); - - if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { - close(fd); - return -1; - } - return fd; -} - -static int mgr_readn(int fd, void *buf, size_t n) { - size_t total = 0; - while (total < n) { - ssize_t r = read(fd, (char *)buf + total, n - total); - if (r < 0 && errno == EINTR) - continue; - if (r <= 0) return -1; - total += r; - } - return 0; -} - -static int mgr_writen(int fd, const void *buf, size_t n) { - size_t total = 0; - while (total < n) { - ssize_t w = write(fd, (const char *)buf + total, n - total); - if (w < 0 && errno == EINTR) - continue; - if (w <= 0) - return -1; - total += w; - } - return 0; -} - -static int mgr_ipc_send_payload(int fd, const char *payload) { - uint32_t len = payload ? (uint32_t)strlen(payload) : 0; - if (len > WF_IPC_MAX_PAYLOAD) - return -1; - - if (mgr_writen(fd, &len, WF_IPC_HEADER_LEN) < 0) - return -1; - if (len > 0 && mgr_writen(fd, payload, len) < 0) - return -1; - return 0; -} - -static int mgr_ipc_send_method(int fd, const char *method, - const char *data_json) { - struct json_object *root = json_object_new_object(); - struct json_object *data = NULL; - - if (!root) - return -1; - - if (data_json && data_json[0] != '\0') - data = json_tokener_parse(data_json); - if (!data) - data = json_object_new_object(); - - json_object_object_add(root, "method", json_object_new_string(method)); - json_object_object_add(root, "data", data); - - const char *payload = json_object_to_json_string_ext( - root, JSON_C_TO_STRING_PLAIN); - int rc = mgr_ipc_send_payload(fd, payload); - - json_object_put(root); - return rc; -} - -static int mgr_ipc_recv(int fd, char **out_payload) { - uint32_t len; - if (mgr_readn(fd, &len, WF_IPC_HEADER_LEN) < 0) - return -1; - if (len > WF_IPC_MAX_PAYLOAD) - return -1; - - char *payload = malloc(len + 1); - if (!payload) return -1; - if (len > 0 && mgr_readn(fd, payload, len) < 0) { - free(payload); - return -1; - } - payload[len] = '\0'; - - if (out_payload) *out_payload = payload; - else free(payload); - return 0; -} - -static struct json_object *mgr_ipc_request(const char *method, - const char *data_json) { - int fd = mgr_ipc_connect(); - if (fd < 0) return NULL; - - if (mgr_ipc_send_method(fd, method, data_json) < 0) { - close(fd); - return NULL; - } - - char *resp = NULL; - if (mgr_ipc_recv(fd, &resp) < 0) { - close(fd); - return NULL; - } - close(fd); - - struct json_object *j = json_tokener_parse(resp); - free(resp); - return j; -} - -/* ── Idle inhibitor detection ──────────────────────────────────────── */ - -static int mgr_response_inhibits_output(struct json_object *response, const char *name) { - struct json_object *outputs = NULL; - if (!response || !json_object_object_get_ex(response, "outputs", &outputs) || - !json_object_is_type(outputs, json_type_array)) - return 0; - for (size_t i = 0; i < json_object_array_length(outputs); i++) { - struct json_object *value = json_object_array_get_idx(outputs, i); - if (json_object_is_type(value, json_type_string) && - strcmp(json_object_get_string(value), name) == 0) - return 1; - } - return 0; -} - -static struct json_object *mgr_check_idle_inhibitors(void) { - if (!G.config.inhibit_video) return NULL; - struct json_object *response = mgr_ipc_request("digitalrain/idle_inhibitors", NULL); - struct json_object *outputs = NULL; - if (!response || !json_object_object_get_ex(response, "outputs", &outputs) || - !json_object_is_type(outputs, json_type_array)) { - if (!G.inhibitor_warned) - LOG_WARNING("Per-output video inhibition unavailable; enable Wayfire's digitalrain-idle plugin"); - G.inhibitor_warned = 1; - } else { - G.inhibitor_warned = 0; - } - return response; -} - -/* ── Config parsing ────────────────────────────────────────────────── */ - -static void mgr_trim(char *s) { - char *start = s; - while (*start == ' ' || *start == '\t') start++; - if (start != s) memmove(s, start, strlen(start) + 1); - char *end = s + strlen(s) - 1; - while (end >= s && (*end == ' ' || *end == '\t' || *end == '\n' || *end == '\r')) - *end-- = '\0'; -} - -#include "digitalrain-config-manager.h" - -static mgr_output_info_t *mgr_find_output(const char *name) { - for (int i = 0; i < G.output_count; i++) { - if (strcmp(G.outputs[i].name, name) == 0) - return &G.outputs[i]; - } - return NULL; -} - -static int mgr_ipc_response_has_error(struct json_object *resp) { - struct json_object *error; - if (json_object_object_get_ex(resp, "error", &error)) - return 1; - - struct json_object *result; - if (json_object_object_get_ex(resp, "result", &result)) { - const char *status = json_object_get_string(result); - if (status && strcmp(status, "ok") != 0) - return 1; - } - - return 0; -} - -static int mgr_get_cursor_status_from_ipc(char *output, size_t output_len, - int *hidden) { - if (output_len > 0) - output[0] = '\0'; - if (hidden) - *hidden = -1; - - struct json_object *resp = - mgr_ipc_request("digitalrain/cursor_status", "{}"); - if (!resp) - return 0; - - int ok = 0; - if (mgr_ipc_response_has_error(resp)) - goto out; - - struct json_object *valid; - if (json_object_object_get_ex(resp, "valid", &valid) && - !json_object_get_boolean(valid)) - goto out; - - struct json_object *hidden_obj; - if (hidden && json_object_object_get_ex(resp, "hidden", &hidden_obj)) - *hidden = json_object_get_boolean(hidden_obj) ? 1 : 0; - - struct json_object *name_obj; - if (json_object_object_get_ex(resp, "output", &name_obj)) { - const char *name = json_object_get_string(name_obj); - if (name && name[0] != '\0') { - snprintf(output, output_len, "%s", name); - struct json_object *x, *y; - if (json_object_object_get_ex(resp, "x", &x) && json_object_object_get_ex(resp, "y", &y)) { - double px = json_object_get_double(x), py = json_object_get_double(y); - pthread_mutex_lock(&G.lock); - for (int i = 0; i < G.output_count; ++i) { - saver_target_t *t = &G.outputs[i].target; - int left = t->layout_x + t->x, top = t->layout_y + t->y; - if (t->region && !strcmp(t->desktop, name) && px >= left && py >= top && - px < left + t->width && py < top + t->height) { - snprintf(output, output_len, "%s", t->name); - break; - } - } - pthread_mutex_unlock(&G.lock); - } - ok = 1; - } - } - -out: - json_object_put(resp); - return ok; -} - -static int mgr_set_wayfire_cursor_hidden(int hidden) { - struct json_object *resp = mgr_ipc_request( - hidden ? "digitalrain/hide_cursor" : "digitalrain/unhide_cursor", "{}"); - if (!resp) - return 0; - - int ok = !mgr_ipc_response_has_error(resp); - json_object_put(resp); - return ok; -} - -static void mgr_release_wayfire_cursor(void) { - pthread_mutex_lock(&G.lock); - int was_hidden = G.wayfire_cursor_hidden; - pthread_mutex_unlock(&G.lock); - - if (!was_hidden) - return; - - int ok = mgr_set_wayfire_cursor_hidden(0); - - pthread_mutex_lock(&G.lock); - G.wayfire_cursor_hidden = 0; - pthread_mutex_unlock(&G.lock); - - if (ok) - LOG_INFO("Cursor unhidden"); - else - LOG_WARNING("Cursor unhide request failed"); -} - -static void mgr_update_focused_output(const char *focused); - -static void mgr_update_wayfire_cursor(void) { - static char previous_output[MAX_OUTPUT_NAME]; - char cursor_output[MAX_OUTPUT_NAME]; - int actual_hidden = -1; - - if (!mgr_get_cursor_status_from_ipc(cursor_output, sizeof(cursor_output), - &actual_hidden)) { - pthread_mutex_lock(&G.lock); - int should_warn = !G.wayfire_cursor_warned; - G.wayfire_cursor_warned = 1; - pthread_mutex_unlock(&G.lock); - - if (should_warn) - LOG_WARNING("Cursor tracking unavailable; load Wayfire plugin digitalrain-cursor"); - - mgr_release_wayfire_cursor(); - return; - } - - /* A pointer crossing between members does not emit output-gain-focus. */ - if (strcmp(previous_output, cursor_output)) { - mgr_update_focused_output(cursor_output); - snprintf(previous_output, sizeof(previous_output), "%s", cursor_output); - } - pthread_mutex_lock(&G.lock); - G.wayfire_cursor_warned = 0; - if (actual_hidden >= 0) - G.wayfire_cursor_hidden = actual_hidden; - - mgr_output_info_t *o = mgr_find_output(cursor_output); - /* Input can resume before the renderer finishes fading out. A moving - * pointer must remain visible even while its output is still blanked. */ - int should_hide = o && strcmp(o->state, "off") == 0 && G.seat_idle; - int is_hidden = G.wayfire_cursor_hidden; - pthread_mutex_unlock(&G.lock); - - if (should_hide == is_hidden) - return; - - if (mgr_set_wayfire_cursor_hidden(should_hide)) { - pthread_mutex_lock(&G.lock); - G.wayfire_cursor_hidden = should_hide; - pthread_mutex_unlock(&G.lock); - - LOG_INFO("Cursor %s on %s", - should_hide ? "hidden" : "unhidden", cursor_output); - } else { - pthread_mutex_lock(&G.lock); - int should_warn = !G.wayfire_cursor_warned; - G.wayfire_cursor_warned = 1; - pthread_mutex_unlock(&G.lock); - - if (should_warn) - LOG_WARNING("Cursor %s request failed", - should_hide ? "hide" : "unhide"); - } -} - -static int mgr_get_output_timeout(mgr_output_info_t *o) { - if (o->timeout_override > 0) return o->timeout_override; - return G.config.global_timeout; -} - -static const char *mgr_get_output_color(mgr_output_info_t *o) { - static char hex_buf[8]; - const char *color = o->color[0] ? o->color : G.config.matrix_color; - /* Per-trail modes: pass through to renderer as-is */ - if (strcasecmp(color, "random_trail_named") == 0 || - strcasecmp(color, "random_trail_hex") == 0) - return color; - /* Per-output random: resolve here so each output gets a different color */ - if (strcasecmp(color, "random") == 0 || - strcasecmp(color, "random_named") == 0) - return mgr_random_color(); - if (strcasecmp(color, "random_hex") == 0) { - snprintf(hex_buf, sizeof(hex_buf), "#%02x%02x%02x", - rand() % 256, rand() % 256, rand() % 256); - return hex_buf; - } - return color; -} - -static double mgr_get_val_d(double per_output, double global) { - return per_output >= 0.0 ? per_output : global; -} -static int mgr_get_val_i(int per_output, int global) { - return per_output >= 0 ? per_output : global; -} - -static void mgr_init_output(mgr_output_info_t *out, const char *name, - double now) { - memset(out, 0, sizeof(*out)); - snprintf(out->name, sizeof(out->name), "%s", name); - out->last_input = now; - strcpy(out->state, "on"); - out->report_fd = -1; out->render_phase = -1; - out->timeout_override = -1; - out->power_off_override = -1; - out->min_speed = -1.0; - out->max_speed = -1.0; - out->min_trail = -1; - out->max_trail = -1; - out->min_density = -1.0; - out->max_density = -1.0; -} - -typedef struct { - struct wl_display *display; - struct wl_registry *registry; - struct zxdg_output_manager_v1 *xdg_output_manager; - struct wl_list outputs; -} mgr_outputs_wayland_t; - -typedef struct { - struct wl_output *output; - struct zxdg_output_v1 *xdg_output; - char name[MAX_OUTPUT_NAME]; - int width; - int height; - int scale; - struct wl_list link; -} mgr_wayland_output_info_t; - -static void mgr_output_set_name(mgr_wayland_output_info_t *info, - const char *name) { - if (!name || name[0] == '\0') - return; - snprintf(info->name, sizeof(info->name), "%s", name); -} - -static void mgr_xdg_output_handle_logical_position(void *data, - struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) { - (void)data; (void)xdg_output; (void)x; (void)y; -} - -static void mgr_xdg_output_handle_logical_size(void *data, - struct zxdg_output_v1 *xdg_output, int32_t w, int32_t h) { - (void)xdg_output; - mgr_wayland_output_info_t *info = data; - info->width = w; - info->height = h; -} - -static void mgr_xdg_output_handle_done(void *data, - struct zxdg_output_v1 *xdg_output) { - (void)data; (void)xdg_output; -} - -static void mgr_xdg_output_handle_name(void *data, - struct zxdg_output_v1 *xdg_output, const char *name) { - (void)xdg_output; - mgr_output_set_name(data, name); -} - -static void mgr_xdg_output_handle_description(void *data, - struct zxdg_output_v1 *xdg_output, const char *desc) { - (void)data; (void)xdg_output; (void)desc; -} - -static const struct zxdg_output_v1_listener mgr_xdg_output_listener = { - .logical_position = mgr_xdg_output_handle_logical_position, - .logical_size = mgr_xdg_output_handle_logical_size, - .done = mgr_xdg_output_handle_done, - .name = mgr_xdg_output_handle_name, - .description = mgr_xdg_output_handle_description, -}; - -static void mgr_output_handle_geometry(void *data, struct wl_output *output, - int32_t x, int32_t y, int32_t pw, int32_t ph, int32_t subpixel, - const char *make, const char *model, int32_t transform) { - (void)data; (void)output; (void)x; (void)y; - (void)pw; (void)ph; (void)subpixel; - (void)make; (void)model; (void)transform; -} - -static void mgr_output_handle_mode(void *data, struct wl_output *output, - uint32_t flags, int32_t width, int32_t height, int32_t refresh) { - (void)output; (void)refresh; - mgr_wayland_output_info_t *info = data; - if (flags & WL_OUTPUT_MODE_CURRENT) { - info->width = width; - info->height = height; - } -} - -static void mgr_output_handle_done(void *data, struct wl_output *output) { - (void)data; (void)output; -} - -static void mgr_output_handle_scale(void *data, struct wl_output *output, - int32_t factor) { - (void)output; - mgr_wayland_output_info_t *info = data; - info->scale = factor; -} - -static void mgr_output_handle_name(void *data, struct wl_output *output, - const char *name) { - (void)output; - mgr_output_set_name(data, name); -} - -static void mgr_output_handle_description(void *data, struct wl_output *output, - const char *desc) { - (void)data; (void)output; (void)desc; -} - -static const struct wl_output_listener mgr_output_listener = { - .geometry = mgr_output_handle_geometry, - .mode = mgr_output_handle_mode, - .done = mgr_output_handle_done, - .scale = mgr_output_handle_scale, - .name = mgr_output_handle_name, - .description = mgr_output_handle_description, -}; - -static void mgr_outputs_registry_handle_global(void *data, - struct wl_registry *registry, uint32_t name, - const char *interface, uint32_t version) { - mgr_outputs_wayland_t *ctx = data; - - if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 && - !ctx->xdg_output_manager) { - ctx->xdg_output_manager = wl_registry_bind(registry, name, - &zxdg_output_manager_v1_interface, version < 3 ? version : 3); - } else if (strcmp(interface, wl_output_interface.name) == 0) { - mgr_wayland_output_info_t *info = calloc(1, sizeof(*info)); - if (!info) - return; - info->output = wl_registry_bind(registry, name, - &wl_output_interface, version < 4 ? version : 4); - info->scale = 1; - wl_output_add_listener(info->output, &mgr_output_listener, info); - wl_list_insert(&ctx->outputs, &info->link); - } -} - -static void mgr_outputs_registry_handle_global_remove(void *data, - struct wl_registry *registry, uint32_t name) { - (void)data; (void)registry; (void)name; -} - -static const struct wl_registry_listener mgr_outputs_registry_listener = { - .global = mgr_outputs_registry_handle_global, - .global_remove = mgr_outputs_registry_handle_global_remove, -}; - -static void mgr_outputs_wayland_cleanup(mgr_outputs_wayland_t *ctx) { - mgr_wayland_output_info_t *info, *tmp; - wl_list_for_each_safe(info, tmp, &ctx->outputs, link) { - if (info->xdg_output) - zxdg_output_v1_destroy(info->xdg_output); - if (info->output) - wl_output_release(info->output); - wl_list_remove(&info->link); - free(info); - } - if (ctx->xdg_output_manager) - zxdg_output_manager_v1_destroy(ctx->xdg_output_manager); - if (ctx->registry) - wl_registry_destroy(ctx->registry); - if (ctx->display) - wl_display_disconnect(ctx->display); -} - -static void mgr_kill_pids(mgr_output_info_t *o); - -static bool same_target(const saver_target_t *a, const saver_target_t *b) { - return !strcmp(a->name, b->name) && !strcmp(a->desktop, b->desktop) && - a->region == b->region && a->x == b->x && a->y == b->y && - a->width == b->width && a->height == b->height && - a->layout_x == b->layout_x && a->layout_y == b->layout_y; -} - -static void mgr_reconcile_targets(const saver_target_t *targets, int count) { - pthread_mutex_lock(&G.lock); - bool changed = false; - for (int i = 0; i < G.output_count;) { - int found = -1; - for (int j = 0; j < count; ++j) - if (!strcmp(G.outputs[i].name, targets[j].name)) { found = j; break; } - if (found < 0 || !same_target(&G.outputs[i].target, &targets[found])) { - LOG_INFO("Removed or reconfigured output: %s", G.outputs[i].name); - mgr_kill_pids(&G.outputs[i]); - memmove(&G.outputs[i], &G.outputs[i + 1], - (G.output_count - i - 1) * sizeof(G.outputs[0])); - --G.output_count; - changed = true; - } else ++i; - } - for (int j = 0; j < count; ++j) { - if (mgr_find_output(targets[j].name)) continue; - mgr_output_info_t *o = &G.outputs[G.output_count++]; - mgr_init_output(o, targets[j].name, mgr_monotime()); - o->target = targets[j]; - LOG_INFO("Discovered output: %s (desktop %s)", o->name, o->target.desktop); - changed = true; - } - if (changed) G.config_mtime = 0; /* Apply overrides to newly discovered monitors. */ - pthread_mutex_unlock(&G.lock); -} - -static void mgr_refresh_outputs(void) { - saver_target_t targets[MAX_OUTPUTS] = {0}; - struct json_object *outputs = mgr_ipc_request("window-rules/list-outputs", "{}"); - int count = parse_targets(outputs, targets); - if (outputs) json_object_put(outputs); - if (count >= 0) { - mgr_reconcile_targets(targets, count); - return; - } - - mgr_outputs_wayland_t ctx = {0}; - wl_list_init(&ctx.outputs); - ensure_wayland_display(); - ctx.display = wl_display_connect(NULL); - if (!ctx.display) { - LOG_ERROR("Output refresh: cannot connect to Wayland display"); - return; - } - ctx.registry = wl_display_get_registry(ctx.display); - wl_registry_add_listener(ctx.registry, &mgr_outputs_registry_listener, &ctx); - if (wl_display_roundtrip(ctx.display) < 0) goto cleanup; - mgr_wayland_output_info_t *info; - if (ctx.xdg_output_manager) { - wl_list_for_each(info, &ctx.outputs, link) { - info->xdg_output = zxdg_output_manager_v1_get_xdg_output(ctx.xdg_output_manager, info->output); - zxdg_output_v1_add_listener(info->xdg_output, &mgr_xdg_output_listener, info); - } - } - if (wl_display_roundtrip(ctx.display) < 0) goto cleanup; - count = 0; - wl_list_for_each(info, &ctx.outputs, link) { - if (!info->name[0] || count == MAX_OUTPUTS) continue; - saver_target_t *t = &targets[count++]; - *t = (saver_target_t){0}; - snprintf(t->name, sizeof(t->name), "%s", info->name); - snprintf(t->desktop, sizeof(t->desktop), "%s", info->name); - t->width = info->width; t->height = info->height; - } - /* During IPC loss, retain member mappings; Wayland only exposes the parent. */ - pthread_mutex_lock(&G.lock); - bool had_regions = false; - for (int i = 0; i < G.output_count; ++i) had_regions |= G.outputs[i].target.region; - pthread_mutex_unlock(&G.lock); - if (!had_regions) mgr_reconcile_targets(targets, count); -cleanup: - mgr_outputs_wayland_cleanup(&ctx); -} - -static const char *mgr_get_focused_output_from_ipc(void) { - static char buf[MAX_OUTPUT_NAME]; - struct json_object *resp = - mgr_ipc_request("window-rules/get-focused-output", "{}"); - if (!resp) - return NULL; - - const char *result = NULL; - struct json_object *status; - if (json_object_object_get_ex(resp, "result", &status) && - strcmp(json_object_get_string(status), "ok") != 0) - goto out; - - struct json_object *info, *name; - if (json_object_object_get_ex(resp, "info", &info) && - json_object_object_get_ex(info, "name", &name)) { - snprintf(buf, sizeof(buf), "%s", json_object_get_string(name)); - result = buf; - } - -out: - json_object_put(resp); - return result; -} - -static void mgr_update_focused_output(const char *focused) { - if (!focused || focused[0] == '\0') - return; - - pthread_mutex_lock(&G.lock); - bool known = mgr_find_output(focused) != NULL; - pthread_mutex_unlock(&G.lock); - - if (!known) { - pthread_mutex_lock(&G.lock); - G.refresh_outputs = 1; - pthread_mutex_unlock(&G.lock); - return; - } - - pthread_mutex_lock(&G.lock); - char old[MAX_OUTPUT_NAME]; - snprintf(old, sizeof(old), "%s", G.focused_output); - snprintf(G.focused_output, sizeof(G.focused_output), "%s", focused); - if (strcmp(old, focused) != 0) { - /* - * Focus shift counts as fresh attention on both monitors: the new one - * is being looked at, and the old one's unfocused timeout starts now. - */ - double now = mgr_monotime(); - mgr_output_info_t *new_o = mgr_find_output(focused); - mgr_output_info_t *old_o = mgr_find_output(old); - if (new_o) new_o->last_input = now; - if (old_o) old_o->last_input = now; - LOG_INFO("Focus moved: %s -> %s", old, focused); - } - pthread_mutex_unlock(&G.lock); -} - -/* ── Screensaver management ────────────────────────────────────────── */ - -static int mgr_pids_alive(mgr_output_info_t *o) { - if (o->pid_count == 0) return 0; - for (int i = 0; i < o->pid_count; i++) { - if (!mgr_pid_alive(o->pids[i])) return 0; - } - return 1; -} - -static void mgr_kill_pids(mgr_output_info_t *o) { - if (o->report_fd >= 0) { close(o->report_fd); o->report_fd = -1; } - for (int i = 0; i < o->pid_count; i++) { - if (o->pids[i] > 0) - kill(o->pids[i], SIGTERM); - } - o->pid_count = 0; -} - -/* - * Get path to current binary via /proc/self/exe. - */ -static const char *mgr_get_self_exe(void) { - static char path[MAX_PATH_LEN]; - ssize_t n = readlink("/proc/self/exe", path, sizeof(path) - 1); - if (n > 0) { - path[n] = '\0'; - return path; - } - /* Fallback: hope "digitalrain" is in PATH */ - return "digitalrain"; -} - -static void mgr_update_adaptive_colors(void); - -static void mgr_blank_output(const char *output_name) { - /* Publish the initial palette before exec, avoiding an initial white flash. */ - mgr_update_adaptive_colors(); - pthread_mutex_lock(&G.lock); - - mgr_output_info_t *o = mgr_find_output(output_name); - if (!o) { - pthread_mutex_unlock(&G.lock); - return; - } - - if (strcmp(o->state, "off") == 0) { - if (mgr_pids_alive(o)) { - pthread_mutex_unlock(&G.lock); - return; - } - G.last_blank_time = mgr_monotime(); - LOG_INFO("[%s] saver died, restarting", output_name); - mgr_kill_pids(o); - } else { - G.last_blank_time = mgr_monotime(); - } - - const char *color = mgr_get_output_color(o); - double min_speed = mgr_get_val_d(o->min_speed, G.config.matrix_min_speed); - double max_speed = mgr_get_val_d(o->max_speed, G.config.matrix_max_speed); - int min_trail = mgr_get_val_i(o->min_trail, G.config.matrix_min_trail); - int max_trail = mgr_get_val_i(o->max_trail, G.config.matrix_max_trail); - double min_density = mgr_get_val_d(o->min_density, G.config.matrix_min_density); - double max_density = mgr_get_val_d(o->max_density, G.config.matrix_max_density); - int depth_layers = G.config.matrix_depth_layers; - pthread_mutex_unlock(&G.lock); - - const char *bin = mgr_get_self_exe(); - - char font_size_str[16], min_speed_str[16], max_speed_str[16], avg_speed_str[16]; - char min_trail_str[16], max_trail_str[16]; - char min_density_str[16], max_density_str[16]; - char depth_layers_str[16], fade_frames_str[16], head_hold_frames_str[16]; - char head_mutation_chance_str[16], head_white_min_str[16], head_white_max_str[16]; - char layer_near_density_str[16], layer_far_density_str[16], transparent_timeout_str[16]; - char black_rain_timeout_str[16], power_off_str[2]; - snprintf(power_off_str, sizeof(power_off_str), "%d", - mgr_get_val_i(o->power_off_override, G.config.matrix_power_off) != 0); - const char *transparent_arg = G.config.matrix_transparent ? "--transparent" : NULL; - const char *hide_desktop_arg = G.config.matrix_hide_desktop ? "--hide-desktop" : NULL; - snprintf(font_size_str, sizeof(font_size_str), "%d", G.config.matrix_font_size); - snprintf(min_speed_str, sizeof(min_speed_str), "%.2f", min_speed); - snprintf(max_speed_str, sizeof(max_speed_str), "%.2f", max_speed); - snprintf(avg_speed_str, sizeof(avg_speed_str), "%.2f", G.config.matrix_avg_speed); - snprintf(min_trail_str, sizeof(min_trail_str), "%d", min_trail); - snprintf(max_trail_str, sizeof(max_trail_str), "%d", max_trail); - snprintf(min_density_str, sizeof(min_density_str), "%.2f", min_density); - snprintf(max_density_str, sizeof(max_density_str), "%.2f", max_density); - snprintf(depth_layers_str, sizeof(depth_layers_str), "%d", depth_layers); - snprintf(fade_frames_str, sizeof(fade_frames_str), "%d", G.config.matrix_fade_frames); - snprintf(head_hold_frames_str, sizeof(head_hold_frames_str), "%d", G.config.matrix_head_hold_frames); - snprintf(head_mutation_chance_str, sizeof(head_mutation_chance_str), "%.2f", G.config.matrix_head_mutation_chance); - snprintf(head_white_min_str, sizeof(head_white_min_str), "%.2f", G.config.matrix_head_white_min); - snprintf(head_white_max_str, sizeof(head_white_max_str), "%.2f", G.config.matrix_head_white_max); - snprintf(layer_near_density_str, sizeof(layer_near_density_str), "%.2f", G.config.matrix_layer_near_density); - snprintf(layer_far_density_str, sizeof(layer_far_density_str), "%.2f", G.config.matrix_layer_far_density); - snprintf(transparent_timeout_str, sizeof(transparent_timeout_str), "%d", G.config.matrix_transparent_timeout); - snprintf(black_rain_timeout_str, sizeof(black_rain_timeout_str), "%d", G.config.matrix_black_rain_timeout); - - int report[2]; - if (socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, report)) return; - char report_str[16]; snprintf(report_str, sizeof(report_str), "%d", report[1]); - pid_t manager_pid = getpid(); - pid_t pid = fork(); - if (pid < 0) { - close(report[0]); close(report[1]); - LOG_ERROR("[%s] fork failed: %s", output_name, strerror(errno)); - return; - } - - if (pid == 0) { - /* Child: exec self in render mode */ - close(report[0]); fcntl(report[1], F_SETFD, 0); - struct sigaction terminate = { .sa_handler = SIG_DFL }; - sigemptyset(&terminate.sa_mask); - sigaction(SIGTERM, &terminate, NULL); - if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0 || getppid() != manager_pid) - _exit(1); - int devnull = open("/dev/null", O_RDWR); - if (devnull >= 0) { - dup2(devnull, STDOUT_FILENO); - dup2(devnull, STDERR_FILENO); - close(devnull); - } - execl(bin, "digitalrain", - "--render", "--report-fd", report_str, - "--output", output_name, - "--color", color, - "--font-size", font_size_str, - "--min-speed", min_speed_str, - "--max-speed", max_speed_str, - "--avg-speed", avg_speed_str, - "--min-trail", min_trail_str, - "--max-trail", max_trail_str, - "--min-density", min_density_str, - "--max-density", max_density_str, - "--depth-layers", depth_layers_str, - "--fade-frames", fade_frames_str, - "--head-hold-frames", head_hold_frames_str, - "--head-mutation-chance", head_mutation_chance_str, - "--head-white-min", head_white_min_str, - "--head-white-max", head_white_max_str, - "--layer-near-density", layer_near_density_str, - "--layer-far-density", layer_far_density_str, - "--transparent-timeout", transparent_timeout_str, - "--black-rain-timeout", black_rain_timeout_str, - "--power-off", power_off_str, - transparent_arg, - hide_desktop_arg, - (char *)NULL); - _exit(127); - } - - close(report[1]); - /* Parent: wait briefly to check if it exits immediately */ - usleep(500000); - int status; - pid_t w = waitpid(pid, &status, WNOHANG); - if (w > 0) { - close(report[0]); - LOG_WARNING("[%s] renderer exited immediately", output_name); - return; - } - - pthread_mutex_lock(&G.lock); - o = mgr_find_output(output_name); - if (o) { - if (o->report_fd >= 0) close(o->report_fd); - o->sent_revision = o->applied_revision = 0; memset(&o->sent_effects, 0, sizeof(o->sent_effects)); - o->report_fd = report[0]; o->render_phase = -1; o->powered_off = false; - o->pids[0] = pid; - o->pid_count = 1; - strcpy(o->state, "off"); - LOG_INFO("[%s] matrix screensaver active (PID %d, color=%s, speed=%.1f-%.1f, depth=%d)", - output_name, pid, color, min_speed, max_speed, depth_layers); - } - pthread_mutex_unlock(&G.lock); -} - -/* Fading renderers are reaped without blocking API or other monitor wakes. */ -static struct { pid_t pid; double deadline; } mgr_fading[64]; -static void mgr_reap_fading(void) { - double now = mgr_monotime(); - for (size_t i = 0; i < 64; ++i) if (mgr_fading[i].pid > 0) { - int status; - pid_t result = waitpid(mgr_fading[i].pid, &status, WNOHANG); - if (result > 0 || (result < 0 && errno == ECHILD)) mgr_fading[i].pid = 0; - else if (now >= mgr_fading[i].deadline) { - kill(mgr_fading[i].pid, SIGTERM); - mgr_fading[i].deadline = now + 3; - } - } -} -static void mgr_unblank_output(const char *output_name) { - pthread_mutex_lock(&G.lock); - mgr_output_info_t *o = mgr_find_output(output_name); - if (!o || strcmp(o->state, "on") == 0) { pthread_mutex_unlock(&G.lock); return; } - for (int i = 0; i < o->pid_count; ++i) if (o->pids[i] > 0) { - kill(o->pids[i], SIGUSR1); - size_t j; - for (j = 0; j < 64; ++j) if (!mgr_fading[j].pid) { - mgr_fading[j].pid = o->pids[i]; mgr_fading[j].deadline = mgr_monotime() + 3; break; - } - if (j == 64) kill(o->pids[i], SIGTERM); - } - if (o->report_fd >= 0) { close(o->report_fd); o->report_fd = -1; } - o->render_phase = -1; o->powered_off = false; o->pid_count = 0; - strcpy(o->state, "on"); - pthread_mutex_unlock(&G.lock); - mgr_update_wayfire_cursor(); - LOG_INFO("[%s] unblanked", output_name); -} - -/* ── Focus watcher thread ──────────────────────────────────────────── */ - -static const char *mgr_focus_name_from_event(struct json_object *event, - char *buf, size_t bufsz) { - struct json_object *event_name_obj; - const char *event_name = NULL; - - if (json_object_object_get_ex(event, "event", &event_name_obj)) - event_name = json_object_get_string(event_name_obj); - - if (!event_name) - return NULL; - - if (strcmp(event_name, "output-added") == 0 || - strcmp(event_name, "output-removed") == 0) { - pthread_mutex_lock(&G.lock); - G.refresh_outputs = 1; - pthread_mutex_unlock(&G.lock); - return NULL; - } - - if (strcmp(event_name, "output-gain-focus") == 0) { - struct json_object *output, *name; - if (json_object_object_get_ex(event, "output", &output) && - json_object_object_get_ex(output, "name", &name)) { - snprintf(buf, bufsz, "%s", json_object_get_string(name)); - return buf; - } - return NULL; - } - - if (strcmp(event_name, "view-focused") == 0) { - struct json_object *view, *name; - if (json_object_object_get_ex(event, "view", &view) && - json_object_object_get_ex(view, "output-name", &name)) { - struct json_object *monitor; - if (json_object_object_get_ex(view, "physical-monitor", &monitor) && - json_object_is_type(monitor, json_type_string) && json_object_get_string(monitor)[0]) name = monitor; - snprintf(buf, bufsz, "%s", json_object_get_string(name)); - return buf; - } - - return mgr_get_focused_output_from_ipc(); - } - - return NULL; -} - -static void *mgr_focus_watcher(void *arg) { - (void)arg; - - while (!G.stop) { - int fd = mgr_ipc_connect(); - if (fd < 0) { - LOG_ERROR("Focus watcher: cannot connect to Wayfire IPC"); - sleep(2); - continue; - } - - if (mgr_ipc_send_method(fd, "window-rules/events/watch", - "{\"events\":[\"view-focused\",\"output-gain-focus\"," - "\"output-added\",\"output-removed\"]}") < 0) { - close(fd); - sleep(2); - continue; - } - - char *resp = NULL; - if (mgr_ipc_recv(fd, &resp) < 0) { - free(resp); - close(fd); - sleep(2); - continue; - } - - struct json_object *ack = json_tokener_parse(resp); - free(resp); - if (ack) { - struct json_object *status; - if (json_object_object_get_ex(ack, "result", &status) && - strcmp(json_object_get_string(status), "ok") != 0) { - LOG_ERROR("Focus watcher: Wayfire event watch rejected: %s", - json_object_to_json_string_ext( - ack, JSON_C_TO_STRING_PLAIN)); - json_object_put(ack); - close(fd); - sleep(2); - continue; - } - json_object_put(ack); - } - - LOG_INFO("Focus watcher: using Wayfire IPC events"); - - while (!G.stop) { - char *payload = NULL; - if (mgr_ipc_recv(fd, &payload) < 0) { - free(payload); - break; - } - - struct json_object *event = json_tokener_parse(payload); - free(payload); - if (!event) - continue; - - char focused_buf[MAX_OUTPUT_NAME]; - const char *focused = - mgr_focus_name_from_event(event, focused_buf, sizeof(focused_buf)); - json_object_put(event); - if (focused) { - mgr_update_focused_output(focused); - } - } - - close(fd); - } - - return NULL; -} - -/* ── Idle watcher thread ───────────────────────────────────────────── */ - -typedef struct { - struct wl_display *display; - struct wl_registry *registry; - struct wl_seat *seat; - struct ext_idle_notifier_v1 *notifier; - struct ext_idle_notification_v1 *notification; - uint32_t notifier_version; - uint32_t seat_version; -} mgr_idle_wayland_t; - -static void mgr_idle_handle_idled(void *data, - struct ext_idle_notification_v1 *notification) { - (void)data; - (void)notification; - - pthread_mutex_lock(&G.lock); - G.seat_idle = 1; - pthread_mutex_unlock(&G.lock); -} - -static void mgr_idle_handle_resumed(void *data, - struct ext_idle_notification_v1 *notification) { - (void)data; - (void)notification; - - double now = mgr_monotime(); - - pthread_mutex_lock(&G.lock); - G.seat_idle = 0; - if ((now - G.last_blank_time) >= 3.0) { - G.manual_activation = 0; - /* The idle notification covers the whole seat, but renewed input is - * attention on the focused output only. Preserve every other output's - * countdown, just as the manager's continuous-activity path does. */ - mgr_output_info_t *focused = mgr_find_output(G.focused_output); - if (focused) focused->last_input = now; - } - pthread_mutex_unlock(&G.lock); -} - -static const struct ext_idle_notification_v1_listener mgr_idle_listener = { - .idled = mgr_idle_handle_idled, - .resumed = mgr_idle_handle_resumed, -}; - -static void mgr_idle_registry_handle_global(void *data, - struct wl_registry *registry, uint32_t name, - const char *interface, uint32_t version) { - mgr_idle_wayland_t *idle = data; - - if (strcmp(interface, wl_seat_interface.name) == 0 && !idle->seat) { - idle->seat_version = version < 7 ? version : 7; - idle->seat = wl_registry_bind(registry, name, - &wl_seat_interface, idle->seat_version); - } else if (strcmp(interface, ext_idle_notifier_v1_interface.name) == 0 && - !idle->notifier) { - idle->notifier_version = version < 2 ? version : 2; - idle->notifier = wl_registry_bind(registry, name, - &ext_idle_notifier_v1_interface, idle->notifier_version); - } -} - -static void mgr_idle_registry_handle_global_remove(void *data, - struct wl_registry *registry, uint32_t name) { - (void)data; - (void)registry; - (void)name; -} - -static const struct wl_registry_listener mgr_idle_registry_listener = { - .global = mgr_idle_registry_handle_global, - .global_remove = mgr_idle_registry_handle_global_remove, -}; - -static void mgr_idle_wayland_cleanup(mgr_idle_wayland_t *idle) { - if (idle->notification) - ext_idle_notification_v1_destroy(idle->notification); - if (idle->notifier) - ext_idle_notifier_v1_destroy(idle->notifier); - if (idle->seat) { - if (idle->seat_version >= WL_SEAT_RELEASE_SINCE_VERSION) - wl_seat_release(idle->seat); - else - wl_seat_destroy(idle->seat); - } - if (idle->registry) - wl_registry_destroy(idle->registry); - if (idle->display) - wl_display_disconnect(idle->display); - memset(idle, 0, sizeof(*idle)); -} - -static void *mgr_idle_watcher(void *arg) { - (void)arg; - - pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); - pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); - - while (!G.stop) { - mgr_idle_wayland_t idle = {0}; - - idle.display = wl_display_connect(NULL); - if (!idle.display) { - LOG_ERROR("Idle watcher: cannot connect to Wayland display"); - sleep(2); - continue; - } - - idle.registry = wl_display_get_registry(idle.display); - wl_registry_add_listener(idle.registry, &mgr_idle_registry_listener, &idle); - wl_display_roundtrip(idle.display); - - if (!idle.notifier || !idle.seat) { - LOG_ERROR("Idle watcher: compositor lacks ext-idle-notify-v1 or wl_seat"); - mgr_idle_wayland_cleanup(&idle); - sleep(5); - continue; - } - - /* Observe physical input independently of application inhibitors, which - * are handled per-output by the manager rather than for the whole seat. */ - if (idle.notifier_version >= 2) { - idle.notification = ext_idle_notifier_v1_get_input_idle_notification( - idle.notifier, 2000, idle.seat); - } else { - idle.notification = ext_idle_notifier_v1_get_idle_notification( - idle.notifier, 2000, idle.seat); - LOG_WARNING("Idle notifier v1: focused output may also stay awake during playback"); - } - ext_idle_notification_v1_add_listener(idle.notification, - &mgr_idle_listener, NULL); - wl_display_flush(idle.display); - - LOG_INFO("Idle watcher: using ext-idle-notify-v1"); - - while (!G.stop) { - int rc = wl_display_dispatch(idle.display); - if (rc < 0) { - LOG_ERROR("Idle watcher: Wayland dispatch failed"); - break; - } - } - - mgr_idle_wayland_cleanup(&idle); - if (!G.stop) - sleep(2); - } - - return NULL; -} - -/* ── Manager signal handling ───────────────────────────────────────── */ - -static void mgr_signal_handler(int sig) { - if (sig == SIGUSR1) - G.activate_all = 1; - else if (sig == SIGUSR2) - G.lock_now = 1; - else if (sig == SIGRTMIN) - G.adaptive_update = 1; - else - G.stop = 1; -} - -/* Palette policy is deliberately independent of the renderer and wallpaper - * rotator: one hue per output, a single contrast shade for the composed scene, - * and a matching bright shade for that output's eventual black background. */ -static rgb_t adaptive_hsl(double hue, double lightness) { - double chroma = (1.0 - fabs(2.0 * lightness - 1.0)) * 0.78; - double h = hue * 6.0; - double x = chroma * (1.0 - fabs(fmod(h, 2.0) - 1.0)); - double m = lightness - chroma / 2.0; - rgb_t rgb; - if (h < 1) rgb = (rgb_t){chroma, x, 0}; - else if (h < 2) rgb = (rgb_t){x, chroma, 0}; - else if (h < 3) rgb = (rgb_t){0, chroma, x}; - else if (h < 4) rgb = (rgb_t){0, x, chroma}; - else if (h < 5) rgb = (rgb_t){x, 0, chroma}; - else rgb = (rgb_t){chroma, 0, x}; - return (rgb_t){rgb.r + m, rgb.g + m, rgb.b + m}; -} - -static void adaptive_hex(char hex[8], rgb_t color) { - snprintf(hex, 8, "#%02x%02x%02x", (unsigned)lroundf(color.r * 255), - (unsigned)lroundf(color.g * 255), (unsigned)lroundf(color.b * 255)); -} - -/* Prefer a hue separated from the other monitors and from this one's last hue. - * Best-of-64 also works when many outputs make the preferred spacing impossible. */ -static double mgr_choose_hue(void) { - double best = 0, best_distance = -1; - for (int attempt = 0; attempt < 64; ++attempt) { - double candidate = rand() / ((double)RAND_MAX + 1.0); - double nearest = 1; - for (int i = 0; i < G.output_count; ++i) { - if (!G.outputs[i].palette_ready) continue; - double d = fabs(candidate - G.outputs[i].adaptive_hue); - nearest = fmin(nearest, fmin(d, 1.0 - d)); - } - if (nearest > best_distance) { - best = candidate; - best_distance = nearest; - } - if (nearest >= 0.12) break; - } - return best; -} - -static void mgr_update_adaptive_colors(void) { - double now = mgr_monotime(); - bool enabled = false, missing = false, hue_due = false; - pthread_mutex_lock(&G.lock); - for (int i = 0; i < G.output_count; ++i) { - mgr_output_info_t *o = &G.outputs[i]; - if (!is_adaptive_color(o->color[0] ? o->color : G.config.matrix_color)) continue; - enabled = true; - missing |= !o->palette_ready; - hue_due |= G.config.adaptive_hue_interval > 0 && - now - o->adaptive_hue_time >= G.config.adaptive_hue_interval; - } - pthread_mutex_unlock(&G.lock); - bool sample_due = missing || now - G.last_adaptive_sample >= G.config.adaptive_sample_interval; - if (!enabled || (!sample_due && !hue_due)) return; - - struct json_object *response = NULL, *samples = NULL, *version = NULL; - if (sample_due) { - G.last_adaptive_sample = now; - response = mgr_ipc_request("digitalrain/sample_desktop", "{}"); - bool valid = response && json_object_object_get_ex(response, "version", &version) && - json_object_get_int(version) == 1 && - json_object_object_get_ex(response, "outputs", &samples) && - json_object_is_type(samples, json_type_object); - if (!valid) { - samples = NULL; - if (!G.sampler_warned) - LOG_WARNING("Desktop sampling unavailable; enable digitalrain-sampler in Wayfire. Retaining palettes."); - G.sampler_warned = true; - } else { - G.sampler_warned = false; - } - } - - struct json_object *palette = json_object_new_object(); - pthread_mutex_lock(&G.lock); - for (int i = 0; i < G.output_count; ++i) { - mgr_output_info_t *o = &G.outputs[i]; - const char *mode = o->color[0] ? o->color : G.config.matrix_color; - if (!is_adaptive_color(mode)) continue; - if (!o->palette_ready || (G.config.adaptive_hue_interval > 0 && - now - o->adaptive_hue_time >= G.config.adaptive_hue_interval)) { - o->adaptive_hue = mgr_choose_hue(); - o->adaptive_hue_time = now; - } - struct json_object *sample = NULL, *luminance = NULL; - if (samples && json_object_object_get_ex(samples, o->name, &sample) && - json_object_object_get_ex(sample, "luminance", &luminance) && - (json_object_is_type(luminance, json_type_double) || - json_object_is_type(luminance, json_type_int))) { - double value = json_object_get_double(luminance); - if (isfinite(value) && value >= 0 && value <= 1) - o->adaptive_luminance = value; - } - /* Linear luminance: black and white contrast ratios cross near 0.18. */ - bool light = o->adaptive_luminance < 0.18; - if (!strcasecmp(mode, "adaptive-light")) light = true; - if (!strcasecmp(mode, "adaptive-dark")) light = false; - char desktop[8], black[8]; - adaptive_hex(desktop, adaptive_hsl(o->adaptive_hue, light ? 0.72 : 0.22)); - adaptive_hex(black, adaptive_hsl(o->adaptive_hue, 0.72)); - struct json_object *entry = json_object_new_object(); - json_object_object_add(entry, "color", json_object_new_string(desktop)); - json_object_object_add(entry, "black-color", json_object_new_string(black)); - json_object_object_add(entry, "hue", json_object_new_double(o->adaptive_hue)); - json_object_object_add(entry, "luminance", json_object_new_double(o->adaptive_luminance)); - json_object_object_add(palette, o->name, entry); - o->palette_ready = true; - } - - const char *runtime = getenv("XDG_RUNTIME_DIR"); - if (runtime && runtime[0]) { - char path[MAX_PATH_LEN], temporary[MAX_PATH_LEN]; - mgr_runtime_path(path, sizeof(path), "palette.json"); - mgr_runtime_path(temporary, sizeof(temporary), "palette.XXXXXX"); - int fd = mkstemp(temporary); - bool published = false; - if (fd >= 0) { - const char *json = json_object_to_json_string_ext(palette, JSON_C_TO_STRING_PLAIN); - size_t length = strlen(json), written = 0; - while (written < length) { - ssize_t n = write(fd, json + written, length - written); - if (n < 0 && errno == EINTR) continue; - if (n <= 0) break; - written += (size_t)n; - } - int closed = close(fd); - published = written == length && closed == 0 && rename(temporary, path) == 0; - if (!published) unlink(temporary); - } - if (published) { - for (int i = 0; i < G.output_count; ++i) { - mgr_output_info_t *o = &G.outputs[i]; - if (!is_adaptive_color(o->color[0] ? o->color : G.config.matrix_color)) continue; - for (int p = 0; p < o->pid_count; ++p) - if (o->pids[p] > 0) kill(o->pids[p], SIGRTMIN); - } - } else { - LOG_WARNING("Unable to publish adaptive palette: %s", strerror(errno)); - } - } - pthread_mutex_unlock(&G.lock); - json_object_put(palette); - if (response) json_object_put(response); -} - -/* ── Lock command ──────────────────────────────────────────────────── */ - - -/* - * Blank all outputs immediately (for activate/lock triggers). - */ -static void mgr_activate_all_outputs(void) { - pthread_mutex_lock(&G.lock); - G.seat_idle = 1; - G.last_blank_time = mgr_monotime(); - G.manual_activation = 1; - int n = G.output_count; - char names[MAX_OUTPUTS][MAX_OUTPUT_NAME]; - for (int i = 0; i < n; i++) - snprintf(names[i], MAX_OUTPUT_NAME, "%s", G.outputs[i].name); - pthread_mutex_unlock(&G.lock); - - for (int i = 0; i < n; i++) - mgr_blank_output(names[i]); - - LOG_INFO("All outputs activated (screensaver forced on)"); -} - -/* ── Manager ownership and PID file for signal delivery ────────────── */ - -/* Return 1 for ownership, 0 for an existing manager, -1 for an error. - * Keep this descriptor until shutdown; never unlink the lock file, since - * competing openers must continue to lock the same inode. O_CLOEXEC prevents - * rain renderers and the independent locker from retaining manager ownership. */ -static int mgr_acquire_instance_lock(int *lock_fd) { - char path[MAX_PATH_LEN]; - mgr_runtime_path(path, sizeof(path), "manager.lock"); - *lock_fd = -1; - - int fd = open(path, O_RDWR | O_CREAT | O_CLOEXEC | O_NOFOLLOW, 0600); - if (fd < 0) { - fprintf(stderr, "digitalrain: cannot open manager lock %s: %s\n", path, strerror(errno)); - return -1; - } - if (flock(fd, LOCK_EX | LOCK_NB) < 0) { - int error = errno; - close(fd); - if (error == EWOULDBLOCK || error == EAGAIN) { - fprintf(stderr, "digitalrain: manager already running; skipping duplicate start\n"); - return 0; - } - fprintf(stderr, "digitalrain: cannot acquire manager lock %s: %s\n", path, strerror(error)); - return -1; - } - *lock_fd = fd; - return 1; -} - -static void mgr_write_pidfile(void) { - char path[MAX_PATH_LEN]; - mgr_runtime_path(path, sizeof(path), "pid"); - - FILE *f = fopen(path, "w"); - if (f) { - fprintf(f, "%d\n", getpid()); - fclose(f); - } -} - -static void mgr_remove_pidfile(void) { - char path[MAX_PATH_LEN]; - mgr_runtime_path(path, sizeof(path), "pid"); - unlink(path); -} - -/* ── Zombie reaper ─────────────────────────────────────────────────── */ - -static void mgr_reap_children(void) { - int status; - while (waitpid(-1, &status, WNOHANG) > 0) - ; -} - -/* The API runs on the manager thread and replaces its existing timed waits. */ -#include "digitalrain-api.h" - -/* ── Manager main ──────────────────────────────────────────────────── */ - -static int manager_main(void) { - srand(time(NULL)); - - memset(&G, 0, sizeof(G)); - pthread_mutex_init(&G.lock, NULL); - ensure_wayland_display(); - - /* Reject duplicate autostarts before changing logs, PID, palette or outputs. */ - int instance_lock_fd; - int ownership = mgr_acquire_instance_lock(&instance_lock_fd); - if (ownership != 1) { - pthread_mutex_destroy(&G.lock); - return ownership == 0 ? 0 : 1; - } - - mgr_set_defaults(); - if (!mgr_load_config()) { fprintf(stderr, "%s\n", C.error); close(instance_lock_fd); return 1; } - snprintf(C.log_destination, sizeof(C.log_destination), "%s", G.config.log_file); - - /* Setup logging */ - { - char logdir[MAX_PATH_LEN]; - snprintf(logdir, sizeof(logdir), "%s", G.config.log_file); - char *slash = strrchr(logdir, '/'); - if (slash) { - *slash = '\0'; - mgr_mkdirs(logdir); - } - } - G.log_fp = fopen(G.config.log_file, "w"); - if (!G.log_fp) - fprintf(stderr, "Warning: cannot open log file %s: %s\n", G.config.log_file, strerror(errno)); - - /* Signal handlers */ - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = mgr_signal_handler; - sigaction(SIGINT, &sa, NULL); - sigaction(SIGTERM, &sa, NULL); - sigaction(SIGUSR1, &sa, NULL); - sigaction(SIGUSR2, &sa, NULL); - sigaction(SIGRTMIN, &sa, NULL); - - signal(SIGPIPE, SIG_IGN); - - if (!api_init()) { close(instance_lock_fd); return 1; } - mgr_write_pidfile(); - - /* Initialize outputs, then re-read config for per-output overrides */ - mgr_refresh_outputs(); - G.config_mtime = 0; - mgr_load_config(); - mgr_update_adaptive_colors(); - - const char *focused = mgr_get_focused_output_from_ipc(); - pthread_mutex_lock(&G.lock); - if (focused) - snprintf(G.focused_output, sizeof(G.focused_output), "%s", focused); - pthread_mutex_unlock(&G.lock); - - /* Log startup */ - { - char outlist[512] = ""; - for (int i = 0; i < G.output_count; i++) { - if (i > 0) strcat(outlist, ", "); - strcat(outlist, G.outputs[i].name); - } - LOG_INFO("Started — outputs: [%s], focused: %s", outlist, G.focused_output); - } - - /* Start threads */ - pthread_t focus_tid, idle_tid; - pthread_create(&focus_tid, NULL, mgr_focus_watcher, NULL); - pthread_create(&idle_tid, NULL, mgr_idle_watcher, NULL); - - double last_config_check = 0; - double last_cursor_check = 0; - double last_adaptive_check = mgr_monotime(); - - while (!G.stop) { - double now = mgr_monotime(); - - pthread_mutex_lock(&G.lock); - int refresh = G.refresh_outputs; - G.refresh_outputs = 0; - pthread_mutex_unlock(&G.lock); - if (refresh || now - last_config_check > 10) { - mgr_refresh_outputs(); - mgr_load_config(); - last_config_check = now; - } - - /* Sampling and hue changes have independent deadlines. */ - if (now - last_adaptive_check >= 1) { - mgr_update_adaptive_colors(); - last_adaptive_check = now; - } - - api_tick(now); - mgr_reap_children(); - - /* Handle activate/lock signals */ - if (G.lock_now) { - G.lock_now = 0; - G.activate_all = 0; /* lock implies activate */ - struct json_object *request = api_lock(); - json_object_put(request); - } else if (G.activate_all && !api_locked()) { - G.activate_all = 0; - mgr_activate_all_outputs(); - } - - struct json_object *inhibitors = mgr_check_idle_inhibitors(); - - pthread_mutex_lock(&G.lock); - char focused_buf[MAX_OUTPUT_NAME]; - snprintf(focused_buf, sizeof(focused_buf), "%s", G.focused_output); - int idle = G.seat_idle; - - if (!idle) { - mgr_output_info_t *fo = mgr_find_output(focused_buf); - if (fo) fo->last_input = now; - } - - int n = G.output_count; - char names[MAX_OUTPUTS][MAX_OUTPUT_NAME]; - double last_inputs[MAX_OUTPUTS]; - char states[MAX_OUTPUTS][8]; - int timeouts[MAX_OUTPUTS]; - int is_focused[MAX_OUTPUTS]; - int inhibited[MAX_OUTPUTS]; - - for (int i = 0; i < n; i++) { - snprintf(names[i], MAX_OUTPUT_NAME, "%s", G.outputs[i].name); - int app_inhibited = !G.manual_activation && mgr_response_inhibits_output(inhibitors, names[i]); - inhibited[i] = api_locked() || (G.outputs[i].preview_until <= now && - (api_inhibited(names[i], now) || app_inhibited)); - if (app_inhibited != G.outputs[i].video_inhibited) { - LOG_INFO("[%s] video inhibition %s", names[i], app_inhibited ? "active" : "released"); - G.outputs[i].video_inhibited = app_inhibited; - } - /* Restart this output's normal idle countdown when playback ends. */ - if (inhibited[i]) G.outputs[i].last_input = now; - last_inputs[i] = G.outputs[i].last_input; - snprintf(states[i], 8, "%s", G.outputs[i].state); - is_focused[i] = (strcmp(names[i], focused_buf) == 0); - if (is_focused[i]) - timeouts[i] = G.config.focused_timeout; - else - timeouts[i] = mgr_get_output_timeout(&G.outputs[i]); - } - pthread_mutex_unlock(&G.lock); - - if (inhibitors) json_object_put(inhibitors); - - for (int i = 0; i < n; i++) { - double idle_secs = now - last_inputs[i]; - - if (inhibited[i]) { - if (strcmp(states[i], "off") == 0) mgr_unblank_output(names[i]); - } else if (A.ambient_fd >= 0) { - /* The ambient controller owns visual idle transitions, not idle clocks. */ - continue; - } else if (G.outputs[i].preview_until > now) { - continue; - } else if (idle_secs >= timeouts[i]) { - mgr_blank_output(names[i]); - } else { - if (strcmp(states[i], "off") == 0 && is_focused[i]) { - mgr_unblank_output(names[i]); - } - } - } - - if (now - last_cursor_check >= CURSOR_POLL_INTERVAL) { - mgr_update_wayfire_cursor(); - last_cursor_check = now; - } - - for (int s = 0; s < G.config.check_interval * 10 && !G.stop; s++) { - api_poll(100); - - double wait_now = mgr_monotime(); - if (wait_now - last_cursor_check >= CURSOR_POLL_INTERVAL) { - mgr_update_wayfire_cursor(); - last_cursor_check = wait_now; - } - - pthread_mutex_lock(&G.lock); - int idle_now = G.seat_idle; - int any_blanked = 0; - if (!idle_now) { - for (int i = 0; i < G.output_count; i++) { - if (strcmp(G.outputs[i].state, "off") == 0) { - any_blanked = 1; - break; - } - } - } - pthread_mutex_unlock(&G.lock); - if (!idle_now && any_blanked) break; - } - if (G.adaptive_update) { - G.adaptive_update = 0; - G.last_adaptive_sample = 0; - mgr_update_adaptive_colors(); - } - } - - /* Clean shutdown */ - LOG_INFO("Shutting down..."); - for (int i = 0; i < G.output_count; i++) - mgr_unblank_output(G.outputs[i].name); - mgr_release_wayfire_cursor(); - for (size_t i = 0; i < 64; ++i) if (mgr_fading[i].pid > 0) kill(mgr_fading[i].pid, SIGTERM); - - pthread_cancel(focus_tid); - pthread_cancel(idle_tid); - - api_close(); - mgr_remove_pidfile(); - if (G.log_fp) fclose(G.log_fp); - pthread_mutex_destroy(&G.lock); - close(instance_lock_fd); - - return 0; -} - -/* =================================================================== - * ENTRY POINT — dispatch to manager or renderer - * =================================================================== */ - -int main(int argc, char **argv) { - if (argc > 1 && !strcmp(argv[1], "--render")) return renderer_main(argc, argv); - if (argc > 1) return api_cli(argc, argv); - return manager_main(); -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain.pam b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain.pam deleted file mode 100644 index 3624630cafc1..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain.pam +++ /dev/null @@ -1,3 +0,0 @@ -#%PAM-1.0 -auth include system-auth -account include system-auth diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain.toml.default b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain.toml.default deleted file mode 100644 index ae3c8b5e089a..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/digitalrain.toml.default +++ /dev/null @@ -1,164 +0,0 @@ -schema_version = 1 -# Digital Rain configuration -# Guide: configuration.md in /usr/share/doc/digitalrain-*/ (may be compressed). -# Manager chooses one config; user settings do not merge with /etc/digitalrain.toml. -# Quote strings and use true/false switches. Inline # comments are supported. -# Timer values are seconds; animation values explicitly say frames. -# Appearance updates live. Surface/background policy applies on next activation. -[global] -# --- Idle detection and logging --- -# Seconds before an unfocused physical monitor starts rain. Use a positive integer. -TIMEOUT = 300 -# Seconds before the focused monitor starts rain while input is idle. Use a positive -# integer. This takes precedence over [output_timeouts]. -FOCUSED_TIMEOUT = 30 -# Seconds between normal manager checks; use a positive integer. Cursor tracking and -# wake checks continue during this wait. -CHECK_INTERVAL = 5 -# true honors visible applications' Wayland and supported portal idle inhibitors on their monitors; false ignores -# them. Requires digitalrain-idle (portal requests also need the GTK backend bridge). This follows application requests, not video -# detection. -INHIBIT_VIDEO = true -# Writable log path; ~/ expands to your home. Reopened and truncated on manager startup. -# Restart the manager after changing this path. -LOG_FILE = "~/.local/state/digitalrain/digitalrain.log" -# Whole-seat inactivity seconds before native session locking. 0 disables automatic -# locking. Independent of monitor saver timers; saver inhibitors do not delay it. -# Requires Wayfire session-lock and the current digitalrain-idle helper. -LOCK_TIMEOUT = 0 -[matrix] -# --- Background, transitions, and monitor power --- -# true starts rain over visible desktop content; false starts with a black background. -TRANSPARENT = false -# Seconds from renderer startup until transparent rain starts fading to a black -# background. 0 keeps it transparent indefinitely. Used only with TRANSPARENT=true. -TRANSPARENT_TIMEOUT = 600 -# Additional seconds after the background is fully black before the rain fades away. 0 -# keeps rain running on black indefinitely; it does not immediately stop rain. -BLACK_RAIN_TIMEOUT = 0 -# false retains a solid-black surface with the monitor powered for instant wake. true powers -# off after rain ends. Requires a positive BLACK_RAIN_TIMEOUT and digitalrain-power; -# unavailable power control falls back to black. -POWER_OFF = false -# false leaves apps and bars visible under transparent rain; the black background covers -# them naturally. true optionally fades them away over the wallpaper. Requires -# TRANSPARENT=true and digitalrain-desktop. -HIDE_DESKTOP = false -# Frames per fade stage, valid range 1–300. Target frame rate is 30 fps: 20 frames ≈ 0.67 -# seconds; 30 ≈ 1 second. A wake can contain both a rain fade and a background fade. -FADE_FRAMES = 30 -# --- Color and adaptive sampling --- -# Fixed: green, red, blue, white, yellow, cyan, magenta, or #RRGGBB. -# Per monitor: random_named (alias random), random_hex. -# Per trail: random_trail_named, random_trail_hex. -# Adaptive: adaptive, adaptive-light, adaptive-dark (requires digitalrain-sampler). -# Named color, six-digit hex, random mode, or adaptive mode; see the color table in -# configuration.md. -COLOR = "green" -# Seconds between desktop contrast samples in adaptive modes; minimum 5. Requires -# digitalrain-sampler. -ADAPTIVE_SAMPLE_INTERVAL = 60 -# Seconds between new random hues in adaptive modes. 0 holds the initial hue; negative -# values are rejected. Independent of contrast sampling. -ADAPTIVE_HUE_INTERVAL = 900 -# --- Glyphs and depth layers --- -# Base glyph size in renderer pixels, valid range 6–72. Depth layers scale the glyph -# grid. -FONT_SIZE = 12 -# Number of rain layers, valid range 1–8. Far layers are smaller, dimmer and slower; near -# layers are larger, brighter and faster. -DEPTH_LAYERS = 4 -# --- Motion and trail lifetime --- -# Minimum fall speed in cells per animation frame before depth scaling. Valid range 0.001–100. -MIN_SPEED = 0.4 -# Maximum fall speed in cells per animation frame before depth scaling. Must be at least -# MIN_SPEED. -MAX_SPEED = 2.0 -# Bias for the random fall-speed distribution. Keep it between MIN_SPEED and MAX_SPEED; -# this is not an exact mean. -AVG_SPEED = 0.8 -# Minimum glyph trail lifetime in animation frames; minimum 1. Visible trail length also -# depends on fall speed. -MIN_TRAIL = 4 -# Maximum glyph trail lifetime in animation frames. Must be at least MIN_TRAIL. -MAX_TRAIL = 28 -# --- Initial rain density --- -# These settings seed the initial animation; they do not enforce an ongoing -# fraction of active columns as trails respawn. -# With DEPTH_LAYERS=1, the midpoint of MIN_DENSITY and MAX_DENSITY seeds active columns. -# Valid range 0–1. Ignored for multiple layers. -MIN_DENSITY = 0.25 -# Upper density value for the single-layer midpoint. Valid between MIN_DENSITY and 1. -MAX_DENSITY = 0.45 -# With DEPTH_LAYERS>1, initial active-column probability in the far layer. Intermediate -# layers interpolate toward LAYER_NEAR_DENSITY. Valid range 0–2; values >=1 seed every -# eligible column. -LAYER_FAR_DENSITY = 0.35 -# With DEPTH_LAYERS>1, initial active-column probability in the near layer. Normal range -# 0–1 (valid range 0–2). Ignored in single-layer mode. -LAYER_NEAR_DENSITY = 0.15 -# --- Leading glyph behavior --- -# Minimum frames before a head glyph may change while it stays in one row. Valid range -# 0–120; entering a new row chooses a new glyph regardless. -HEAD_HOLD_FRAMES = 5 -# Probability per eligible frame of changing the head glyph, valid range 0–1. 0 disables -# these stationary changes. -HEAD_MUTATION_CHANCE = 0.15 -# Minimum white blend for heads in non-adaptive colors, valid range 0–1. 0 uses the trail -# color; 1 uses white. Adaptive modes use their palette instead. -HEAD_WHITE_MIN = 0.75 -# Maximum white blend, valid range 0–1. If the minimum exceeds the maximum, the config -# is rejected. -HEAD_WHITE_MAX = 1.00 -[output_timeouts] -# Unfocused idle delay by physical connector; FOCUSED_TIMEOUT still applies to the focused monitor. Positive seconds; 0 falls back to TIMEOUT. -# Use physical names such as DP-1, even when they share VIRTUAL-1. -#HDMI-A-1 = 600 -#DP-1 = 300 -[output_colors] -# Same color modes as COLOR; a value here takes precedence on this monitor. -# Use physical names such as DP-1, even when they share VIRTUAL-1. -#HDMI-A-1 = "adaptive" -#DP-1 = "random_trail_hex" -[output_power_off] -# Per-monitor override of POWER_OFF: false keeps instant wake, true powers off after rain. -# Use physical names such as DP-1, even when they share VIRTUAL-1. -#HDMI-A-1 = false -#DP-1 = true - -[alerts] -# Temporary status colors submitted by programs or agents; does not activate rain. -ENABLED = true -# Allow colors on native lock surfaces; independent from authentication. -SHOW_WHILE_LOCKED = false - -[alerts.info] -COLOR = "#00bfff" -PRIORITY = 10 -PULSE = 0.0 # Gentle brightness reduction, 0..0.25, with a two-second period. -[alerts.success] -COLOR = "#40ff80" -PRIORITY = 20 -PULSE = 0.0 -[alerts.warning] -COLOR = "#ffb000" -PRIORITY = 30 -PULSE = 0.0 -[alerts.error] -COLOR = "#ff4040" -PRIORITY = 40 -PULSE = 0.0 - -[audio] -# Requires the optional PipeWire helper. Capture runs only with eligible visible rain. -ENABLED = false -SOURCE = "default-output" # Or the PipeWire node.name of a playback sink. -MODE = "energy" # RMS/peak response; spectrum/beat modes are not implemented. -SHOW_WHILE_LOCKED = false -OUTPUTS = [] # Physical connectors; empty means all eligible monitors. -SPEED_AMOUNT = 0.25 # Maximum proportional increase, valid range 0..2. -BRIGHTNESS_AMOUNT = 0.10 # Maximum proportional increase, valid range 0..1. -ATTACK_MS = 50.0 # Envelope rise time, 1..2000 ms. -RELEASE_MS = 250.0 # Envelope fall time, 1..5000 ms. -GAIN = 4.0 # Measurement gain, 0.01..100; normalized output is capped at 1. -NOISE_GATE = 0.005 # RMS below this level produces no modulation; 0..1. diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/ext-idle-notify-v1-client-protocol.h b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/ext-idle-notify-v1-client-protocol.h deleted file mode 100644 index 19f2a6dd7f20..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/ext-idle-notify-v1-client-protocol.h +++ /dev/null @@ -1,323 +0,0 @@ -/* Generated by wayland-scanner 1.26.0 */ - -#ifndef EXT_IDLE_NOTIFY_V1_CLIENT_PROTOCOL_H -#define EXT_IDLE_NOTIFY_V1_CLIENT_PROTOCOL_H - -#include <stdint.h> -#include <stddef.h> -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_ext_idle_notify_v1 The ext_idle_notify_v1 protocol - * @section page_ifaces_ext_idle_notify_v1 Interfaces - * - @subpage page_iface_ext_idle_notifier_v1 - idle notification manager - * - @subpage page_iface_ext_idle_notification_v1 - idle notification - * @section page_copyright_ext_idle_notify_v1 Copyright - * <pre> - * - * Copyright © 2015 Martin Gräßlin - * Copyright © 2022 Simon Ser - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * </pre> - */ -struct ext_idle_notification_v1; -struct ext_idle_notifier_v1; -struct wl_seat; - -#ifndef EXT_IDLE_NOTIFIER_V1_INTERFACE -#define EXT_IDLE_NOTIFIER_V1_INTERFACE -/** - * @page page_iface_ext_idle_notifier_v1 ext_idle_notifier_v1 - * @section page_iface_ext_idle_notifier_v1_desc Description - * - * This interface allows clients to monitor user idle status. - * - * After binding to this global, clients can create ext_idle_notification_v1 - * objects to get notified when the user is idle for a given amount of time. - * @section page_iface_ext_idle_notifier_v1_api API - * See @ref iface_ext_idle_notifier_v1. - */ -/** - * @defgroup iface_ext_idle_notifier_v1 The ext_idle_notifier_v1 interface - * - * This interface allows clients to monitor user idle status. - * - * After binding to this global, clients can create ext_idle_notification_v1 - * objects to get notified when the user is idle for a given amount of time. - */ -extern const struct wl_interface ext_idle_notifier_v1_interface; -#endif -#ifndef EXT_IDLE_NOTIFICATION_V1_INTERFACE -#define EXT_IDLE_NOTIFICATION_V1_INTERFACE -/** - * @page page_iface_ext_idle_notification_v1 ext_idle_notification_v1 - * @section page_iface_ext_idle_notification_v1_desc Description - * - * This interface is used by the compositor to send idle notification events - * to clients. - * - * Initially the notification object is not idle. The notification object - * becomes idle when no user activity has happened for at least the timeout - * duration, starting from the creation of the notification object. User - * activity may include input events or a presence sensor, but is - * compositor-specific. - * - * How this notification responds to idle inhibitors depends on how - * it was constructed. If constructed from the - * get_idle_notification request, then if an idle inhibitor is - * active (e.g. another client has created a zwp_idle_inhibitor_v1 - * on a visible surface), the compositor must not make the - * notification object idle. However, if constructed from the - * get_input_idle_notification request, then idle inhibitors are - * ignored, and only input from the user, e.g. from a keyboard or - * mouse, counts as activity. - * - * When the notification object becomes idle, an idled event is sent. When - * user activity starts again, the notification object stops being idle, - * a resumed event is sent and the timeout is restarted. - * @section page_iface_ext_idle_notification_v1_api API - * See @ref iface_ext_idle_notification_v1. - */ -/** - * @defgroup iface_ext_idle_notification_v1 The ext_idle_notification_v1 interface - * - * This interface is used by the compositor to send idle notification events - * to clients. - * - * Initially the notification object is not idle. The notification object - * becomes idle when no user activity has happened for at least the timeout - * duration, starting from the creation of the notification object. User - * activity may include input events or a presence sensor, but is - * compositor-specific. - * - * How this notification responds to idle inhibitors depends on how - * it was constructed. If constructed from the - * get_idle_notification request, then if an idle inhibitor is - * active (e.g. another client has created a zwp_idle_inhibitor_v1 - * on a visible surface), the compositor must not make the - * notification object idle. However, if constructed from the - * get_input_idle_notification request, then idle inhibitors are - * ignored, and only input from the user, e.g. from a keyboard or - * mouse, counts as activity. - * - * When the notification object becomes idle, an idled event is sent. When - * user activity starts again, the notification object stops being idle, - * a resumed event is sent and the timeout is restarted. - */ -extern const struct wl_interface ext_idle_notification_v1_interface; -#endif - -#define EXT_IDLE_NOTIFIER_V1_DESTROY 0 -#define EXT_IDLE_NOTIFIER_V1_GET_IDLE_NOTIFICATION 1 -#define EXT_IDLE_NOTIFIER_V1_GET_INPUT_IDLE_NOTIFICATION 2 - - -/** - * @ingroup iface_ext_idle_notifier_v1 - */ -#define EXT_IDLE_NOTIFIER_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_ext_idle_notifier_v1 - */ -#define EXT_IDLE_NOTIFIER_V1_GET_IDLE_NOTIFICATION_SINCE_VERSION 1 -/** - * @ingroup iface_ext_idle_notifier_v1 - */ -#define EXT_IDLE_NOTIFIER_V1_GET_INPUT_IDLE_NOTIFICATION_SINCE_VERSION 2 - -/** @ingroup iface_ext_idle_notifier_v1 */ -static inline void -ext_idle_notifier_v1_set_user_data(struct ext_idle_notifier_v1 *ext_idle_notifier_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) ext_idle_notifier_v1, user_data); -} - -/** @ingroup iface_ext_idle_notifier_v1 */ -static inline void * -ext_idle_notifier_v1_get_user_data(struct ext_idle_notifier_v1 *ext_idle_notifier_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) ext_idle_notifier_v1); -} - -static inline uint32_t -ext_idle_notifier_v1_get_version(struct ext_idle_notifier_v1 *ext_idle_notifier_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) ext_idle_notifier_v1); -} - -/** - * @ingroup iface_ext_idle_notifier_v1 - * - * Destroy the manager object. All objects created via this interface - * remain valid. - */ -static inline void -ext_idle_notifier_v1_destroy(struct ext_idle_notifier_v1 *ext_idle_notifier_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) ext_idle_notifier_v1, - EXT_IDLE_NOTIFIER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) ext_idle_notifier_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_ext_idle_notifier_v1 - * - * Create a new idle notification object. - * - * The notification object has a minimum timeout duration and is tied to a - * seat. The client will be notified if the seat is inactive for at least - * the provided timeout. See ext_idle_notification_v1 for more details. - * - * A zero timeout is valid and means the client wants to be notified as - * soon as possible when the seat is inactive. - */ -static inline struct ext_idle_notification_v1 * -ext_idle_notifier_v1_get_idle_notification(struct ext_idle_notifier_v1 *ext_idle_notifier_v1, uint32_t timeout, struct wl_seat *seat) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) ext_idle_notifier_v1, - EXT_IDLE_NOTIFIER_V1_GET_IDLE_NOTIFICATION, &ext_idle_notification_v1_interface, wl_proxy_get_version((struct wl_proxy *) ext_idle_notifier_v1), 0, NULL, timeout, seat); - - return (struct ext_idle_notification_v1 *) id; -} - -/** - * @ingroup iface_ext_idle_notifier_v1 - * - * Create a new idle notification object to track input from the - * user, such as keyboard and mouse movement. Because this object is - * meant to track user input alone, it ignores idle inhibitors. - * - * The notification object has a minimum timeout duration and is tied to a - * seat. The client will be notified if the seat is inactive for at least - * the provided timeout. See ext_idle_notification_v1 for more details. - * - * A zero timeout is valid and means the client wants to be notified as - * soon as possible when the seat is inactive. - */ -static inline struct ext_idle_notification_v1 * -ext_idle_notifier_v1_get_input_idle_notification(struct ext_idle_notifier_v1 *ext_idle_notifier_v1, uint32_t timeout, struct wl_seat *seat) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) ext_idle_notifier_v1, - EXT_IDLE_NOTIFIER_V1_GET_INPUT_IDLE_NOTIFICATION, &ext_idle_notification_v1_interface, wl_proxy_get_version((struct wl_proxy *) ext_idle_notifier_v1), 0, NULL, timeout, seat); - - return (struct ext_idle_notification_v1 *) id; -} - -/** - * @ingroup iface_ext_idle_notification_v1 - * @struct ext_idle_notification_v1_listener - */ -struct ext_idle_notification_v1_listener { - /** - * notification object is idle - * - * This event is sent when the notification object becomes idle. - * - * It's a compositor protocol error to send this event twice - * without a resumed event in-between. - */ - void (*idled)(void *data, - struct ext_idle_notification_v1 *ext_idle_notification_v1); - /** - * notification object is no longer idle - * - * This event is sent when the notification object stops being - * idle. - * - * It's a compositor protocol error to send this event twice - * without an idled event in-between. It's a compositor protocol - * error to send this event prior to any idled event. - */ - void (*resumed)(void *data, - struct ext_idle_notification_v1 *ext_idle_notification_v1); -}; - -/** - * @ingroup iface_ext_idle_notification_v1 - */ -static inline int -ext_idle_notification_v1_add_listener(struct ext_idle_notification_v1 *ext_idle_notification_v1, - const struct ext_idle_notification_v1_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) ext_idle_notification_v1, - (void (**)(void)) listener, data); -} - -#define EXT_IDLE_NOTIFICATION_V1_DESTROY 0 - -/** - * @ingroup iface_ext_idle_notification_v1 - */ -#define EXT_IDLE_NOTIFICATION_V1_IDLED_SINCE_VERSION 1 -/** - * @ingroup iface_ext_idle_notification_v1 - */ -#define EXT_IDLE_NOTIFICATION_V1_RESUMED_SINCE_VERSION 1 - -/** - * @ingroup iface_ext_idle_notification_v1 - */ -#define EXT_IDLE_NOTIFICATION_V1_DESTROY_SINCE_VERSION 1 - -/** @ingroup iface_ext_idle_notification_v1 */ -static inline void -ext_idle_notification_v1_set_user_data(struct ext_idle_notification_v1 *ext_idle_notification_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) ext_idle_notification_v1, user_data); -} - -/** @ingroup iface_ext_idle_notification_v1 */ -static inline void * -ext_idle_notification_v1_get_user_data(struct ext_idle_notification_v1 *ext_idle_notification_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) ext_idle_notification_v1); -} - -static inline uint32_t -ext_idle_notification_v1_get_version(struct ext_idle_notification_v1 *ext_idle_notification_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) ext_idle_notification_v1); -} - -/** - * @ingroup iface_ext_idle_notification_v1 - * - * Destroy the notification object. - */ -static inline void -ext_idle_notification_v1_destroy(struct ext_idle_notification_v1 *ext_idle_notification_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) ext_idle_notification_v1, - EXT_IDLE_NOTIFICATION_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) ext_idle_notification_v1), WL_MARSHAL_FLAG_DESTROY); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/ext-idle-notify-v1-protocol.c b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/ext-idle-notify-v1-protocol.c deleted file mode 100644 index 56871e977d6b..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/ext-idle-notify-v1-protocol.c +++ /dev/null @@ -1,80 +0,0 @@ -/* Generated by wayland-scanner 1.26.0 */ - -/* - * Copyright © 2015 Martin Gräßlin - * Copyright © 2022 Simon Ser - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include <stdbool.h> -#include <stdlib.h> -#include <stdint.h> -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface ext_idle_notification_v1_interface; -extern const struct wl_interface wl_seat_interface; - -static const struct wl_interface *ext_idle_notify_v1_types[] = { - &ext_idle_notification_v1_interface, - NULL, - &wl_seat_interface, - &ext_idle_notification_v1_interface, - NULL, - &wl_seat_interface, -}; - -static const struct wl_message ext_idle_notifier_v1_requests[] = { - { "destroy", "", ext_idle_notify_v1_types + 0 }, - { "get_idle_notification", "nuo", ext_idle_notify_v1_types + 0 }, - { "get_input_idle_notification", "2nuo", ext_idle_notify_v1_types + 3 }, -}; - -WL_PRIVATE const struct wl_interface ext_idle_notifier_v1_interface = { - "ext_idle_notifier_v1", 2, - 3, ext_idle_notifier_v1_requests, - 0, NULL, -}; - -static const struct wl_message ext_idle_notification_v1_requests[] = { - { "destroy", "", ext_idle_notify_v1_types + 0 }, -}; - -static const struct wl_message ext_idle_notification_v1_events[] = { - { "idled", "", ext_idle_notify_v1_types + 0 }, - { "resumed", "", ext_idle_notify_v1_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface ext_idle_notification_v1_interface = { - "ext_idle_notification_v1", 2, - 1, ext_idle_notification_v1_requests, - 2, ext_idle_notification_v1_events, -}; - diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/ext-session-lock-v1-client-protocol.h b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/ext-session-lock-v1-client-protocol.h deleted file mode 100644 index ac2da228101d..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/ext-session-lock-v1-client-protocol.h +++ /dev/null @@ -1,684 +0,0 @@ -/* Generated by wayland-scanner 1.26.0 */ - -#ifndef EXT_SESSION_LOCK_V1_CLIENT_PROTOCOL_H -#define EXT_SESSION_LOCK_V1_CLIENT_PROTOCOL_H - -#include <stdint.h> -#include <stddef.h> -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_ext_session_lock_v1 The ext_session_lock_v1 protocol - * secure session locking with arbitrary graphics - * - * @section page_desc_ext_session_lock_v1 Description - * - * This protocol allows for a privileged Wayland client to lock the session - * and display arbitrary graphics while the session is locked. - * - * The compositor may choose to restrict this protocol to a special client - * launched by the compositor itself or expose it to all privileged clients, - * this is compositor policy. - * - * The client is responsible for performing authentication and informing the - * compositor when the session should be unlocked. If the client dies while - * the session is locked the session remains locked, possibly permanently - * depending on compositor policy. - * - * The key words "must", "must not", "required", "shall", "shall not", - * "should", "should not", "recommended", "may", and "optional" in this - * document are to be interpreted as described in IETF RFC 2119. - * - * Warning! The protocol described in this file is currently in the - * testing phase. Backward compatible changes may be added together with - * the corresponding interface version bump. Backward incompatible changes - * can only be done by creating a new major version of the extension. - * - * @section page_ifaces_ext_session_lock_v1 Interfaces - * - @subpage page_iface_ext_session_lock_manager_v1 - used to lock the session - * - @subpage page_iface_ext_session_lock_v1 - manage lock state and create lock surfaces - * - @subpage page_iface_ext_session_lock_surface_v1 - a surface displayed while the session is locked - * @section page_copyright_ext_session_lock_v1 Copyright - * <pre> - * - * Copyright 2021 Isaac Freund - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * </pre> - */ -struct ext_session_lock_manager_v1; -struct ext_session_lock_surface_v1; -struct ext_session_lock_v1; -struct wl_output; -struct wl_surface; - -#ifndef EXT_SESSION_LOCK_MANAGER_V1_INTERFACE -#define EXT_SESSION_LOCK_MANAGER_V1_INTERFACE -/** - * @page page_iface_ext_session_lock_manager_v1 ext_session_lock_manager_v1 - * @section page_iface_ext_session_lock_manager_v1_desc Description - * - * This interface is used to request that the session be locked. - * @section page_iface_ext_session_lock_manager_v1_api API - * See @ref iface_ext_session_lock_manager_v1. - */ -/** - * @defgroup iface_ext_session_lock_manager_v1 The ext_session_lock_manager_v1 interface - * - * This interface is used to request that the session be locked. - */ -extern const struct wl_interface ext_session_lock_manager_v1_interface; -#endif -#ifndef EXT_SESSION_LOCK_V1_INTERFACE -#define EXT_SESSION_LOCK_V1_INTERFACE -/** - * @page page_iface_ext_session_lock_v1 ext_session_lock_v1 - * @section page_iface_ext_session_lock_v1_desc Description - * - * In response to the creation of this object the compositor must send - * either the locked or finished event. - * - * The locked event indicates that the session is locked. This means - * that the compositor must stop rendering and providing input to normal - * clients. Instead the compositor must blank all outputs with an opaque - * color such that their normal content is fully hidden. - * - * The only surfaces that should be rendered while the session is locked - * are the lock surfaces created through this interface and optionally, - * at the compositor's discretion, special privileged surfaces such as - * input methods or portions of desktop shell UIs. - * - * The locked event must not be sent until a new "locked" frame (either - * from a session lock surface or the compositor blanking the output) has - * been presented on all outputs and no security sensitive normal/unlocked - * content is possibly visible. - * - * The finished event should be sent immediately on creation of this - * object if the compositor decides that the locked event will not be sent. - * - * The compositor may wait for the client to create and render session lock - * surfaces before sending the locked event to avoid displaying intermediate - * blank frames. However, it must impose a reasonable time limit if - * waiting and send the locked event as soon as the hard requirements - * described above can be met if the time limit expires. Clients should - * immediately create lock surfaces for all outputs on creation of this - * object to make this possible. - * - * This behavior of the locked event is required in order to prevent - * possible race conditions with clients that wish to suspend the system - * or similar after locking the session. Without these semantics, clients - * triggering a suspend after receiving the locked event would race with - * the first "locked" frame being presented and normal/unlocked frames - * might be briefly visible as the system is resumed if the suspend - * operation wins the race. - * - * If the client dies while the session is locked, the compositor must not - * unlock the session in response. It is acceptable for the session to be - * permanently locked if this happens. The compositor may choose to continue - * to display the lock surfaces the client had mapped before it died or - * alternatively fall back to a solid color, this is compositor policy. - * - * Compositors may also allow a secure way to recover the session, the - * details of this are compositor policy. Compositors may allow a new - * client to create a ext_session_lock_v1 object and take responsibility - * for unlocking the session, they may even start a new lock client - * instance automatically. - * @section page_iface_ext_session_lock_v1_api API - * See @ref iface_ext_session_lock_v1. - */ -/** - * @defgroup iface_ext_session_lock_v1 The ext_session_lock_v1 interface - * - * In response to the creation of this object the compositor must send - * either the locked or finished event. - * - * The locked event indicates that the session is locked. This means - * that the compositor must stop rendering and providing input to normal - * clients. Instead the compositor must blank all outputs with an opaque - * color such that their normal content is fully hidden. - * - * The only surfaces that should be rendered while the session is locked - * are the lock surfaces created through this interface and optionally, - * at the compositor's discretion, special privileged surfaces such as - * input methods or portions of desktop shell UIs. - * - * The locked event must not be sent until a new "locked" frame (either - * from a session lock surface or the compositor blanking the output) has - * been presented on all outputs and no security sensitive normal/unlocked - * content is possibly visible. - * - * The finished event should be sent immediately on creation of this - * object if the compositor decides that the locked event will not be sent. - * - * The compositor may wait for the client to create and render session lock - * surfaces before sending the locked event to avoid displaying intermediate - * blank frames. However, it must impose a reasonable time limit if - * waiting and send the locked event as soon as the hard requirements - * described above can be met if the time limit expires. Clients should - * immediately create lock surfaces for all outputs on creation of this - * object to make this possible. - * - * This behavior of the locked event is required in order to prevent - * possible race conditions with clients that wish to suspend the system - * or similar after locking the session. Without these semantics, clients - * triggering a suspend after receiving the locked event would race with - * the first "locked" frame being presented and normal/unlocked frames - * might be briefly visible as the system is resumed if the suspend - * operation wins the race. - * - * If the client dies while the session is locked, the compositor must not - * unlock the session in response. It is acceptable for the session to be - * permanently locked if this happens. The compositor may choose to continue - * to display the lock surfaces the client had mapped before it died or - * alternatively fall back to a solid color, this is compositor policy. - * - * Compositors may also allow a secure way to recover the session, the - * details of this are compositor policy. Compositors may allow a new - * client to create a ext_session_lock_v1 object and take responsibility - * for unlocking the session, they may even start a new lock client - * instance automatically. - */ -extern const struct wl_interface ext_session_lock_v1_interface; -#endif -#ifndef EXT_SESSION_LOCK_SURFACE_V1_INTERFACE -#define EXT_SESSION_LOCK_SURFACE_V1_INTERFACE -/** - * @page page_iface_ext_session_lock_surface_v1 ext_session_lock_surface_v1 - * @section page_iface_ext_session_lock_surface_v1_desc Description - * - * The client may use lock surfaces to display a screensaver, render a - * dialog to enter a password and unlock the session, or however else it - * sees fit. - * - * On binding this interface the compositor will immediately send the - * first configure event. After making the ack_configure request in - * response to this event the client should attach and commit the first - * buffer. Committing the surface before acking the first configure is a - * protocol error. Committing the surface with a null buffer at any time - * is a protocol error. - * - * The compositor is free to handle keyboard/pointer focus for lock - * surfaces however it chooses. A reasonable way to do this would be to - * give the first lock surface created keyboard focus and change keyboard - * focus if the user clicks on other surfaces. - * @section page_iface_ext_session_lock_surface_v1_api API - * See @ref iface_ext_session_lock_surface_v1. - */ -/** - * @defgroup iface_ext_session_lock_surface_v1 The ext_session_lock_surface_v1 interface - * - * The client may use lock surfaces to display a screensaver, render a - * dialog to enter a password and unlock the session, or however else it - * sees fit. - * - * On binding this interface the compositor will immediately send the - * first configure event. After making the ack_configure request in - * response to this event the client should attach and commit the first - * buffer. Committing the surface before acking the first configure is a - * protocol error. Committing the surface with a null buffer at any time - * is a protocol error. - * - * The compositor is free to handle keyboard/pointer focus for lock - * surfaces however it chooses. A reasonable way to do this would be to - * give the first lock surface created keyboard focus and change keyboard - * focus if the user clicks on other surfaces. - */ -extern const struct wl_interface ext_session_lock_surface_v1_interface; -#endif - -#define EXT_SESSION_LOCK_MANAGER_V1_DESTROY 0 -#define EXT_SESSION_LOCK_MANAGER_V1_LOCK 1 - - -/** - * @ingroup iface_ext_session_lock_manager_v1 - */ -#define EXT_SESSION_LOCK_MANAGER_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_ext_session_lock_manager_v1 - */ -#define EXT_SESSION_LOCK_MANAGER_V1_LOCK_SINCE_VERSION 1 - -/** @ingroup iface_ext_session_lock_manager_v1 */ -static inline void -ext_session_lock_manager_v1_set_user_data(struct ext_session_lock_manager_v1 *ext_session_lock_manager_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) ext_session_lock_manager_v1, user_data); -} - -/** @ingroup iface_ext_session_lock_manager_v1 */ -static inline void * -ext_session_lock_manager_v1_get_user_data(struct ext_session_lock_manager_v1 *ext_session_lock_manager_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) ext_session_lock_manager_v1); -} - -static inline uint32_t -ext_session_lock_manager_v1_get_version(struct ext_session_lock_manager_v1 *ext_session_lock_manager_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) ext_session_lock_manager_v1); -} - -/** - * @ingroup iface_ext_session_lock_manager_v1 - * - * This informs the compositor that the session lock manager object will - * no longer be used. Existing objects created through this interface - * remain valid. - */ -static inline void -ext_session_lock_manager_v1_destroy(struct ext_session_lock_manager_v1 *ext_session_lock_manager_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) ext_session_lock_manager_v1, - EXT_SESSION_LOCK_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) ext_session_lock_manager_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_ext_session_lock_manager_v1 - * - * This request creates a session lock and asks the compositor to lock the - * session. The compositor will send either the ext_session_lock_v1.locked - * or ext_session_lock_v1.finished event on the created object in - * response to this request. - */ -static inline struct ext_session_lock_v1 * -ext_session_lock_manager_v1_lock(struct ext_session_lock_manager_v1 *ext_session_lock_manager_v1) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) ext_session_lock_manager_v1, - EXT_SESSION_LOCK_MANAGER_V1_LOCK, &ext_session_lock_v1_interface, wl_proxy_get_version((struct wl_proxy *) ext_session_lock_manager_v1), 0, NULL); - - return (struct ext_session_lock_v1 *) id; -} - -#ifndef EXT_SESSION_LOCK_V1_ERROR_ENUM -#define EXT_SESSION_LOCK_V1_ERROR_ENUM -enum ext_session_lock_v1_error { - /** - * attempted to destroy session lock while locked - */ - EXT_SESSION_LOCK_V1_ERROR_INVALID_DESTROY = 0, - /** - * unlock requested but locked event was never sent - */ - EXT_SESSION_LOCK_V1_ERROR_INVALID_UNLOCK = 1, - /** - * given wl_surface already has a role - */ - EXT_SESSION_LOCK_V1_ERROR_ROLE = 2, - /** - * given output already has a lock surface - */ - EXT_SESSION_LOCK_V1_ERROR_DUPLICATE_OUTPUT = 3, - /** - * given wl_surface has a buffer attached or committed - */ - EXT_SESSION_LOCK_V1_ERROR_ALREADY_CONSTRUCTED = 4, -}; -#endif /* EXT_SESSION_LOCK_V1_ERROR_ENUM */ - -/** - * @ingroup iface_ext_session_lock_v1 - * @struct ext_session_lock_v1_listener - */ -struct ext_session_lock_v1_listener { - /** - * session successfully locked - * - * This client is now responsible for displaying graphics while - * the session is locked and deciding when to unlock the session. - * - * The locked event must not be sent until a new "locked" frame has - * been presented on all outputs and no security sensitive - * normal/unlocked content is possibly visible. - * - * If this event is sent, making the destroy request is a protocol - * error, the lock object must be destroyed using the - * unlock_and_destroy request. - */ - void (*locked)(void *data, - struct ext_session_lock_v1 *ext_session_lock_v1); - /** - * the session lock object should be destroyed - * - * The compositor has decided that the session lock should be - * destroyed as it will no longer be used by the compositor. - * Exactly when this event is sent is compositor policy, but it - * must never be sent more than once for a given session lock - * object. - * - * This might be sent because there is already another - * ext_session_lock_v1 object held by a client, or the compositor - * has decided to deny the request to lock the session for some - * other reason. This might also be sent because the compositor - * implements some alternative, secure way to authenticate and - * unlock the session. - * - * The finished event should be sent immediately on creation of - * this object if the compositor decides that the locked event will - * not be sent. - * - * If the locked event is sent on creation of this object the - * finished event may still be sent at some later time in this - * object's lifetime. This is compositor policy. - * - * Upon receiving this event, the client should make either the - * destroy request or the unlock_and_destroy request, depending on - * whether or not the locked event was received on this object. - */ - void (*finished)(void *data, - struct ext_session_lock_v1 *ext_session_lock_v1); -}; - -/** - * @ingroup iface_ext_session_lock_v1 - */ -static inline int -ext_session_lock_v1_add_listener(struct ext_session_lock_v1 *ext_session_lock_v1, - const struct ext_session_lock_v1_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) ext_session_lock_v1, - (void (**)(void)) listener, data); -} - -#define EXT_SESSION_LOCK_V1_DESTROY 0 -#define EXT_SESSION_LOCK_V1_GET_LOCK_SURFACE 1 -#define EXT_SESSION_LOCK_V1_UNLOCK_AND_DESTROY 2 - -/** - * @ingroup iface_ext_session_lock_v1 - */ -#define EXT_SESSION_LOCK_V1_LOCKED_SINCE_VERSION 1 -/** - * @ingroup iface_ext_session_lock_v1 - */ -#define EXT_SESSION_LOCK_V1_FINISHED_SINCE_VERSION 1 - -/** - * @ingroup iface_ext_session_lock_v1 - */ -#define EXT_SESSION_LOCK_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_ext_session_lock_v1 - */ -#define EXT_SESSION_LOCK_V1_GET_LOCK_SURFACE_SINCE_VERSION 1 -/** - * @ingroup iface_ext_session_lock_v1 - */ -#define EXT_SESSION_LOCK_V1_UNLOCK_AND_DESTROY_SINCE_VERSION 1 - -/** @ingroup iface_ext_session_lock_v1 */ -static inline void -ext_session_lock_v1_set_user_data(struct ext_session_lock_v1 *ext_session_lock_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) ext_session_lock_v1, user_data); -} - -/** @ingroup iface_ext_session_lock_v1 */ -static inline void * -ext_session_lock_v1_get_user_data(struct ext_session_lock_v1 *ext_session_lock_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) ext_session_lock_v1); -} - -static inline uint32_t -ext_session_lock_v1_get_version(struct ext_session_lock_v1 *ext_session_lock_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) ext_session_lock_v1); -} - -/** - * @ingroup iface_ext_session_lock_v1 - * - * This informs the compositor that the lock object will no longer be - * used. Existing objects created through this interface remain valid. - * - * After this request is made, lock surfaces created through this object - * should be destroyed by the client as they will no longer be used by - * the compositor. - * - * It is a protocol error to make this request if the locked event was - * sent, the unlock_and_destroy request must be used instead. - */ -static inline void -ext_session_lock_v1_destroy(struct ext_session_lock_v1 *ext_session_lock_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) ext_session_lock_v1, - EXT_SESSION_LOCK_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) ext_session_lock_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_ext_session_lock_v1 - * - * The client is expected to create lock surfaces for all outputs - * currently present and any new outputs as they are advertised. These - * won't be displayed by the compositor unless the lock is successful - * and the locked event is sent. - * - * Providing a wl_surface which already has a role or already has a buffer - * attached or committed is a protocol error, as is attaching/committing - * a buffer before the first ext_session_lock_surface_v1.configure event. - * - * Attempting to create more than one lock surface for a given output - * is a duplicate_output protocol error. - */ -static inline struct ext_session_lock_surface_v1 * -ext_session_lock_v1_get_lock_surface(struct ext_session_lock_v1 *ext_session_lock_v1, struct wl_surface *surface, struct wl_output *output) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) ext_session_lock_v1, - EXT_SESSION_LOCK_V1_GET_LOCK_SURFACE, &ext_session_lock_surface_v1_interface, wl_proxy_get_version((struct wl_proxy *) ext_session_lock_v1), 0, NULL, surface, output); - - return (struct ext_session_lock_surface_v1 *) id; -} - -/** - * @ingroup iface_ext_session_lock_v1 - * - * This request indicates that the session should be unlocked, for - * example because the user has entered their password and it has been - * verified by the client. - * - * This request also informs the compositor that the lock object will - * no longer be used and should be destroyed. Existing objects created - * through this interface remain valid. - * - * After this request is made, lock surfaces created through this object - * should be destroyed by the client as they will no longer be used by - * the compositor. - * - * It is a protocol error to make this request if the locked event has - * not been sent. In that case, the lock object must be destroyed using - * the destroy request. - * - * Note that a correct client that wishes to exit directly after unlocking - * the session must use the wl_display.sync request to ensure the server - * receives and processes the unlock_and_destroy request. Otherwise - * there is no guarantee that the server has unlocked the session due - * to the asynchronous nature of the Wayland protocol. For example, - * the server might terminate the client with a protocol error before - * it processes the unlock_and_destroy request. - */ -static inline void -ext_session_lock_v1_unlock_and_destroy(struct ext_session_lock_v1 *ext_session_lock_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) ext_session_lock_v1, - EXT_SESSION_LOCK_V1_UNLOCK_AND_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) ext_session_lock_v1), WL_MARSHAL_FLAG_DESTROY); -} - -#ifndef EXT_SESSION_LOCK_SURFACE_V1_ERROR_ENUM -#define EXT_SESSION_LOCK_SURFACE_V1_ERROR_ENUM -enum ext_session_lock_surface_v1_error { - /** - * surface committed before first ack_configure request - */ - EXT_SESSION_LOCK_SURFACE_V1_ERROR_COMMIT_BEFORE_FIRST_ACK = 0, - /** - * surface committed with a null buffer - */ - EXT_SESSION_LOCK_SURFACE_V1_ERROR_NULL_BUFFER = 1, - /** - * failed to match ack'd width/height - */ - EXT_SESSION_LOCK_SURFACE_V1_ERROR_DIMENSIONS_MISMATCH = 2, - /** - * serial provided in ack_configure is invalid - */ - EXT_SESSION_LOCK_SURFACE_V1_ERROR_INVALID_SERIAL = 3, -}; -#endif /* EXT_SESSION_LOCK_SURFACE_V1_ERROR_ENUM */ - -/** - * @ingroup iface_ext_session_lock_surface_v1 - * @struct ext_session_lock_surface_v1_listener - */ -struct ext_session_lock_surface_v1_listener { - /** - * the client should resize its surface - * - * This event is sent once on binding the interface and may be - * sent again at the compositor's discretion, for example if output - * geometry changes. - * - * The width and height are in surface-local coordinates and are - * exact requirements. Failing to match these surface dimensions in - * the next commit after acking a configure is a protocol error. - * @param serial serial for use in ack_configure - */ - void (*configure)(void *data, - struct ext_session_lock_surface_v1 *ext_session_lock_surface_v1, - uint32_t serial, - uint32_t width, - uint32_t height); -}; - -/** - * @ingroup iface_ext_session_lock_surface_v1 - */ -static inline int -ext_session_lock_surface_v1_add_listener(struct ext_session_lock_surface_v1 *ext_session_lock_surface_v1, - const struct ext_session_lock_surface_v1_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) ext_session_lock_surface_v1, - (void (**)(void)) listener, data); -} - -#define EXT_SESSION_LOCK_SURFACE_V1_DESTROY 0 -#define EXT_SESSION_LOCK_SURFACE_V1_ACK_CONFIGURE 1 - -/** - * @ingroup iface_ext_session_lock_surface_v1 - */ -#define EXT_SESSION_LOCK_SURFACE_V1_CONFIGURE_SINCE_VERSION 1 - -/** - * @ingroup iface_ext_session_lock_surface_v1 - */ -#define EXT_SESSION_LOCK_SURFACE_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_ext_session_lock_surface_v1 - */ -#define EXT_SESSION_LOCK_SURFACE_V1_ACK_CONFIGURE_SINCE_VERSION 1 - -/** @ingroup iface_ext_session_lock_surface_v1 */ -static inline void -ext_session_lock_surface_v1_set_user_data(struct ext_session_lock_surface_v1 *ext_session_lock_surface_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) ext_session_lock_surface_v1, user_data); -} - -/** @ingroup iface_ext_session_lock_surface_v1 */ -static inline void * -ext_session_lock_surface_v1_get_user_data(struct ext_session_lock_surface_v1 *ext_session_lock_surface_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) ext_session_lock_surface_v1); -} - -static inline uint32_t -ext_session_lock_surface_v1_get_version(struct ext_session_lock_surface_v1 *ext_session_lock_surface_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) ext_session_lock_surface_v1); -} - -/** - * @ingroup iface_ext_session_lock_surface_v1 - * - * This informs the compositor that the lock surface object will no - * longer be used. - * - * It is recommended for a lock client to destroy lock surfaces if - * their corresponding wl_output global is removed. - * - * If a lock surface on an active output is destroyed before the - * ext_session_lock_v1.unlock_and_destroy event is sent, the compositor - * must fall back to rendering a solid color. - */ -static inline void -ext_session_lock_surface_v1_destroy(struct ext_session_lock_surface_v1 *ext_session_lock_surface_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) ext_session_lock_surface_v1, - EXT_SESSION_LOCK_SURFACE_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) ext_session_lock_surface_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_ext_session_lock_surface_v1 - * - * 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. - * - * Sending an ack_configure request consumes the configure event - * referenced by the given serial, as well as all older configure events - * sent on this object. - * - * It is a protocol error to issue multiple ack_configure requests - * referencing the same configure event or to issue an ack_configure - * request referencing a configure event older than the last configure - * event acked for a given lock surface. - */ -static inline void -ext_session_lock_surface_v1_ack_configure(struct ext_session_lock_surface_v1 *ext_session_lock_surface_v1, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) ext_session_lock_surface_v1, - EXT_SESSION_LOCK_SURFACE_V1_ACK_CONFIGURE, NULL, wl_proxy_get_version((struct wl_proxy *) ext_session_lock_surface_v1), 0, serial); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/ext-session-lock-v1-protocol.c b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/ext-session-lock-v1-protocol.c deleted file mode 100644 index 5f9541520e54..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/ext-session-lock-v1-protocol.c +++ /dev/null @@ -1,97 +0,0 @@ -/* Generated by wayland-scanner 1.26.0 */ - -/* - * Copyright 2021 Isaac Freund - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -#include <stdbool.h> -#include <stdlib.h> -#include <stdint.h> -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface ext_session_lock_surface_v1_interface; -extern const struct wl_interface ext_session_lock_v1_interface; -extern const struct wl_interface wl_output_interface; -extern const struct wl_interface wl_surface_interface; - -static const struct wl_interface *ext_session_lock_v1_types[] = { - NULL, - NULL, - NULL, - &ext_session_lock_v1_interface, - &ext_session_lock_surface_v1_interface, - &wl_surface_interface, - &wl_output_interface, -}; - -static const struct wl_message ext_session_lock_manager_v1_requests[] = { - { "destroy", "", ext_session_lock_v1_types + 0 }, - { "lock", "n", ext_session_lock_v1_types + 3 }, -}; - -WL_PRIVATE const struct wl_interface ext_session_lock_manager_v1_interface = { - "ext_session_lock_manager_v1", 1, - 2, ext_session_lock_manager_v1_requests, - 0, NULL, -}; - -static const struct wl_message ext_session_lock_v1_requests[] = { - { "destroy", "", ext_session_lock_v1_types + 0 }, - { "get_lock_surface", "noo", ext_session_lock_v1_types + 4 }, - { "unlock_and_destroy", "", ext_session_lock_v1_types + 0 }, -}; - -static const struct wl_message ext_session_lock_v1_events[] = { - { "locked", "", ext_session_lock_v1_types + 0 }, - { "finished", "", ext_session_lock_v1_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface ext_session_lock_v1_interface = { - "ext_session_lock_v1", 1, - 3, ext_session_lock_v1_requests, - 2, ext_session_lock_v1_events, -}; - -static const struct wl_message ext_session_lock_surface_v1_requests[] = { - { "destroy", "", ext_session_lock_v1_types + 0 }, - { "ack_configure", "u", ext_session_lock_v1_types + 0 }, -}; - -static const struct wl_message ext_session_lock_surface_v1_events[] = { - { "configure", "uuu", ext_session_lock_v1_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface ext_session_lock_surface_v1_interface = { - "ext_session_lock_surface_v1", 1, - 2, ext_session_lock_surface_v1_requests, - 1, ext_session_lock_surface_v1_events, -}; - diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/hide-desktop.md b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/hide-desktop.md deleted file mode 100644 index 7ed57b66dff0..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/hide-desktop.md +++ /dev/null @@ -1,42 +0,0 @@ -# Wallpaper-only rain - -This integration is included in Ambient Background. See the configuration guide for current file paths and the WCM controls for continuous live wallpapers; helper and socket identifiers retain their compatible names. - -See [Configuration](configuration.md) for the full option reference, defaults -and reload rules. - -Add these settings to the `[matrix]` section of your digitalrain configuration: - -```toml -[matrix] -TRANSPARENT = true -HIDE_DESKTOP = true -``` - -Enable `digitalrain-desktop` alongside `ipc` in Wayfire's `[core] plugins` list. -The plugin can be loaded by reloading the Wayfire configuration; a compositor -restart is not required. Restart digitalrain after installing a new binary. -The manager reloads changed settings for the next renderer activation. - -Apps, panels, popups and desktop widgets fade to fully transparent while rain -fades in. The wallpaper stays visible until `TRANSPARENT_TIMEOUT` fades the -background to black. Activity fades the desktop back in as the rain/black overlay -fades away. `FADE_FRAMES` controls both directions at 30 frames per -second: 20 frames is about 0.67 seconds, 30 frames is about one second. - -The packaged default is `HIDE_DESKTOP = false`, which keeps desktop content visible. -The option only takes effect with `TRANSPARENT = true`. Renderers can also opt in -with `ambient-idle --render --output NAME --transparent --hide-desktop`. - -The effect applies separately to each sleeping output and includes newly opened -windows. Window opacity, workspace, minimization and stacking settings are not -rewritten. If activity resumes during the fade-out, components fade back in from -their current opacity without jumping. Session-lock surfaces remain visible -above the saver. - -The renderer holds an IPC connection for the effect's lifetime. Closing that -connection, a renderer crash, plugin unload or output removal immediately releases -the effect; normal activity requests a fade-in first. Once a fade finishes, the -plugin stops its animation timer. Without -the plugin, rain continues with the desktop visible and the renderer reports -that the optional effect is unavailable. diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/interfaces-and-locking.md b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/interfaces-and-locking.md deleted file mode 100644 index 29bdbefc598e..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/interfaces-and-locking.md +++ /dev/null @@ -1,209 +0,0 @@ -# Local control API and native session locking - -This integration is included in Ambient Background. See the configuration guide for current file paths and the WCM controls for continuous live wallpapers; helper and socket identifiers retain their compatible names. - -Digital Rain 1.2.0 provides native Wayland session locking, a local manager API, -and an optional stdio MCP adapter. The normal saver keeps independent physical -monitor timers. Locking protects the whole session across all outputs. - -## Start and control Digital Rain - -Run the manager once through Wayfire autostart. Commands connect to that manager: - -```sh -ambient-idle --status -ambient-idle --explain -ambient-idle --activate DP-1 -ambient-idle --black DP-1 -ambient-idle --wake DP-1 -ambient-idle --lock -ambient-idle --lock-status -ambient-idle --schema -ambient-idle --watch -``` - -Omit the output on activate/black/wake to target all physical monitors. Connector -names are reported by `--status`, including monitors sharing a virtual cube. -`--black` uses the configured monitor power policy. `--wake` resets the selected -monitor's idle countdown; while locked it can show the password prompt, but -cannot unlock. `--help` lists manager controls; `--render --help` lists renderer -arguments. - -## Native lock screen - -Enable `session-lock` and the current `digitalrain-idle` helper in Wayfire's -existing `[core] plugins` list. The former implements `ext-session-lock-v1`; -the latter reports compositor protection after a locker disconnects, allowing -manager restart and crash recovery. These can be loaded without restarting the -compositor. A shortcut can then run: - -```ini -[command] -binding_lock = <super> <alt> KEY_ESC -command_lock = ambient-idle --lock -``` - -`ambient-idle --lock` starts the independent `ambient-lock` process and waits -for confirmed session locking. Failure or lack of confirmation produces a -nonzero exit status. Starting rain or launching a process alone is not reported -as a confirmed lock. `ambient-lock` can also be run directly without the -manager; automatic recovery requires the manager and lock-status helper. - -The locker renders an opaque selected background inside actual session-lock surfaces above the -normal saver layer. Each logical output has a lock surface; on a stitched output -the prompt is repeated in its physical monitor regions. Newly connected outputs -receive lock surfaces, and resized surfaces follow compositor configuration. - -Type your password and press Enter. Backspace removes a character, Ctrl+U clears -input, and Escape clears and hides the prompt. Caps Lock is indicated. UTF-8 -keyboard input and compose sequences use the compositor's keymap. The prompt -hides and clears unfinished input after 15 seconds without activity. - -A separate unprivileged `ambient-auth` worker checks the current user's -password through `/etc/pam.d/digitalrain`, which includes the system authentication -and account policy. Passwords travel only through its private inherited pipe; -there is no password argument, API field, or MCP authentication tool. The UI -remains responsive while PAM checks the password. This interface supports password -authentication, not interactive multi-step PAM/MFA conversations. - -The selected lock effect starts on black, regardless of the normal saver's transparency -setting. It uses the configured rain appearance; adaptive colors use the saved -black-background palette without sampling desktop content during locking. -WCM’s Lock screen settings choose one physical monitor or all monitors for the -clock and unlock controls, while every output keeps its opaque protected -background. A disconnected selection falls back to an available monitor. The -controls start invisible, fade in on input and fade away after the configurable -idle delay, clearing unfinished passwords. Authentication in progress remains -visible. These settings also control effect selection, appearance, clock/date and -fade-to-black independently of Ambient. When Ambient is unavailable, -`BLACK_RAIN_TIMEOUT` controls when locked rain settles to black; zero keeps rain -running. A black lock surface stops animation. Input shows the prompt without -releasing protection. `POWER_OFF=false` keeps monitors powered for instant response; -opt-in global/per-output power settings work through `digitalrain-power`. - -Only successful PAM authentication requests unlock. Saver signals, normal input, -API wake, or restarting the idle manager do not unlock. If the locker crashes, -Wayfire retains protection; the manager restarts the locker with a bounded retry -delay. Without the manager, a replacement `ambient-lock` can recover the prompt. - -Automatic locking is independent of per-monitor saver timers and disabled by -this default: - -```toml -[global] -LOCK_TIMEOUT = 0 -``` - -A positive value requests a session lock after that many seconds of whole-seat -input inactivity, with approximately the idle notifier/manager scheduling delay. -Application and API saver inhibitors do not suppress automatic security locking. -Per-monitor activity on another screen still postpones the session-wide lock. -`LOCK_CMD` has been removed and is rejected in TOML; no external locker is launched. - -## API version 1 - -The manager listens on `$XDG_RUNTIME_DIR/digitalrain/control.sock`. The runtime -directory must belong to the user and be private. Digital Rain's subdirectory is -mode 0700, sockets are mode 0600, and accepted clients must have the same UID. -The transport is Unix `SOCK_SEQPACKET`: one UTF-8 JSON object per packet, at most -16 KiB. There is no network listener. Connections and queued traffic are bounded; -slow readers are disconnected instead of blocking rendering or monitor wake. - -Every request includes `"version":1` and `"method"`. A reply is a JSON object; -failures contain `"error"`. Unexpected fields, unknown outputs, unsupported -versions, and invalid setting types/ranges are rejected. Process commands in -sequence on a connection. `watch` returns an initial snapshot followed by state -changes; it does not emit frame ticks or continuously changing countdown values. - -| Method | Additional fields | Behavior | -| --- | --- | --- | -| `reload` | None | Validate and reload the selected TOML file; keep the old configuration on error. | -| `alert`, `clear_alert`, `list_alerts` | See [alerts and audio](alerts-and-audio.md) | Temporary visual status, with bounded lifetime and priorities. | -| `status`, `explain` | None | Outputs, phases, focus, input-idle timing, inhibitors, power state, and reasons. | -| `activate`, `black`, `wake` | Optional `output` | Control one physical monitor or all monitors. | -| `lock` | None | Request the native session-wide lock; the reply may still be `locking`. | -| `lock_status` | None | Read `state`; use this to observe an API lock request completing. | -| `watch` | None | Subscribe to changes in the manager snapshot. | -| `inhibit` | `seconds`, `reason`, optional `output` | Create a timed saver inhibitor and return its `id`; maximum 24 hours. | -| `uninhibit` | `id` | Release a timed inhibitor early. | -| `schema` | None | Supported API settings, types, defaults, ranges, current values, and application timing. | -| `settings` | `settings` object, optional boolean `save` | Apply validated settings; `save:true` also atomically updates the selected user-owned config. | -| `preview` | `settings` object, optional `output`, optional `seconds` | Temporarily render with settings for 15 seconds by default, up to 600; do not change saved settings. | - -Saver phase and session-lock state are separate. Lock states are `locking`, -`locked`, `locked_unavailable` (compositor protection with no connected locker), -`unlocked`, and `unknown` (no authoritative status available). A missing helper -or disconnected locker is not assumed to mean unlocked. Protection status from -Wayfire and the locker's confirmed protocol event drive these transitions. - -Settings use `section.KEY` names. API schema covers numeric global/matrix controls -and `matrix.COLOR`. Edit paths and per-output configuration sections in the config -file. Runtime settings last until overwritten by a later config reload or manager -restart. Saved TOML writes preserve unrelated entries and comments, validate the result, and reject detected concurrent edits; saving a config -owned by another user, such as a system file, is rejected. - -Examples: - -```sh -ambient-idle --request '{"version":1,"method":"inhibit","seconds":600,"reason":"presentation"}' -ambient-idle --request '{"version":1,"method":"preview","output":"DP-1","seconds":20,"settings":{"matrix.COLOR":"cyan","matrix.FONT_SIZE":18}}' -ambient-idle --request '{"version":1,"method":"settings","save":true,"settings":{"global.LOCK_TIMEOUT":900}}' -``` - -A Python client needs only the standard library: - -```python -import json, os, socket -with socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) as client: - client.connect(os.environ['XDG_RUNTIME_DIR'] + '/digitalrain/control.sock') - client.sendall(json.dumps({'version': 1, 'method': 'status'}).encode()) - print(json.loads(client.recv(16384))) -``` - -The private `lock.sock` channel between manager and locker accepts only status, -watch, and wake, plus bounded private appearance snapshots. It has no unlock or password operation. Monitor power leases are -released on wake, normal exit, or process death without affecting session locking. - -## Optional MCP adapter - -The Gentoo `mcp` USE flag installs `ambient-background-mcp`. Configure an MCP client to -launch that command as the desktop user with the same `XDG_RUNTIME_DIR`. No MCP -process runs until a client starts it. It translates tools and resources into the -same local API and has no renderer, PAM, shell execution, or HTTP server. - -Resources: `digitalrain://status`, `digitalrain://schema`. Tools: `status`, -`explain`, `activate`, `black`, `wake`, `lock`, `lock_status`, `inhibit`, `uninhibit`, -`preview`, `settings`, `reload`, `alert`, `clear_alert`, and `list_alerts`. `lock` requests locking; check `lock_status` for completion. -There is deliberately no unlock tool. Tool inputs and limits match the API table. - -The adapter uses MCP [stdio transport](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports) -and [initialization negotiation](https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle). -An HTTP adapter remains future work. - -## Performance and validation - -The API uses the manager's existing timed waits rather than adding a polling -thread or timer. Renderers report only phase/power transitions over a private -socket; they do not serialize JSON or serve clients. The locker and PAM worker -run only when needed. Monitor fades are reaped asynchronously so they do not -block API requests or another monitor's wake. - -Historical 1.1.0 baseline: in five-second isolated comparisons on the development machine, the old and new -manager both used less than one CPU accounting tick with no clients and had -17 voluntary context switches per second. The new manager used 92 KiB more RSS; -an idle event subscriber added 40 KiB and about 0.2 switches/second. Both rain -renderers measured 0.4% CPU and 90.2 switches/second. These short samples support -low overhead; they are not a universal zero-cost guarantee. Wayland commit traces -measured median frame intervals of 33.390 ms before and 33.387 ms after, with -95th percentiles of 33.429 ms and 33.426 ms respectively (roughly 30 fps). - -Tests cover incorrect/correct PAM authentication using a separate test-only PAM -policy, locked output coverage, black idle, duplicate lockers, crash recovery, -manager restart, input/API wake isolation, hot-plugging, and opt-in power-off. -The API tests cover validation, save/preview separation, timed inhibitors, -events, oversized packets, and MCP initialization/tools/resources. Existing -stitched-output, application-inhibition, topology, glyph-compositing, and manager -ownership regressions remain part of validation. No integration test locks the -real desktop or needs the user's password. - -See [Configuration](configuration.md) and the [Wayfire session-lock implementation](https://github.com/WayfireWM/wayfire/blob/master/plugins/protocols/session-lock.cpp). diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/live-config-alerts-audio-plan.md b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/live-config-alerts-audio-plan.md deleted file mode 100644 index ed974e5fe54f..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/live-config-alerts-audio-plan.md +++ /dev/null @@ -1,363 +0,0 @@ -# Plan: live configuration, automation alerts, and audio-reactive rain - -This integration is included in Ambient Background. See the configuration guide for current file paths and the WCM controls for continuous live wallpapers; helper and socket identifiers retain their compatible names. - -Status: core delivery implemented in 1.2.0. This is the accepted design from -2026-09-27; the design discussion and proposed examples below are retained for -context. TOML/live appearance, alerts, and energy-mode audio are implemented. -Frequency bands, rhythmic-onset detection, displayed alert text, and waking black -screens for alerts remain future work. Actual interfaces and limits are documented -in [configuration.md](configuration.md), [interfaces-and-locking.md](interfaces-and-locking.md), -and [alerts-and-audio.md](alerts-and-audio.md). - -Digital Rain should respond to saved configuration, events from programs or -agents, and playing audio while keeping its independent physical-monitor timers -and inexpensive solid-black state. Build one live appearance-update path and use -it for all three features. - -## What is already available - -- The manager periodically rereads the existing INI-style configuration. Most - appearance settings apply when the next renderer starts; adaptive palettes - already have a limited update path to running renderers. -- The local API supports status, change subscriptions, settings, and temporary - previews. A preview starts a renderer and is unavailable during session locking; - it is not an alert mechanism that preserves the current animation and timers. -- The optional MCP adapter exposes the same API to agents. Ordinary programs can - use the CLI or local socket without an agent. -- Renderer-to-manager reporting currently carries phase/power transitions. - General manager-to-renderer appearance updates still need implementation. -- Native locking uses a separate process and actual Wayland lock surfaces. - Its private channel currently accepts status, watch, and wake only. -- This development desktop runs PipeWire and WirePlumber. Audio analysis is not - currently part of Digital Rain. - -## Intended experience - -Save a color or speed setting and see running rain change smoothly. A failed -backup can temporarily turn rain red; clearing the failure restores the current -normal palette. Playing music can gently increase the rain's speed and brightness -with its energy. Later, bass, midrange, and treble can influence different depth -layers or columns. - -An error color remains recognizable while music plays: alerts own the hue, and -audio can still influence motion within configured limits. The visual controls -affect Digital Rain's pixels; display brightness and panel styling are separate. - -Default behavior: - -| Situation | Planned response | -| --- | --- | -| Transparent rain or rain over black | Apply eligible alerts and audio effects. | -| Solid black or a powered-off monitor | Keep it that way; stop audio sampling when no other rain needs it. | -| A monitor is awake | Store eligible alerts until expiry; do not activate its saver. | -| Input resumes | Preserve normal wake behavior and independent monitor timers. | -| Session is locked | Visual alerts and audio require separate opt-in settings; authentication remains independent. | -| Playback stops or an audio source disappears | Smoothly return to normal rain. | -| Invalid configuration is saved | Keep the last valid configuration and report the error. | - -Keep `POWER_OFF = false`, `LOCK_TIMEOUT = 0`, and `HIDE_DESKTOP = false` for this -desktop. Existing application idle inhibitors retain their behavior; playing audio -does not automatically override them, activate rain, or extend a saver phase. - -## 1. TOML and reliable live reload - -Use a maintained C TOML parser with verified TOML 1.0 support, license, and Gentoo -packaging. Select the library during implementation after checking available -packages and parser tests. TOML defines data syntax; file watching and live -application are Digital Rain features. The [TOML 1.0 specification](https://toml.io/en/v1.0.0) -defines the baseline syntax for this plan. - -Keep familiar section names and setting names. Quote strings and hex colors, -use native booleans, and represent monitor overrides as tables. Introduce -`schema_version = 1` at the top level. Read one selected file, with no implicit -system/user merging: explicit `AMBIENT_BACKGROUND_CONFIG` (or legacy `DIGITALRAIN_CONFIG`), then the user location under -`XDG_CONFIG_HOME` or `~/.config`, then `/etc/ambient-background.toml`. Pin the selected -path for that manager lifetime so a temporary deletion cannot select another file. - -Provide a one-time conversion command that writes a separate `.toml` file and -reports obsolete or unconvertible entries. Preserve the original and compare -effective settings before switching. Preserve the user's actual values, including -`MIN_SPEED = 0.01`; the existing API's 0.1 minimum must be reconciled with the -renderer/config semantics rather than silently changing that value. Update the -system/user config, examples, package messages, tests, and documentation together. -The legacy parser can serve the converter; ongoing parallel config formats are -not required. - -Implement a dedicated configuration structure and one validator shared by file -loads and API updates. Parse each file into defaults plus explicit overrides. -Removing a setting then consistently restores its documented default. Reject -unknown keys, invalid types, non-finite numbers, and invalid relationships such -as minimum speed greater than maximum speed. Preserve the previous snapshot on -any reload error; report source path and line/key where available. An invalid -startup configuration produces a clear startup failure, never a silent fallback -that changes locking or power policy. - -Watch the containing directory with inotify so editor atomic replacement works. -Debounce save bursts, verify file identity/content, and recover watches after -directory replacement or event overflow. Use the existing manager housekeeping -as a low-frequency fallback. Add explicit `reload` and config-check commands. -Bound file size and parsing work. A missing file during replacement retains the -last valid configuration until a valid replacement arrives. - -Publish validated revisions and classify each setting in the API schema: - -| Setting class | Application | -| --- | --- | -| Palette, speed, alert/audio controls | Update running rain without resetting it. | -| Font size and depth/grid resources | Build replacement resources, then swap at a frame boundary; keep old resources on failure. | -| Idle thresholds | Reevaluate against existing activity timestamps on the next manager check. | -| Phase durations | Recalculate from the existing phase start; never move a black screen backward into rain. | -| Surface/background policy and monitor power | Apply on next activation unless a separately documented safe transition exists. | -| Chosen config path and logging destination | Explicit restart-required status. | - -Report desired and applied revisions, deferred settings, and renderer failures. -Do not claim a multi-process update is atomic if an individual renderer has not -applied it. Reloading appearance never restarts a locker or requests an unlock. - -Preserve API version 1's existing field names and integer boolean inputs through -an adapter while TOML uses booleans. Replace the current INI text writer: TOML -forbids duplicate keys/table definitions, so appending repeated sections is not -valid. Use syntax-aware edits that preserve comments, validate the complete result, -detect concurrent edits, and commit with a same-directory atomic rename. Runtime -settings retain the documented lifetime until a successful reload or restart. -Alert and audio values are transient and never written into base configuration. - -## 2. A shared live appearance path - -Extend the inherited renderer socket into a bounded, versioned, bidirectional -channel. Send small configuration/appearance snapshots and acknowledgements. -Retain phase/power reporting. Coalesce pending updates so slow consumers receive -the newest state, and reserve delivery for control transitions rather than letting -audio samples fill the queue. Include revision/sequence IDs and monotonic times. -Integrate the channel into the renderer's Wayland polling, including solid-black -waits, without introducing continuous wakeups in that phase. - -Separate the normal configuration from the effective appearance: - -```text -validated base settings + physical-monitor overrides - | - temporary preview, if any - | - highest-priority eligible alert - | - bounded audio modulation - | - current saver/lock phase policy - | - renderer at its existing frame cadence -``` - -Clamp the final result to brightness, speed, and density limits. An alert owns -hue while active; audio hue modulation is suppressed during that alert. Preview -temporarily suppresses alert/audio visuals on its selected output so the requested -appearance can be inspected; alert expiry continues normally. Clearing an alert -reveals the latest underlying state, not a stale snapshot from when it arrived. - -The separate locker needs its own narrow appearance-update operation. Render -per-physical-monitor effects inside a stitched lock surface using its existing -region mapping. Keep visual updates separate from wake/input, PAM, and unlock. -On manager/helper disconnection, temporary effects decay or expire locally while -the locker continues protecting the session. - -## 3. Automation alerts - -Add `alert`, `clear_alert`, and `list_alerts` to the local API, then expose them -through CLI and MCP tools. Extend status/watch with alert lifecycle and effective -appearance changes. These are events, not frame or audio-sample streams. - -Proposed request: - -```json -{ - "version": 1, - "method": "alert", - "source": "backup", - "id": "nightly", - "severity": "error", - "seconds": 60, - "output": "DP-1" -} -``` - -The same `(source, id)` updates an existing alert and renews its expiry. Clearing -that pair is idempotent. Omit `output` for all eligible physical monitors. Require -a finite duration from 1 to 86400 seconds; use monotonic expiry from acceptance, -including while black, awake, or disconnected. Programs renew ongoing conditions; -a crashed producer cannot leave an indefinite alert. An acknowledged alert survives -the producer closing its socket, but does not persist across manager restart. - -Keep at most 64 active alerts and bounded identifiers/messages; reject excess -requests clearly and limit per-client work so wake events stay responsive. -All producers use the existing same-user socket permissions. Source names are -labels, not additional authentication identities. - -Default priority: error, warning, success, info. A newer update wins a tie; a -lower-priority alert becomes visible if the winner expires and it is still valid. -Map severity to appearance in TOML. Validate a specifically named output at -acceptance; after a disconnect retain its alert until expiry and reapply only if -that physical output returns. Never transfer it to another monitor. - -Start with color and an optional gentle pulse, with transitions rather than -flashing. Message text can be stored as status metadata; rendered text is a later -feature, separately controlled for locked sessions. Black-screen alert presentation -is also a later explicit option. Initial alerts never resume animation from black -or power a monitor on. - -Ship examples for build completion/failure, backup status, and service health. -Programs publish changes through IPC; agents can use the equivalent MCP tools. -No shell hooks or script execution inside the renderer are needed. - -## 4. Audio-reactive rain - -Use an optional `ambient-audio` helper backed by native PipeWire. Capture the -selected playback sink's monitor; microphone input is outside the initial scope. -PipeWire provides `stream.capture.sink` for targeting output audio and -`stream.monitor` for monitor streams. See the official [stream keys](https://docs.pipewire.org/group__pw__keys.html) -and [capture example](https://docs.pipewire.org/audio-capture_8c-example.html). - -Start with one source: the default playback output, or an explicitly configured -sink. This covers audio routed to that sink, not every device simultaneously. -Follow default-output changes when configured to do so; reconnect a named sink by -stable identity after unplug/replug. Missing sources or permissions produce an -observable unavailable state and normal rain, with bounded reconnect backoff. -Never silently substitute a microphone. Verify volume/mute behavior experimentally -and document whether the captured monitor data reflects output volume; honor the -selected sink's mute state in the visual effect. - -Use one analysis stream shared by all participating monitors. Start the helper -only when enabled and an eligible rain surface is visible. Stop capture when all -eligible surfaces become awake, solid black, or powered off. On a locked desktop, -only start it if locked audio effects are explicitly enabled. A standalone locker -without the manager can render ordinary rain without audio integration. - -The first mode measures short-window RMS energy and peaks. Apply a noise gate, -configurable gain, and attack/release smoothing. Silence settles to zero modulation; -source loss or stale measurements also decays to zero. This mode reacts to musical -energy without claiming beat or tempo recognition. - -| Input | Initial visual mapping | Later options | -| --- | --- | --- | -| Overall energy | Gentle speed increase | Density within preallocated capacity | -| Smoothed peaks | Bounded glyph-brightness increase | Soft accent pulses | -| Bass / midrange / treble | Deferred | Separate depth layers or column regions | -| Detected rhythmic onsets | Deferred | Bounded pulses with a minimum interval | - -Keep processing outside GL, Wayland, and PAM paths. The PipeWire callback uses -preallocated storage and bounded work, with no blocking, file/network I/O, JSON, -or allocation. A worker computes measurements and sends small timestamped scalar -snapshots through a private channel, initially capped at 20 updates per second. -Renderers interpolate at the existing 30 FPS; they never wait for audio. Drop old -measurements under backpressure. Extend to three-band analysis only after measuring -the simpler mode. Raw audio is neither stored nor exposed through the public API. - -Audio effects must not create a playback loop, alter routing, change device -volume, or hold audio devices active after the last eligible rain surface stops. -Account for any PipeWire/session-manager idle-inhibition interaction in testing. -MCP/status expose enabled/source/availability state, not a high-rate sample feed. - -## Proposed configuration excerpt - -This is valid TOML syntax illustrating the planned schema. It is not accepted by -Digital Rain 1.1.0 and is not a complete replacement for the user's config. - -```toml -schema_version = 1 - -[global] -LOCK_TIMEOUT = 0 - -[matrix] -COLOR = "adaptive" -POWER_OFF = false -HIDE_DESKTOP = false - -[alerts] -ENABLED = true -SHOW_WHILE_LOCKED = false - -[alerts.error] -COLOR = "#ff4040" -PRIORITY = 40 - -[alerts.warning] -COLOR = "#ffb000" -PRIORITY = 30 - -[audio] -ENABLED = false -SOURCE = "default-output" -MODE = "energy" -SHOW_WHILE_LOCKED = false -OUTPUTS = [] # Empty means all eligible physical monitors. -SPEED_AMOUNT = 0.25 # Maximum proportional increase above base speed. -BRIGHTNESS_AMOUNT = 0.10 # Maximum proportional increase, subject to final cap. -ATTACK_MS = 50 -RELEASE_MS = 250 -``` - -Audio is opt-in. Tune gain and modulation amounts while previewing on one monitor; -the eventual full schema must document defaults, units, bounds, and application -timing for every field. Success/info themes also receive documented defaults. - -## Delivery order and checks - -1. **Configuration foundation:** isolate configuration state, unify validation, - select the TOML parser, implement conversion and validation commands, and make - saves/reloads transactional. Test the actual user config for equivalent values. -2. **Live appearance:** add renderer/locker updates, revision reporting, and - resource replacement. Test without alerts/audio first, including locked reloads. -3. **Alerts:** implement bounded lifecycle/priority rules, IPC/CLI/MCP support, - event subscriptions, themes, and producer examples. -4. **Audio energy mode:** optional helper and Gentoo `pipewire` USE dependency, - source selection, smoothing, visibility-based lifecycle, and per-monitor opt-in. -5. **Measure and extend:** benchmark before considering spectrum bands, rhythmic - onsets, displayed alert text, or alerts on black screens. Publish measured costs. - -Configuration checks cover invalid/partial saves, atomic replacement, deletion, -duplicate TOML keys/tables, unknown settings, removed keys, comments, simultaneous -API saves, and resource-allocation failure. Confirm that applying appearance does -not restart animation, change phase age, or change independent idle deadlines. - -Alert checks cover competing producers, priority ties, updates, clear/expiry, -producer death, manager restart, disconnected outputs, previews, adaptive-color -updates underneath alerts, floods, and slow readers. Lock integration checks must -prove that appearance changes cannot trigger wake or unlock and that protected -surfaces survive visual/helper failures and display hotplug. - -Audio checks use deterministic silence, tones, impulses, and changing levels, -then real playback on a private PipeWire test setup. Cover clipping/non-finite -input, stereo/channel layouts, mute/volume, default-device changes, source loss, -server/helper restarts, stale measurements, and audio while an application inhibits -the saver. Use a virtual playback sink for automated tests; do not capture real -user audio during verification. - -Measure disabled features, an idle API subscriber, active alerts, audio energy -mode, and later spectrum mode separately. Compare CPU, RSS, wakeups, frame-time -median/p95/p99, update latency, and audio xruns over sustained runs with warmup and -repetitions. Test one monitor and stitched monitors at actual desktop resolutions. -Compare equivalent PipeWire playback with and without capture to expose costs in -the audio server as well as Digital Rain. - -Acceptance requires no audio helper/capture when disabled, no continuing audio -work when all eligible surfaces are black, unchanged target frame cadence, bounded -memory/queues, responsive wake, and no measurable disabled-feature regression -beyond repeat-run noise. Enabled audio has a real cost; report the measurements -instead of promising zero overhead. Use isolated compositor sessions for lock and -hotplug tests. A compositor restart is not part of the rollout. - -## Files expected to change during implementation - -| Area | Starting points | -| --- | --- | -| Config parsing and renderer state | `digitalrain.c`; extract reusable config/schema code as needed | -| Public controls, validation, saves, events | `digitalrain-api.h`, `digitalrain-control.h` | -| Protected visual updates and physical regions | `digitalrain-lock.c` | -| Agent tools | `ambient-background-mcp.py` | -| Optional audio capture and analysis | New `digitalrain-audio.c` and focused analysis code | -| Build and packaging | `Makefile`, next-version ebuild, annotated TOML example | -| Validation and user guidance | Existing headless tests, new focused tests, configuration/API/feature guides | - -The PAM worker needs no new responsibilities. Preserve the existing GLES rendering -baseline; an OpenGL version migration or language rewrite is outside this plan. diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/monitor-power.md b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/monitor-power.md deleted file mode 100644 index 087d81d74959..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/monitor-power.md +++ /dev/null @@ -1,50 +0,0 @@ -# Optional monitor power-off - -This integration is included in Ambient Background. See the configuration guide for current file paths and the WCM controls for continuous live wallpapers; helper and socket identifiers retain their compatible names. - -See [Configuration](configuration.md) for the full option reference, defaults -and reload rules. - -Digital Rain keeps monitors powered on by default. With a positive `BLACK_RAIN_TIMEOUT`, -the rain fades to solid black and the renderer sleeps without redrawing. This -keeps OLED pixels black while avoiding the monitor's power-on delay. - -For LCDs or other monitors where powering down is preferred, enable the -`digitalrain-power` Wayfire plugin and configure: - -```toml -[matrix] -BLACK_RAIN_TIMEOUT = 600 -POWER_OFF = true -``` - -Power-off replaces the **final solid-black state**. It does not skip rain or -change the idle timers. With transparent rain, the sequence is: idle timeout, -transparent rain, black-background rain, monitor power-off. A zero -`BLACK_RAIN_TIMEOUT` means rain continues indefinitely, so power-off is never -reached. Transparent rain also needs a nonzero `TRANSPARENT_TIMEOUT` to reach -the black background. - -Mixed setups can override the global setting by physical connector name: - -```toml -[output_power_off] -HDMI-A-1 = false -DP-1 = true -``` - -The manager still tracks each physical monitor independently, including members -of a stitched desktop. Activity or focus returning to that monitor restores -power before the wake animation. Video inhibition and normal shutdown also -restore it. Power control preserves the monitor's place in the desktop, so -windows are not rearranged. - -The renderer holds a compositor IPC connection while power is off. Disconnect, -renderer crash, or unloading the power plugin restores power. Managed renderers -also receive SIGTERM if their manager dies. An output removed and replaced while -asleep is not treated as the same connector object. Outputs already powered off -by another owner are left alone. If the helper is missing or rejects the request, -Digital Rain stays solid black and reports the failure. - -For a standalone renderer, use `--power-off 1` with a positive -`--black-rain-timeout`; `SIGUSR1` wakes it, as in black-screen mode. diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/stitched-outputs.md b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/stitched-outputs.md deleted file mode 100644 index 2ed008229370..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/stitched-outputs.md +++ /dev/null @@ -1,45 +0,0 @@ -# Digital Rain on a stitched Wayfire desktop - -This integration is included in Ambient Background. See the configuration guide for current file paths and the WCM controls for continuous live wallpapers; helper and socket identifiers retain their compatible names. - -Digital Rain keeps independent physical-monitor timers when Wayfire exposes -DP-1, DP-2 and DP-3 as regions of VIRTUAL-1. The cube and workspace remain shared. - -The manager discovers member names and rectangles from `window-rules/list-outputs`. -Each member keeps its own unfocused/focused timeout, color overrides, adaptive -palette, renderer, and wake state. Pointer crossings select the physical member; -view-focus events also use Wayfire's `physical-monitor` field. Keep using physical -names in `[output_timeouts]`, `[output_colors]`, and `[output_power_off]`. -See [Configuration](configuration.md) for defaults, units and precedence. - -Renderers bind the parent Wayland output and anchor a surface to the member's -rectangle. With the default `HIDE_DESKTOP=false`, apps and bars remain visible under -transparent rain until the black background covers them. Optional desktop hiding -fades only pixels in that rectangle, including the corresponding slice of a -spanning window or panel, leaving the wallpaper visible during transparent rain. -Rain on black and solid black are also limited to that monitor. Optional monitor -power-off targets the physical connector and preserves the shared desktop layout. - -Video inhibition protects every member intersected by the visible playback -window. Moving the window moves its protection; closing it releases protection. -Adaptive luminance samples are taken separately for each member. - -Output changes are reconciled after events and periodically. Disappearing outputs -(including Wayfire's temporary NOOP-1) are removed and their renderers stopped. -Changing grouping or geometry recreates affected targets with fresh timers. -An IPC outage preserves existing member mappings instead of replacing them with -one combined timer. Ordinary outputs retain the Wayland discovery fallback. - -The desktop, idle, sampler and optional power helpers must match the manager -and the installed Wayfire ABI. The cursor helper reports coordinates used by -the manager; that helper is non-unloadable during a running Wayfire session. - -Validation: `python3 tests/test-stitched-outputs.py` as a regular user starts its -own small three-output headless compositor. It checks exact neighboring pixels, -spanning windows, fades, renderer/black timeout, per-member sampling, actual mpv -idle inhibitors, independent manager timers, wake-up, grouping changes, optional -power-off, crash recovery and removal of a sleeping monitor. `--binary PATH` tests -an installed manager/renderer binary with the built helpers. -`--upgrade-from DIRECTORY` first loads old helper libraries from that directory -and verifies a live upgrade. Tests require the usual build dependencies plus -Wayfire, grim, swaybg, mpv and Pillow. They do not use the real desktop's sockets. diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/README.black-rain-timeout.md b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/README.black-rain-timeout.md deleted file mode 100644 index d1f683b8a400..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/README.black-rain-timeout.md +++ /dev/null @@ -1,27 +0,0 @@ -# Black output timer regression - -`test-black-rain-timeout.py` runs the production renderer against an isolated -Wayfire compositor with three headless outputs and `ipc ipc-rules` enabled. -It requires `wlr-randr`, `swaybg`, `grim`, and Python Pillow. The optional -`render-test` fixture's `test/stats` method also verifies that static black -does not submit compositor render passes. - -With the test compositor's `XDG_RUNTIME_DIR`, `WAYLAND_DISPLAY`, and -`WAYFIRE_SOCKET` environment variables set: - -```sh -python3 tests/test-black-rain-timeout.py /path/to/built/digitalrain -``` - -The test refuses a session without exactly HEADLESS-1, HEADLESS-2, HEADLESS-3. -It sets test output modes and backgrounds, uses short timers, and verifies: - -* Transparent rain, rain on black, then pure RGB zero on each pixel. -* Staggered activation gives each output its own countdown. -* Zero disables the rain-stop timer; indefinite transparency does not start it. -* Initially opaque mode starts its timer after the black background appears. -* Renderers remain alive and sleep; palette notifications do not restart rain. -* Resize retains complete black coverage and all outputs stay enabled. -* Activity reveals the desktop without a rain flash; termination wakes sleepers. - -The existing glyph-compositing regression also remains applicable. diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/README.cursor-wakeup.md b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/README.cursor-wakeup.md deleted file mode 100644 index cd18ac7a00ba..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/README.cursor-wakeup.md +++ /dev/null @@ -1,24 +0,0 @@ -# Cursor visibility during wake-up - -Version 1.0.18 kept the Wayfire cursor hidden whenever its output was marked -`off`, including after input resumed. Unblanking waited for renderer exit -before changing that state, and cursor polling stopped during the wait. This -left the pointer hidden during the fade and delayed visibility changes while -crossing monitors. - -Version 1.0.19 only hides the cursor on a blanked output while the seat is idle. -Unblanking marks the output awake and updates cursor visibility before waiting -for the renderer. Cursor polling continues during that wait. Stationary cursors -on blanked outputs are still hidden once the seat is idle. - -Run `python3 tests/test-cursor-wakeup.py` from the package directory. The test -compiles the production manager code and supplies a private Wayfire IPC socket; -it never connects to the desktop. It checks idle hiding, resumed input, -unblanking with a deliberately slow renderer, and crossing between blanked and -awake outputs during the fade. `--source /path/to/older/digitalrain.c` permits a -before/after comparison. All three wake-up cases fail against 1.0.18 and pass -against 1.0.19; idle hiding passes against both. - -The existing `test-inhibition.c` and `test-manager-lock.py` regressions also -pass. These checks validate manager visibility requests, not physical GPU -cursor-plane rendering or the appearance of a faint cursor outline. diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/README.glyph-compositing.md b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/README.glyph-compositing.md deleted file mode 100644 index 287743777d5c..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/README.glyph-compositing.md +++ /dev/null @@ -1,28 +0,0 @@ -# Glyph compositing regression - -Version 1.0.13 multiplied layer depth and trail brightness into stroke alpha. -A dim glyph could therefore leave 95% of the application pixel beneath it -visible. Version 1.0.14 applies those factors to RGB intensity and reserves -stroke alpha for antialias coverage and the overall fade. Empty background -pixels remain transparent; glow is still translucent. - -`test-glyph-compositing.c` runs the actual production `render_frame()` and GLSL -shader in a surfaceless EGL pbuffer. A synthetic atlas gives known coverage. -It checks a dim far-layer trail over a white application pixel, antialiasing, -phase fading, and empty-background transparency. The test fails with 1.0.13 -(RGBA `12 6 3 12`) and passes with 1.0.14 (`12 6 3 255`). - -From the package directory, with generated protocol files available in the module directory: - -```sh -cc -std=c11 -O2 -I. tests/test-glyph-compositing.c tomlc17.c \ - ext-idle-notify-v1-protocol.c \ - wlr-layer-shell-unstable-v1-protocol.c \ - xdg-output-unstable-v1-protocol.c xdg-shell-protocol.c \ - $(pkg-config --cflags --libs json-c wayland-client wayland-egl egl glesv2 freetype2) \ - -lpthread -lm -o /tmp/test-glyph-compositing -/tmp/test-glyph-compositing -``` - -The fixture needs EGL with `EGL_PLATFORM_SURFACELESS_MESA`, GLES2 and an RGBA8 -pbuffer. It does not connect to or modify a desktop session. diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/README.hide-desktop.md b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/README.hide-desktop.md deleted file mode 100644 index 904ef1c36579..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/README.hide-desktop.md +++ /dev/null @@ -1,25 +0,0 @@ -# Desktop fade regression - -Build with `make`, then run as a regular user: - -```sh -python3 tests/test-hide-desktop.py --existing-tests -``` - -The runner starts its own Wayfire with three headless outputs and loads the -candidate plugin directly from ``. It needs Wayfire development headers, -a C++ compiler, pkg-config, grim, wlr-randr, swaybg and Python Pillow. It does not -connect to the desktop, alter the real configuration, or install its fixture. - -Pixel checks cover app, panel, popup and widget layers, wallpaper and lock -visibility, the intermediate opacity of all six desktop layers, simultaneous -rain fade-in, independent monitors, new windows, and multiple IPC owners. -Wake-up must show intermediate desktop opacity while rain or black fades away, -and reverse partial fades without jumping. Repeated wake requests are idempotent. -Wake during the fade, normal wake, SIGKILL and plugin unload must restore exact -baseline pixels. Missing-plugin fallback and output removal are also exercised. -`--existing-tests` runs the black-rain timeout regressions in the same isolated -compositor after the new tests finish. - -Existing cursor-wakeup, manager-lock, glyph-compositing and inhibition tests -remain applicable. Test artifacts are retained under the printed temporary path. diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/README.live-config-alerts-audio.md b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/README.live-config-alerts-audio.md deleted file mode 100644 index cfd9b85465c1..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/README.live-config-alerts-audio.md +++ /dev/null @@ -1,109 +0,0 @@ -# Live configuration, alerts and playback tests - -Build with `make`. The audio helper requires PipeWire development files; -`make PIPEWIRE=0` builds the remaining programs without it. - -Run the production manager without a compositor to check TOML parsing, atomic -replacement, comment-preserving API saves (including inline/dotted/multiline TOML), -invalid-edit recovery, default restoration, explicit reload, alert limits/expiry -and the absence of an audio child when disabled: - -```sh -python3 tests/test-live-config-alerts.py digitalrain -``` - -Run the native integration suite as root, selecting an ordinary local account. -It creates a temporary test-only PAM service and removes it afterward. All input, -locking, screenshots and audio happen in private headless Wayfire/PipeWire sessions: - -```sh -python3 tests/run-native-lock-tests.py --user USER -# Validate an installed build instead: -python3 tests/run-native-lock-tests.py --user USER --binary-dir /usr/bin -``` - -This covers live colors/font/depth without changing renderer PIDs, physical-region -alerts inside a stitched desktop, priority and restoration, black-screen policy, -locked appearance without unlocking, authentication, crash recovery, monitor -hotplug, optional DPMS, MCP/API transport, and audio-helper visibility, crash and -server-loss recovery. A private virtual sink is created; hardware audio monitors -are not loaded by the policy-only WirePlumber profile. - -For deterministic playback levels, silence, mute/unmute, default-source changes, -named-sink removal/recreation and missing-source behavior: - -```sh -runuser -u USER -- python3 tests/test-audio.py digitalrain-audio -``` - -The C harness `test-live-render.c` injects allocation failure into a resource -replacement and checks rollback, phase clocks, vertical monitor regions, color -restoration and invalid packets. `run-c-tests.py` compiles it along with the existing -topology, focus/inhibition and glyph-compositing harnesses: - -```sh -python3 tests/run-c-tests.py -python3 tests/test-cursor-wakeup.py -runuser -u USER -- python3 tests/test-stitched-outputs.py -``` - -For a sustained comparison, retain the previous 1.1 executable and run: - -```sh -runuser -u USER -- python3 tests/benchmark-live.py OLD_BINARY digitalrain 15 -``` - -Each case uses two repetitions, three seconds of warmup, and a configurable sample -window (15 seconds by default). Cases include idle/no clients, idle subscription, -one 4K monitor, three stitched 4K monitors, alerts, and audio. A private virtual -sink plays the same tone throughout, exposing additional PipeWire cost when capture -starts. CPU percentages use one logical core as 100%; context-switch counts are -not hardware wakeup measurements. RSS is sampled per process group. - -A separate direct-renderer pass records Wayland client commit intervals. These are -submission cadence, not GPU execution or presentation latency. Manager-owned renderers silence their logs, so multi-monitor manager cases -report CPU/RSS only. `pw-top` -snapshots retain the private audio graph's error counters. Headless tests cannot -establish hardware speaker latency or real-display presentation behavior. - -`DR_BENCH_REPETITIONS` overrides the repetition count; `DR_BENCH_CASES` selects -comma-separated case names for focused reruns. The release measurements below describe their limits. - -## 1.2.0 release observations (2026-09-27) - -Correctness suites passed against both the source build and installed package. -Measurements are short diagnostic samples on the development desktop, not a -statistically conclusive zero-overhead result. The longer run was interrupted by -an external session restart, so its audio measurements were discarded. The final -focused run used one ten-second sample per case after three seconds of warmup. -Raw results: [benchmark-1.2.0.json](benchmark-1.2.0.json). - -| Three stitched 3840×2160 outputs | Renderer CPU, % of one core | Renderer RSS, KiB | -| --- | ---: | ---: | -| 1.1 baseline | 6.3 | 195780 | -| 1.2, effects inactive | 9.6 | 197020 | -| 1.2, alert active | 7.0 | 196396 | -| 1.2, audio active | 7.0 | 196536 | - -The variation between inactive and active effects means these short, randomized -animation runs do not isolate incremental rendering cost. Do not infer that alerts -make rendering faster, or claim no disabled-feature CPU regression from this data. -Earlier matched one-output samples measured 2.07% versus 2.27% CPU. Further sustained -repetitions with controlled animation state would be needed for a reliable delta. - -The idle manager used under one CPU accounting tick in fifteen seconds in both -versions, about 3.7–4.0 MiB RSS, and roughly 17 context switches/second, including an -idle subscriber. Audio added a 6.1 MiB helper with about 44 switches/second; manager -switches rose to about 28/second. Helper and private PipeWire CPU each stayed below -one 10 ms accounting tick during the final sample. This means below measurement -resolution, not zero cost. The virtual audio graph reported zero errors in its -`pw-top` snapshots, with identical playback running before and during capture. - -| Direct renderer commit interval | 1.1 | 1.2 | -| --- | ---: | ---: | -| Median | 33.383 ms | 33.377 ms | -| p95 | 33.575 ms | 33.591 ms | -| p99 | 33.680 ms | 33.846 ms | - -Both retained the intended 30 FPS submission cadence. These measurements do not -establish hardware presentation latency or speaker latency. Audio remains opt-in. diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/README.native-lock.md b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/README.native-lock.md deleted file mode 100644 index 3f09458d737b..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/README.native-lock.md +++ /dev/null @@ -1,28 +0,0 @@ -# Native lock and control regressions - -Build with `umask 022; make`, then as root run: - -```sh -python3 tests/run-native-lock-tests.py --user YOUR_DESKTOP_USER -``` - -To validate packaged executables, add `--binary-dir /usr/bin`. The wrapper creates -a temporary, uniquely named PAM service and a test worker compiled for that -service. It removes the policy and module when the suite exits. It never changes -`/etc/pam.d/digitalrain`, any account password, or the user's Wayfire config. - -The actual suite runs as the selected unprivileged user in a private headless -Wayfire session. It uses isolated compositor plugins for keyboard input and -output inspection; neither those plugins nor the deterministic test PAM module -are included in the installed package. The production auth worker's service is -fixed at compile time and accepts no password arguments or environment override. - -Coverage includes failed/successful PAM authentication, black idle, lock coverage -on stitched monitors, duplicate/refused locking, saver wake isolation, manager -restart, automatic crash recovery, monitor hot-plugging, and power-off/wake while -locked. API tests cover settings validation, preview/save separation, inhibition, -state events, packet bounds, and MCP negotiation/tools/resources. Logs and screen -captures remain in the printed `/tmp/digitalrain-lock-test-*` artifact directory. - -The other package regressions continue to cover ordinary saver behavior. The -native suite neither locks nor injects input into the real desktop. diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/benchmark-1.2.0.json b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/benchmark-1.2.0.json deleted file mode 100644 index 7f9718a8a6ca..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/benchmark-1.2.0.json +++ /dev/null @@ -1,179 +0,0 @@ -[ - { - "case": "old three 4K", - "repetition": 1, - "seconds": 10.000068796995038, - "cost": { - "manager": { - "cpu_percent": 0.0, - "switches_per_second": 16.8, - "rss_kib": 3836 - }, - "renderers": { - "cpu_percent": 6.3, - "switches_per_second": 273.898, - "rss_kib": 195780 - }, - "wayfire": { - "cpu_percent": 0.9, - "switches_per_second": 1087.893, - "rss_kib": 72724 - }, - "pipewire": { - "cpu_percent": 0.0, - "switches_per_second": 23.4, - "rss_kib": 7312 - }, - "policy": { - "cpu_percent": 0.0, - "switches_per_second": 0.0, - "rss_kib": 1800 - }, - "playback": { - "cpu_percent": 0.0, - "switches_per_second": 23.4, - "rss_kib": 17264 - } - }, - "frames": {} - }, - { - "case": "new three 4K", - "repetition": 1, - "seconds": 10.000072757000453, - "cost": { - "manager": { - "cpu_percent": 0.0, - "switches_per_second": 17.2, - "rss_kib": 3972 - }, - "renderers": { - "cpu_percent": 9.6, - "switches_per_second": 277.398, - "rss_kib": 197020 - }, - "wayfire": { - "cpu_percent": 1.2, - "switches_per_second": 1055.392, - "rss_kib": 71788 - }, - "pipewire": { - "cpu_percent": 0.0, - "switches_per_second": 23.4, - "rss_kib": 7312 - }, - "policy": { - "cpu_percent": 0.0, - "switches_per_second": 0.0, - "rss_kib": 1800 - }, - "playback": { - "cpu_percent": 0.0, - "switches_per_second": 23.9, - "rss_kib": 17264 - } - }, - "frames": {} - }, - { - "case": "new alerts three 4K", - "repetition": 1, - "seconds": 10.00006255399785, - "cost": { - "manager": { - "cpu_percent": 0.0, - "switches_per_second": 16.9, - "rss_kib": 3940 - }, - "renderers": { - "cpu_percent": 7.0, - "switches_per_second": 272.898, - "rss_kib": 196396 - }, - "wayfire": { - "cpu_percent": 0.9, - "switches_per_second": 1170.193, - "rss_kib": 71788 - }, - "pipewire": { - "cpu_percent": 0.1, - "switches_per_second": 23.5, - "rss_kib": 7312 - }, - "policy": { - "cpu_percent": 0.0, - "switches_per_second": 0.0, - "rss_kib": 1800 - }, - "playback": { - "cpu_percent": 0.0, - "switches_per_second": 25.0, - "rss_kib": 17264 - } - }, - "frames": {} - }, - { - "case": "new audio three 4K", - "repetition": 1, - "seconds": 10.0000647880006, - "cost": { - "manager": { - "cpu_percent": 0.0, - "switches_per_second": 27.9, - "rss_kib": 4120 - }, - "renderers": { - "cpu_percent": 7.0, - "switches_per_second": 272.598, - "rss_kib": 196536 - }, - "audio": { - "cpu_percent": 0.0, - "switches_per_second": 43.6, - "rss_kib": 6216 - }, - "wayfire": { - "cpu_percent": 0.9, - "switches_per_second": 1004.093, - "rss_kib": 71788 - }, - "pipewire": { - "cpu_percent": 0.0, - "switches_per_second": 23.4, - "rss_kib": 7560 - }, - "policy": { - "cpu_percent": 0.0, - "switches_per_second": 0.0, - "rss_kib": 1800 - }, - "playback": { - "cpu_percent": 0.0, - "switches_per_second": 24.9, - "rss_kib": 17264 - } - }, - "frames": {} - }, - { - "case": "old frame cadence", - "repetition": 1, - "frames": { - "commits": 300, - "median_ms": 33.383, - "p95_ms": 33.575, - "p99_ms": 33.68 - } - }, - { - "case": "new frame cadence", - "repetition": 1, - "frames": { - "commits": 299, - "median_ms": 33.377, - "p95_ms": 33.591, - "p99_ms": 33.846 - } - } -] diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/benchmark-live.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/benchmark-live.py deleted file mode 100755 index 086b7fb5869f..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/benchmark-live.py +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/env python3 -"""Compare a 1.1 executable with the current build using private Wayfire/PipeWire. -Run unprivileged: python3 tests/benchmark-live.py OLD_BINARY NEW_BINARY [SECONDS] -Uses two repetitions after three-second warmup; results include frame commit -intervals (not GPU presentation latency). Never connects to desktop sockets. -""" -import array, json, math, os, pathlib, re, socket, statistics, struct, subprocess, shlex, sys, tempfile, time, wave -old,new=map(lambda x:str(pathlib.Path(x).resolve()),sys.argv[1:3]);duration=float(sys.argv[3]) if len(sys.argv)>3 else 15 -repetitions=int(os.environ.get('DR_BENCH_REPETITIONS','2')) -selected=os.environ.get('DR_BENCH_CASES','') -assert os.getuid()!=0 -pkg=pathlib.Path(__file__).resolve().parents[1] -root=pathlib.Path(tempfile.mkdtemp(prefix='digitalrain-live-bench-'));children=[];results=[] -env=os.environ.copy() -for key in ['WAYLAND_DISPLAY','WAYFIRE_SOCKET','_WAYFIRE_SOCKET','DISPLAY','WAYFIRE_CONFIG_FILE','WAYFIRE_DEFAULT_CONFIG_BACKEND','PIPEWIRE_RUNTIME_DIR','PIPEWIRE_CONFIG_DIR','PIPEWIRE_CONFIG_NAME','WIREPLUMBER_CONFIG_DIR','DBUS_SESSION_BUS_ADDRESS']:env.pop(key,None) -env.update(XDG_RUNTIME_DIR=str(root),XDG_CONFIG_HOME=str(root/'config'),XDG_STATE_HOME=str(root/'state'),WLR_BACKENDS='headless',WLR_HEADLESS_OUTPUTS='4',WLR_RENDERER='gles2',WLR_RENDER_DRM_DEVICE='/dev/dri/renderD128',WAYFIRE_PLUGIN_PATH=f'{root}:{pkg}',WAYFIRE_PLUGIN_XML_PATH=f'{root}:{pkg}',GIO_USE_VFS='local',PIPEWIRE_REMOTE='pipewire-0') -(root/'wayfire.ini').write_text('[core]\nplugins = ipc ipc-rules digitalrain-cursor digitalrain-idle desktop-test\nxwayland = false\nstitch_enabled = true\nstitch_outputs = HEADLESS-1 HEADLESS-2 HEADLESS-3\nwindow_scope = virtual\n'+''.join(f'[output:HEADLESS-{i}]\nmode = 3840x2160@120\nposition = {(i-1)*3840},0\n' for i in range(1,4))+'[output:HEADLESS-4]\nmode = 1920x1080@60\nposition = 11520,0\n') -base='[global]\nTIMEOUT=9999\nFOCUSED_TIMEOUT=9999\nCHECK_INTERVAL=3\nINHIBIT_VIDEO={inhibit}\nLOG_FILE={log}\n[matrix]\nCOLOR={color}\nFONT_SIZE=14\nDEPTH_LAYERS=8\nTRANSPARENT_TIMEOUT=600\nBLACK_RAIN_TIMEOUT=600\nMIN_SPEED=0.1\nMAX_SPEED=1.25\nAVG_SPEED=0.18\n' -(root/'rain.conf').write_text(base.format(inhibit=0,log=root/'manager.log',color='green')) -(root/'rain.toml').write_text(base.format(inhibit='false',log=json.dumps(str(root/'manager.log')),color='"green"')) -def spawn(argv,debug=False): - e=env.copy() - if debug:e['WAYLAND_DEBUG']='client' - log=root/f'child-{len(children)}.log' - with log.open('w') as stream:p=subprocess.Popen(argv,env=e,stdout=stream,stderr=stream) - children.append(p);p.log=log;return p -def stop(p): - if p.poll() is None: - p.terminate() - try:p.wait(8) - except subprocess.TimeoutExpired:p.kill();p.wait() -def until(fn,timeout=10): - end=time.monotonic()+timeout - while time.monotonic()<end: - try: - result=fn() - if result:return result - except (ConnectionError,FileNotFoundError):pass - time.sleep(.05) - raise AssertionError('timed out') -def ipc(method,data): - with socket.socket(socket.AF_UNIX) as s: - s.settimeout(4);s.connect(env['WAYFIRE_SOCKET']);body=json.dumps(dict(method=method,data=data)).encode();s.sendall(struct.pack('=I',len(body))+body) - def read(n): - b=b'' - while len(b)<n:b+=s.recv(n-len(b)) - return b - return json.loads(read(struct.unpack('=I',read(4))[0])) -def api(method,**kw): - with socket.socket(socket.AF_UNIX,socket.SOCK_SEQPACKET) as s: - s.settimeout(10);s.connect(str(root/'digitalrain/control.sock'));s.send(json.dumps(dict(version=1,method=method,**kw)).encode());v=json.loads(s.recv(16384));assert 'error' not in v,v;return v -def descendants(pid): - children=pathlib.Path(f'/proc/{pid}/task/{pid}/children').read_text().split() - return [int(p) for p in children] -def counters(pid): - f=pathlib.Path(f'/proc/{pid}/stat').read_text().split();cpu=(int(f[13])+int(f[14]))/os.sysconf('SC_CLK_TCK');ctx=0 - for task in pathlib.Path(f'/proc/{pid}/task').iterdir(): - for line in (task/'status').read_text().splitlines(): - if line.startswith(('voluntary_ctxt_switches:','nonvoluntary_ctxt_switches:')):ctx+=int(line.split()[1]) - status=pathlib.Path(f'/proc/{pid}/status').read_text().splitlines();rss=int(next(x.split()[1] for x in status if x.startswith('VmRSS:'))) - return cpu,ctx,rss -commit_re=re.compile(r'\[(\d+):(\d+):([0-9.]+)\].*?wl_surface[@#](\d+)\.commit\(') -def frame_stats(text): - groups={} - for line in text.splitlines(): - m=commit_re.search(line) - if m:groups.setdefault(m[4],[]).append((int(m[1])*3600+int(m[2])*60+float(m[3]))*1000) - gaps=sorted(b-a for values in groups.values() for a,b in zip(values,values[1:]) if b>a) - return dict(commits=sum(map(len,groups.values())),median_ms=round(statistics.median(gaps),3),p95_ms=round(gaps[int(len(gaps)*.95)],3),p99_ms=round(gaps[int(len(gaps)*.99)],3)) if gaps else {} -try: - flags=shlex.split(subprocess.check_output(['pkg-config','--cflags','--libs','wayfire','wf-utils'],text=True)) - subprocess.run(['c++','-std=c++17','-fPIC','-shared','-DWAYFIRE_PLUGIN','-DWLR_USE_UNSTABLE',str(pkg/'tests/desktop-test.cpp'),'-o',str(root/'libdesktop-test.so'),*flags],check=True) - (root/'desktop-test.xml').write_text('<wayfire><plugin name="desktop-test"><_short>Test</_short></plugin></wayfire>') - wf=spawn(['wayfire','-c',str(root/'wayfire.ini')]);until(lambda:list(root.glob('wayfire-*.socket'))) - env['WAYFIRE_SOCKET']=str(next(root.glob('wayfire-*.socket')));env['WAYLAND_DISPLAY']=next(x.name for x in root.glob('wayland-*') if not x.name.endswith('.lock')) - time.sleep(1);ipc('test/activity',dict(x=12000,y=500));time.sleep(.2) - server=spawn(['pipewire']);until(lambda:(root/'pipewire-0').exists());policy=spawn(['dbus-run-session','--','wireplumber','--profile','policy']);time.sleep(1) - subprocess.run(['pw-cli','create-node','adapter','{ factory.name = support.null-audio-sink node.name = rain-benchmark media.class = Audio/Sink object.linger = true audio.position = [ FL FR ] }'],env=env,check=True,stdout=subprocess.DEVNULL) - tone=root/'tone.wav' - # 15 minutes, generated as repeated one-second stereo blocks. - samples=array.array('h',(int(.25*32767*math.sin(2*math.pi*440*i/48000)) for i in range(48000) for _ in range(2))) - with wave.open(str(tone),'wb') as w: - w.setnchannels(2);w.setsampwidth(2);w.setframerate(48000) - for _ in range(900):w.writeframesraw(samples.tobytes()) - playback=spawn(['pw-play','--target','rain-benchmark',str(tone)]) - cases=[('old idle',old,0,False,False),('new idle',new,0,False,False),('new subscriber',new,0,False,False),('old one 4K',old,1,False,False),('new one 4K',new,1,False,False),('old three 4K',old,3,False,False),('new three 4K',new,3,False,False),('new alerts three 4K',new,3,True,False),('new audio three 4K',new,3,False,True)] - for repetition in range(repetitions): - for label,exe,count,alert,audio in cases: - if selected and label not in selected.split(','):continue - assert server.poll() is None and playback.poll() is None,'Private audio server/playback exited; discard interrupted run' - env['DIGITALRAIN_CONFIG']=str(root/('rain.conf' if exe==old else 'rain.toml')) - manager=spawn([exe]);until(lambda:api('status').get('outputs')) - if audio:api('settings',settings={'audio.ENABLED':1,'audio.SOURCE':'rain-benchmark'}) - for i in range(count):api('activate',output=f'HEADLESS-{i+1}') - if alert:api('alert',source='benchmark',id='busy',severity='error',seconds=300) - if audio:until(lambda:api('status')['audio']['state']=='monitoring') - watcher=None - if label=='new subscriber': - watcher=socket.socket(socket.AF_UNIX,socket.SOCK_SEQPACKET);watcher.connect(str(root/'digitalrain/control.sock'));watcher.send(b'{"version":1,"method":"watch"}');watcher.recv(16384) - time.sleep(3) - if count:assert sum(o['saver'] for o in api('status')['outputs'])==count,api('status') - pids=[manager.pid,*descendants(manager.pid),wf.pid,server.pid,policy.pid,playback.pid] - names={pid:('manager' if pid==manager.pid else 'wayfire' if pid==wf.pid else 'pipewire' if pid==server.pid else 'policy' if pid==policy.pid else 'playback' if pid==playback.pid else 'audio' if b'digitalrain-audio' in pathlib.Path(f'/proc/{pid}/cmdline').read_bytes() else 'renderers') for pid in pids} - before={pid:counters(pid) for pid in pids};offset=manager.log.stat().st_size;start=time.monotonic();time.sleep(duration);elapsed=time.monotonic()-start;after={pid:counters(pid) for pid in pids};cost={} - for pid in pids: - v=cost.setdefault(names[pid],dict(cpu_percent=0,switches_per_second=0,rss_kib=0));a=before[pid];b=after[pid];v['cpu_percent']+=100*(b[0]-a[0])/elapsed;v['switches_per_second']+=(b[1]-a[1])/elapsed;v['rss_kib']+=b[2] - for v in cost.values(): - for k in v:v[k]=round(v[k],3) - frames={} - row=dict(case=label,repetition=repetition+1,seconds=elapsed,cost=cost,frames=frames);results.append(row);print(json.dumps(row),flush=True) - if audio: - top=subprocess.run(['pw-top','-b','-n','2'],env=env,text=True,capture_output=True,timeout=8);(root/f'pw-top-{repetition}.txt').write_text(top.stdout) - if watcher:watcher.close() - stop(manager);time.sleep(.4) - for repetition in range(repetitions): - for exe,label in [(old,'old frame cadence'),(new,'new frame cadence')]: - renderer=spawn([exe,'--render','--output','HEADLESS-1','--depth-layers','8','--font-size','14','--min-speed','0.1','--max-speed','1.25','--avg-speed','0.18'],debug=True) - time.sleep(3);offset=renderer.log.stat().st_size;time.sleep(duration) - with renderer.log.open() as stream:stream.seek(offset);frames=frame_stats(stream.read()) - assert frames.get('commits',0)>100,frames - row=dict(case=label,repetition=repetition+1,frames=frames);results.append(row);print(json.dumps(row),flush=True);stop(renderer) - (root/'results.json').write_text(json.dumps(results,indent=2));print('Artifacts:',root,flush=True) -finally: - for child in reversed(children):stop(child) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/desktop-test.cpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/desktop-test.cpp deleted file mode 100644 index 4fa668daab5e..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/desktop-test.cpp +++ /dev/null @@ -1,98 +0,0 @@ -/* Solid test surfaces make unwanted desktop pixels measurable in screenshots. */ -#include <map> -#include <wayfire/seat.hpp> -#include <wayfire/compositor-view.hpp> -#include <wayfire/core.hpp> -#include <wayfire/output-layout.hpp> -#include <wayfire/plugin.hpp> -#include <wayfire/scene.hpp> -#include <wayfire/workspace-stream.hpp> -#include <wayfire/workspace-set.hpp> -#include <wayfire/scene-operations.hpp> -#include <wayfire/nonstd/wlroots-full.hpp> -#include <wayfire/plugins/common/shared-core-data.hpp> -#include <wayfire/plugins/ipc/ipc-method-repository.hpp> - -class desktop_test_t : public wf::plugin_interface_t -{ - wf::shared_data::ref_ptr_t<wf::ipc::method_repository_t> repo; - std::map<std::string, std::shared_ptr<wf::color_rect_view_t>> solids; - std::shared_ptr<wf::workspace_stream_node_t> stream; - public: - void init() override - { - repo->register_method("test/workspace-stream", [this] (wf::json_t data) - { - if (stream) wf::scene::remove_child(stream); - stream.reset(); - if (!data.has_member("close")) - { - auto output = wf::get_core().output_layout->find_output(data["output"].as_string()); - auto ws = output->wset()->get_current_workspace(); - if (data.has_member("workspace")) ws = {data["workspace"].as_int(), ws.y}; - stream = std::make_shared<wf::workspace_stream_node_t>(output, ws); - wf::scene::add_front(wf::get_core().scene(), stream); - } - return wf::ipc::json_ok(); - }); - repo->register_method("test/output-power", [] (wf::json_t) - { - wf::json_t result; - for (auto& [head, state] : wf::get_core().output_layout->get_current_configuration()) - result[std::string(head->name)] = head->enabled; - return result; - }); - repo->register_method("test/remove-output", [] (wf::json_t data) - { - for (auto& [head, state] : wf::get_core().output_layout->get_current_configuration()) - if (data["output"].as_string() == head->name) { - wlr_output_destroy(head); - return wf::ipc::json_ok(); - } - return wf::ipc::json_error("Unknown test output"); - }); - repo->register_method("test/activity", [] (wf::json_t data) - { - wf::get_core().warp_cursor({data["x"].as_double(), data["y"].as_double()}); - wf::get_core().seat->notify_activity(); - return wf::ipc::json_ok(); - }); - repo->register_method("test/solid", [this] (wf::json_t data) - { - auto output = wf::get_core().output_layout->find_output(data["output"].as_string()); - auto& view = solids[data["key"].as_string()]; - if (data.has_member("close")) - { - if (view) - { - view->damage(); - wf::scene::set_node_enabled(view->get_root_node(), false); - view->close(); - view.reset(); - } - return wf::ipc::json_ok(); - } - if (!view) view = wf::color_rect_view_t::create(wf::VIEW_ROLE_DESKTOP_ENVIRONMENT, - output, (wf::scene::layer)data["layer"].as_int()); - view->set_geometry({double(data["x"].as_int()), double(data["y"].as_int()), - double(data["w"].as_int()), double(data["h"].as_int())}); - view->set_color({data["r"].as_double(), data["g"].as_double(), - data["b"].as_double(), 1.0}); - view->set_border(0); - return wf::ipc::json_ok(); - }); - } - void fini() override - { - repo->unregister_method("test/workspace-stream"); - if (stream) wf::scene::remove_child(stream); - stream.reset(); - repo->unregister_method("test/output-power"); - repo->unregister_method("test/remove-output"); - repo->unregister_method("test/solid"); - repo->unregister_method("test/activity"); - for (auto& [key, view] : solids) if (view) view->close(); - solids.clear(); - } -}; -DECLARE_WAYFIRE_PLUGIN(desktop_test_t); diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/lock-test.cpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/lock-test.cpp deleted file mode 100644 index 1b1e406069f3..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/lock-test.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/* Test-only input and compositor inspection. Never installed. */ -#include <wayfire/core.hpp> -#include <wayfire/seat.hpp> -#include <wayfire/output.hpp> -#include <wayfire/output-layout.hpp> -#include <wayfire/plugin.hpp> -#include <wayfire/nonstd/wlroots-full.hpp> -#include <wayfire/plugins/common/shared-core-data.hpp> -#include <wayfire/plugins/ipc/ipc-method-repository.hpp> -extern "C" { -#include <wlr/interfaces/wlr_keyboard.h> -#include <wlr/backend/headless.h> -#include <wlr/backend/multi.h> -} -class lock_test : public wf::plugin_interface_t { - wf::shared_data::ref_ptr_t<wf::ipc::method_repository_t> repo; - wlr_keyboard keyboard{}; - wlr_keyboard_impl impl{.name = "digitalrain-lock-test"}; - - public: - void init() override { - wlr_keyboard_init(&keyboard, &impl, "digitalrain-lock-test"); - auto ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS); - auto keymap = xkb_keymap_new_from_names(ctx, nullptr, XKB_KEYMAP_COMPILE_NO_FLAGS); - wlr_keyboard_set_keymap(&keyboard, keymap); - xkb_keymap_unref(keymap); - xkb_context_unref(ctx); - wl_signal_emit_mutable(&wf::get_core().backend->events.new_input, &keyboard.base); - repo->register_method("test/key", [this](wf::json_t data) { - wlr_keyboard_key_event ev{.time_msec = 1, - .keycode = (uint32_t)data["code"].as_int(), - .update_state = true, - .state = WL_KEYBOARD_KEY_STATE_PRESSED}; - wlr_keyboard_notify_key(&keyboard, &ev); - ev.state = WL_KEYBOARD_KEY_STATE_RELEASED; - wlr_keyboard_notify_key(&keyboard, &ev); - return wf::ipc::json_ok(); - }); - repo->register_method("test/lock-state", [](wf::json_t) { - wf::json_t result; - for (auto o : wf::get_core().output_layout->get_outputs()) - result[o->to_string()] = o->is_inhibited(); - return result; - }); - repo->register_method("test/add-output", [](wf::json_t) { - wlr_backend *headless = nullptr; - wlr_multi_for_each_backend( - wf::get_core().backend, - [](wlr_backend *b, void *d) { - if (wlr_backend_is_headless(b)) - *static_cast<wlr_backend **>(d) = b; - }, - &headless); - if (!headless) - return wf::ipc::json_error("no headless backend"); - wlr_headless_add_output(headless, 320, 240); - return wf::ipc::json_ok(); - }); - } - void fini() override { - repo->unregister_method("test/key"); - repo->unregister_method("test/lock-state"); - repo->unregister_method("test/add-output"); - wlr_keyboard_finish(&keyboard); - } -}; -DECLARE_WAYFIRE_PLUGIN(lock_test); diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/pam-test.c b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/pam-test.c deleted file mode 100644 index 4b45ee702c57..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/pam-test.c +++ /dev/null @@ -1,27 +0,0 @@ -/* Deterministic test PAM policy. Compile only for an isolated test service. */ -#include <security/pam_modules.h> -#include <security/pam_ext.h> -#include <string.h> -PAM_EXTERN int pam_sm_authenticate(pam_handle_t *p, int f, int n, const char **v) { - (void)f; - (void)n; - (void)v; - const char *password = NULL; - if (pam_get_authtok(p, PAM_AUTHTOK, &password, NULL) != PAM_SUCCESS) - return PAM_AUTH_ERR; - return password && !strcmp(password, "rain-test-7") ? PAM_SUCCESS : PAM_AUTH_ERR; -} -PAM_EXTERN int pam_sm_setcred(pam_handle_t *p, int f, int n, const char **v) { - (void)p; - (void)f; - (void)n; - (void)v; - return PAM_SUCCESS; -} -PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *p, int f, int n, const char **v) { - (void)p; - (void)f; - (void)n; - (void)v; - return PAM_SUCCESS; -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/portal-player.c b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/portal-player.c deleted file mode 100644 index 96dd4e5f92e7..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/portal-player.c +++ /dev/null @@ -1,108 +0,0 @@ -/* A real Wayland application using the public portal API, not a broker PID - * stand-in. Commands let the private suite exercise request/window lifetimes. */ -#include <gtk/gtk.h> -#include <gdk/gdkwayland.h> -#include <stdio.h> - -static GDBusConnection *bus; -static GtkWidget *window; -static GPtrArray *requests; -static char *parent; - -static void exported(GdkWindow *w, const char *handle, gpointer data) -{ - g_free(parent); - parent = g_strconcat("wayland:", handle, NULL); - puts("READY"); -} - -static void connect_bus(void) -{ - bus = g_dbus_connection_new_for_address_sync(g_getenv("DBUS_SESSION_BUS_ADDRESS"), - G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, - NULL, NULL, NULL); - g_assert(bus); - g_dbus_connection_set_exit_on_close(bus, FALSE); -} - -static void make_window(void) -{ - window = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_title(GTK_WINDOW(window), "Portal video fixture"); - gtk_window_set_default_size(GTK_WINDOW(window), 350, 300); - gtk_container_add(GTK_CONTAINER(window), gtk_label_new("Portal playing")); - gtk_widget_show_all(window); - g_assert(gdk_wayland_window_export_handle(gtk_widget_get_window(window), exported, NULL, NULL)); -} - -static gboolean command(GIOChannel *channel, GIOCondition condition, gpointer data) -{ - char *line = NULL; - GError *error = NULL; - if (g_io_channel_read_line(channel, &line, NULL, NULL, NULL) != G_IO_STATUS_NORMAL) - return FALSE; - g_strchomp(line); - gboolean firefox = g_str_has_prefix(line, "INHIBIT_FIREFOX "); - if (firefox || g_str_has_prefix(line, "INHIBIT ")) { - const char *selector = line + (firefox ? 16 : 8); - char *token = g_strdup_printf("test%u", requests->len); - GVariantBuilder options; - g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT); - if (!firefox) - g_variant_builder_add(&options, "{sv}", "handle_token", g_variant_new_string(token)); - g_variant_builder_add(&options, "{sv}", "reason", g_variant_new_string("Playing private test video")); - const char *handle = strcmp(selector, "parent") == 0 ? parent : - strcmp(selector, "empty") == 0 ? "" : selector; - GVariant *reply = g_dbus_connection_call_sync(bus, "org.freedesktop.portal.Desktop", - "/org/freedesktop/portal/desktop", "org.freedesktop.portal.Inhibit", "Inhibit", - g_variant_new("(su@a{sv})", handle, 8, g_variant_builder_end(&options)), - G_VARIANT_TYPE("(o)"), 0, 5000, NULL, &error); - if (reply) { - char *path; - g_variant_get(reply, "(o)", &path); - g_ptr_array_add(requests, path); - printf("HANDLE %s\n", path); - g_variant_unref(reply); - } - g_free(token); - } else if (!strcmp(line, "PARENT")) { - printf("PARENT %s\n", parent); - } else if (g_str_has_prefix(line, "CLOSE ")) { - unsigned index = strtoul(line + 6, NULL, 10); - g_assert(index < requests->len); - GVariant *reply = g_dbus_connection_call_sync(bus, "org.freedesktop.portal.Desktop", - g_ptr_array_index(requests, index), "org.freedesktop.portal.Request", "Close", - NULL, NULL, 0, 5000, NULL, &error); - if (reply) { puts("OK"); g_variant_unref(reply); } - } else if (!strcmp(line, "DISCONNECT")) { - g_dbus_connection_close_sync(bus, NULL, NULL); - g_clear_object(&bus); - puts("OK"); - } else if (!strcmp(line, "RECONNECT")) { - connect_bus(); puts("OK"); - } else if (!strcmp(line, "DESTROY")) { - gtk_widget_destroy(window); window = NULL; puts("OK"); - } else if (!strcmp(line, "NEW")) { - make_window(); - } else if (!strcmp(line, "SECOND")) { - GtkWidget *other = gtk_window_new(GTK_WINDOW_TOPLEVEL); - gtk_window_set_title(GTK_WINDOW(other), "Second same-process window"); - gtk_widget_show_all(other); puts("OK"); - } - if (error) { printf("ERROR %s\n", error->message); g_error_free(error); } - g_free(line); - return TRUE; -} - -int main(int argc, char **argv) -{ - setvbuf(stdout, NULL, _IOLBF, 0); - gtk_init(&argc, &argv); - connect_bus(); - requests = g_ptr_array_new_with_free_func(g_free); - GIOChannel *input = g_io_channel_unix_new(0); - g_io_add_watch(input, G_IO_IN | G_IO_HUP, command, NULL); - make_window(); - gtk_main(); - return 0; -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/run-c-tests.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/run-c-tests.py deleted file mode 100755 index 5428040ecfc6..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/run-c-tests.py +++ /dev/null @@ -1,7 +0,0 @@ -import pathlib,shlex,subprocess -pkg=pathlib.Path(__file__).resolve().parents[1];files=pkg -flags=shlex.split(subprocess.check_output(['pkg-config','--cflags','--libs','json-c','wayland-client','wayland-egl','egl','glesv2','freetype2'],text=True)) -for name in ['topology','inhibition','glyph-compositing','live-render']: - out=pathlib.Path('/tmp')/f'digitalrain-test-{name}' - subprocess.run(['cc','-std=c11','-O2','-I'+str(files),str(pkg/f'tests/test-{name}.c'),str(files/'tomlc17.c'),*map(str,files.glob('*-protocol.c')),*flags,'-lpthread','-lm',*(['-Wl,--wrap=calloc'] if name=='live-render' else []),'-o',str(out)],check=True) - subprocess.run([str(out)],check=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/run-native-lock-tests.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/run-native-lock-tests.py deleted file mode 100755 index 8274e5892e57..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/run-native-lock-tests.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python3 -"""Provision a temporary test-only PAM service; always remove it afterward. -Requires root for the policy file, --user selects the unprivileged test runner. -All compositor/input activity happens in the suite's private headless session. -""" -import argparse -import os -from pathlib import Path -import pwd -import subprocess -import tempfile - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--user', required=True) - parser.add_argument('--binary-dir', type=Path, help='Use installed binaries instead of files/') - parser.add_argument('--test-script', type=Path, help='Alternative private lock integration suite') - parser.add_argument('--stage', type=Path, help='Private compositor and renderer fixture') - args = parser.parse_args() - if os.getuid() != 0 or pwd.getpwnam(args.user).pw_uid == 0: - parser.error('Run as root and select a non-root user') - package = Path(__file__).resolve().parents[1] - service = f'digitalrain-test-{os.getpid()}' - policy = Path('/etc/pam.d') / service - with tempfile.TemporaryDirectory(prefix='digitalrain-test-pam-') as temporary: - directory = Path(temporary) - directory.chmod(0o755) - module, worker = directory / 'pam-test.so', directory / 'digitalrain-auth' - subprocess.run(['cc', '-shared', '-fPIC', '-o', str(module), str(package / 'tests/pam-test.c'), '-lpam'], check=True) - subprocess.run(['cc', '-O2', f'-DDIGITALRAIN_PAM_SERVICE="{service}"', '-o', str(worker), str(package / 'digitalrain-auth.c'), '-lpam'], check=True) - module.chmod(0o755) - worker.chmod(0o755) - with policy.open('x') as stream: - stream.write(f'auth required {module}\naccount required {module}\n') - try: - policy.chmod(0o644) - command = ['runuser', '-u', args.user, '--', 'python3', str(args.test_script or package / 'tests/test-native-lock.py'), '--auth-helper', str(worker)] - if args.stage: - command += ['--stage', str(args.stage.resolve())] - if args.binary_dir: - command += ['--binary-dir', str(args.binary_dir.resolve())] - return subprocess.run(command).returncode - finally: - policy.unlink() - - -if __name__ == '__main__': - raise SystemExit(main()) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-audio.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-audio.py deleted file mode 100755 index 6598f77414f3..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-audio.py +++ /dev/null @@ -1,76 +0,0 @@ -"""Playback analysis using private PipeWire, policy-only WirePlumber and virtual sinks. -Uses a private D-Bus session; no hardware monitors or desktop sockets are used. -""" -import array, json, math, os, pathlib, socket, struct, subprocess, sys, tempfile, time, wave -binary=str(pathlib.Path(sys.argv[1]).resolve()) -root=pathlib.Path(tempfile.mkdtemp(prefix='digitalrain-audio-test-'));children=[] -env=os.environ.copy();env.update(XDG_RUNTIME_DIR=str(root),XDG_CONFIG_HOME=str(root/'config'),XDG_STATE_HOME=str(root/'state'),PIPEWIRE_REMOTE='pipewire-0') -for k in ['PIPEWIRE_CONFIG_DIR','PIPEWIRE_CONFIG_NAME','WIREPLUMBER_CONFIG_DIR','PIPEWIRE_RUNTIME_DIR']:env.pop(k,None) -def spawn(args,**kwargs): - with (root/f'child-{len(children)}.log').open('w') as out: p=subprocess.Popen(args,env=env,stdout=out,stderr=out,**kwargs) - children.append(p);return p -def command(*args):return subprocess.check_output(args,env=env,stderr=subprocess.STDOUT,text=True,timeout=5) -def until(fn,timeout=8): - end=time.monotonic()+timeout - while time.monotonic()<end: - try: - value=fn() - if value:return value - except (subprocess.CalledProcessError,ConnectionError):pass - time.sleep(.05) - raise AssertionError('timed out') -def sinks(): - return {n['info']['props']['node.name']:n['id'] for n in json.loads(command('pw-dump')) if n['type']=='PipeWire:Interface:Node' and n.get('info',{}).get('props',{}).get('media.class')=='Audio/Sink'} -def create(name): - command('pw-cli','create-node','adapter',f'{{ factory.name = support.null-audio-sink node.name = {name} media.class = Audio/Sink object.linger = true audio.position = [ FL FR ] }}') - return until(lambda:sinks().get(name)) -def default(name):command('pw-metadata','-n','default','0','default.audio.sink',json.dumps({'name':name}),'Spa:String:JSON') -connections=[] -def helper(source): - left,right=socket.socketpair(socket.AF_UNIX,socket.SOCK_SEQPACKET);left.settimeout(3) - p=spawn([binary,str(right.fileno()),source],pass_fds=[right.fileno()]);right.close();connections.append(left);return p,left -def collect(s,seconds): - end=time.monotonic()+seconds;values=[] - while time.monotonic()<end: - raw=s.recv(1024);assert len(raw)==24,raw - magic,available,timestamp,rms,peak=struct.unpack('=IIdff',raw) - assert magic==0x44524132 and 0<=rms<=1 and 0<=peak<=1 - values.append((timestamp,available,rms,peak)) - return values -def tone(name, amplitude=.25, seconds=2): - path=root/f'{name}-{amplitude}.wav' - samples=array.array('h',(int(amplitude*32767*math.sin(2*math.pi*440*i/48000)) for i in range(int(seconds*48000)) for _ in range(2))) - with wave.open(str(path),'wb') as w:w.setnchannels(2);w.setsampwidth(2);w.setframerate(48000);w.writeframes(samples.tobytes()) - return spawn(['pw-play','--target',name,str(path)]) -try: - server=spawn(['pipewire']);until(lambda:(root/'pipewire-0').exists()) - policy=spawn(['dbus-run-session','--','wireplumber','--profile','policy']);until(lambda:'default' in command('pw-metadata','-l')) - first=create('rain-test-one');second=create('rain-test-two');assert set(sinks())=={'rain-test-one','rain-test-two'},sinks() - default('rain-test-one') - process,s=helper('default-output');until(lambda:any(v[1] for v in collect(s,.15))) - playback=tone('rain-test-one');values=collect(s,1.6) - assert max(v[2] for v in values)>.12 and max(v[3] for v in values)>.23,values - assert max(v[2] for v in values)<.20,values - playback.wait(5);values=collect(s,.6);assert values[-1][2]==0,values - print('Virtual playback RMS/peak and silence: PASS',flush=True) - playback=tone('rain-test-one',seconds=3);collect(s,.4) - command('pw-cli','set-param',str(first),'Props','{ mute = true }');values=collect(s,.7);assert all(v[2]==0 for v in values[-5:]),values - command('pw-cli','set-param',str(first),'Props','{ mute = false }');values=collect(s,.5);assert max(v[2] for v in values)>.12 - playback.wait(5) - default('rain-test-two');time.sleep(.3);playback=tone('rain-test-two');values=collect(s,1.4);assert max(v[2] for v in values)>.12,values;playback.wait(5) - print('Sink mute and default playback-device switch: PASS',flush=True) - named,n=helper('rain-test-one');playback=tone('rain-test-one');values=collect(n,1.4);assert max(v[2] for v in values)>.12;playback.wait(5) - command('pw-cli','destroy',str(first));values=collect(n,.6);assert values[-1][1]==0 and values[-1][2]==0 - first=create('rain-test-one');time.sleep(.3);playback=tone('rain-test-one');values=collect(n,1.4);assert max(v[2] for v in values)>.12;playback.wait(5) - print('Named playback sink unplug/reconnect: PASS',flush=True) - absent,a=helper('missing-output');values=collect(a,.4);assert all(v[1]==0 and v[2]==0 for v in values) - assert set(sinks())=={'rain-test-one','rain-test-two'} - print('Missing sink remains unavailable without input fallback: PASS',flush=True) -finally: - for s in connections:s.close() - for p in reversed(children): - if p.poll() is None: - p.terminate() - try:p.wait(4) - except subprocess.TimeoutExpired:p.kill();p.wait() - print('Artifacts:',root,flush=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-black-rain-timeout.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-black-rain-timeout.py deleted file mode 100755 index 334c9a1f806b..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-black-rain-timeout.py +++ /dev/null @@ -1,178 +0,0 @@ -"""Run ONLY in an isolated Wayfire session with three HEADLESS outputs. - -Usage: python3 test-black-rain-timeout.py /path/to/digitalrain -Needs Wayfire ipc/ipc-rules, wlr-randr, swaybg, grim, and Pillow. -Uses seconds-long timers; does not change the user's config or power outputs off. -""" -import io -import json -import os -from pathlib import Path -import signal -import socket -import struct -import subprocess -import sys -import tempfile -import time - -from PIL import Image - - -def ipc(method, data=None): - with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock: - sock.settimeout(5) - sock.connect(os.environ['WAYFIRE_SOCKET']) - payload = json.dumps({'method': method, 'data': data or {}}).encode() - sock.sendall(struct.pack('<I', len(payload)) + payload) - - def read(n): - result = b'' - while len(result) < n: - part = sock.recv(n - len(result)) - if not part: - raise RuntimeError('IPC disconnected') - result += part - return result - - return json.loads(read(struct.unpack('<I', read(4))[0])) - - -outputs = ipc('window-rules/list-outputs') -assert {o['name'] for o in outputs} == {'HEADLESS-1', 'HEADLESS-2', 'HEADLESS-3'}, \ - 'This test must run in its own three-output headless compositor' -binary = str(Path(sys.argv[1]).resolve()) -children = [] -logs = tempfile.TemporaryDirectory(prefix='digitalrain-black-test-') - - -def spawn(command): - log = open(Path(logs.name) / f'{len(children)}.log', 'w') - process = subprocess.Popen(command, stdout=log, stderr=subprocess.STDOUT) - log.close() - children.append(process) - return process - - -def rain(output, timeout, transparent=True, transparent_timeout=1, fade_frames=6): - command = [binary, '--render', '--output', output, '--color', 'adaptive', - '--fade-frames', str(fade_frames), '--min-density', '0.8', - '--max-density', '0.8', '--black-rain-timeout', str(timeout), - '--transparent-timeout', str(transparent_timeout)] - if transparent: - command.append('--transparent') - return spawn(command) - - -def shot(output): - png = subprocess.check_output(['grim', '-o', output, '-']) - return Image.open(io.BytesIO(png)).convert('RGB') - - -def black(output): - return shot(output).getbbox() is None - - -def desktop_pixels(output): - return sum(r > 150 and g < 20 and b < 20 - for r, g, b in shot(output).get_flattened_data()) - - -def until(check, timeout=6): - deadline = time.monotonic() + timeout - while time.monotonic() < deadline: - if check(): - return - time.sleep(.1) - raise AssertionError('Timed out waiting for expected state') - - -def cpu_ticks(process): - fields = Path(f'/proc/{process.pid}/stat').read_text().split() - return int(fields[13]) + int(fields[14]) - - -def dismiss(process): - process.send_signal(signal.SIGUSR1) - assert process.wait(timeout=3) == 0 - - -try: - command = ['wlr-randr'] - for i in range(1, 4): - command += ['--output', f'HEADLESS-{i}', '--custom-mode', '640x480', - '--scale', '1', '--transform', 'normal', '--pos', f'{(i-1)*640},0'] - subprocess.run(command, check=True) - spawn(['swaybg', '-c', 'cc0000']) - until(lambda: desktop_pixels('HEADLESS-1') > 100000) - - a = rain('HEADLESS-1', 2) - time.sleep(.7) - assert desktop_pixels('HEADLESS-1') > 100000, 'transparent stage missing' - time.sleep(1) - assert desktop_pixels('HEADLESS-1') == 0 - assert not black('HEADLESS-1'), 'rain must continue after background blackens' - b = rain('HEADLESS-2', 2) - c = rain('HEADLESS-3', 0, transparent=False) - until(lambda: black('HEADLESS-1')) - assert not black('HEADLESS-2'), 'later output must keep its own timer' - assert not black('HEADLESS-3'), 'zero must disable the rain-stop timer' - until(lambda: black('HEADLESS-2')) - assert all(p.poll() is None for p in (a, b, c)), 'black must retain the renderer' - assert not black('HEADLESS-3') - print('Transparent -> rain on black -> solid black; independent timers; zero disables: PASS', flush=True) - dismiss(c) - - # Palette notifications must not restart animation or redraw a black output. - time.sleep(.4) - ticks = [cpu_ticks(p) for p in (a, b)] - stats = ipc('test/stats') # Optional fixture measuring compositor render passes. - a.send_signal(signal.SIGRTMIN) - time.sleep(1) - delta = [cpu_ticks(p) - ticks[i] for i, p in enumerate((a, b))] - assert max(delta) <= 1, delta - after = ipc('test/stats') - if 'passes' in stats: - assert after['passes'] == 0, after - assert black('HEADLESS-1') and black('HEADLESS-2') - print(f'Static black sleeps; palette update keeps it black (CPU ticks {delta}): PASS', flush=True) - - subprocess.run(['wlr-randr', '--output', 'HEADLESS-1', '--custom-mode', '800x600'], check=True) - time.sleep(.4) - resized = shot('HEADLESS-1') - assert resized.size == (800, 600) and resized.getbbox() is None - assert len(ipc('window-rules/list-outputs')) == 3 - print('Resize remains fully black; outputs stay enabled: PASS', flush=True) - - # Capture the reveal: its pixels may only be black or the red desktop, not rain. - a.send_signal(signal.SIGUSR1) - while a.poll() is None: - reveal = shot('HEADLESS-1') - assert all(g == 0 and b == 0 for r, g, b in reveal.get_flattened_data()) - assert a.returncode == 0 - assert desktop_pixels('HEADLESS-1') > 100000 - dismiss(b) - print('Activity reveals desktop without bringing rain back: PASS', flush=True) - - c = rain('HEADLESS-3', .5, transparent=True, transparent_timeout=0) - time.sleep(1.4) - assert desktop_pixels('HEADLESS-3') > 100000 - dismiss(c) - c = rain('HEADLESS-3', .8, transparent=False) - time.sleep(.7) - assert not black('HEADLESS-3') - until(lambda: black('HEADLESS-3')) - assert c.poll() is None - c.terminate() - assert c.wait(timeout=2) == 0 - print('Timer waits for black; initially opaque mode and termination work: PASS', flush=True) -finally: - for process in reversed(children): - if process.poll() is None: - process.terminate() - try: - process.wait(timeout=5) - except subprocess.TimeoutExpired: - process.kill() - process.wait() - logs.cleanup() diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-cursor-wakeup.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-cursor-wakeup.py deleted file mode 100755 index f86209b1adb3..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-cursor-wakeup.py +++ /dev/null @@ -1,176 +0,0 @@ -"""Exercise the production manager against private, simulated Wayfire IPC. - -Run from any directory with Python 3; --source accepts an older digitalrain.c -for before/after checks. No desktop sockets or configuration are used. -""" -import argparse -import json -import os -from pathlib import Path -import shlex -import socket -import struct -import subprocess -import tempfile -import threading -import time - - -package = Path(__file__).resolve().parents[1] -parser = argparse.ArgumentParser() -parser.add_argument('--source', type=Path, default=package / 'digitalrain.c') -args = parser.parse_args() - -HARNESS = r''' -#define main digitalrain_program_main -#include DIGITALRAIN_SOURCE -#undef main -#include <assert.h> - -int main(int argc, char **argv) { - assert(argc == 2); - pthread_mutex_init(&G.lock, NULL); - G.output_count = 3; - mgr_init_output(&G.outputs[0], "DP-1", mgr_monotime()); - mgr_init_output(&G.outputs[1], "DP-2", mgr_monotime()); - mgr_init_output(&G.outputs[2], "DP-3", mgr_monotime()); - strcpy(G.outputs[0].state, "off"); - strcpy(G.outputs[1].state, "off"); - G.seat_idle = strcmp(argv[1], "active") != 0; - if (!strncmp(argv[1], "fade", 4)) { - /* A renderer that deliberately needs 1.2 seconds to finish its fade. */ - int ready[2]; - assert(pipe(ready) == 0); - pid_t child = fork(); - assert(child >= 0); - if (child == 0) { - close(ready[0]); - signal(SIGUSR1, SIG_IGN); - assert(write(ready[1], "r", 1) == 1); - usleep(1200000); - _exit(0); - } - close(ready[1]); - char byte; - assert(read(ready[0], &byte, 1) == 1); - close(ready[0]); - G.wayfire_cursor_hidden = 1; - G.outputs[0].pids[0] = child; - G.outputs[0].pid_count = 1; - mgr_unblank_output("DP-1"); - assert(!strcmp(G.outputs[0].state, "on")); - assert(!strcmp(G.outputs[1].state, "off")); - assert(G.outputs[0].pid_count == 0); - /* The production loop now reaps fades asynchronously. Keep polling - * cursor state while this detached renderer is still fading. */ - double end = mgr_monotime() + 1.3; - while (mgr_monotime() < end) { - mgr_update_wayfire_cursor(); mgr_reap_fading(); usleep(100000); - } - mgr_reap_fading(); - } else { - mgr_update_wayfire_cursor(); - mgr_update_wayfire_cursor(); /* repeated polls must be idempotent */ - } - pthread_mutex_destroy(&G.lock); - return 0; -} -''' - - -def read(sock, count): - data = b'' - while len(data) < count: - part = sock.recv(count - len(data)) - if not part: - raise RuntimeError('IPC disconnected') - data += part - return data - - -def check(binary, runtime, case): - path = runtime / 'ipc.socket' - hidden = case != 'idle' - changes = [] - errors = [] - started = time.monotonic() - done = threading.Event() - with socket.socket(socket.AF_UNIX) as server: - server.bind(str(path)) - server.listen() - server.settimeout(.05) - - def serve(): - nonlocal hidden - try: - while not done.is_set(): - try: - connection, _ = server.accept() - except socket.timeout: - continue - with connection: - connection.settimeout(2) - request = json.loads(read(connection, struct.unpack('=I', read(connection, 4))[0])) - method = request['method'] - elapsed = time.monotonic() - started - output = 'DP-1' - if case == 'fade-crossing': - if elapsed >= .6: - output = 'DP-3' - elif elapsed >= .25: - output = 'DP-2' - if method == 'digitalrain/cursor_status': - response = dict(valid=True, hidden=hidden, output=output) - else: - assert method in ('digitalrain/hide_cursor', 'digitalrain/unhide_cursor'), method - hidden = method == 'digitalrain/hide_cursor' - changes.append((hidden, elapsed)) - response = dict(result='ok') - payload = json.dumps(response).encode() - connection.sendall(struct.pack('=I', len(payload)) + payload) - except Exception as error: - errors.append(error) - - thread = threading.Thread(target=serve) - thread.start() - try: - env = dict(os.environ, WAYFIRE_SOCKET=str(path)) - result = subprocess.run([str(binary), case], env=env, - capture_output=True, text=True, timeout=5) - assert result.returncode == 0, result.stdout + result.stderr - finally: - done.set() - thread.join(timeout=3) - path.unlink() - assert not errors, errors - expected = [True] if case == 'idle' else [False] - if case == 'fade-crossing': - expected = [False, True, False] - assert [value for value, _ in changes] == expected, changes - if case.startswith('fade'): - assert changes[0][1] < .5, f'cursor was hidden during fade: {changes}' - assert changes[-1][1] < 1.1, f'cursor tracking waited for renderer exit: {changes}' - print(f'PASS {case}: {changes}') - - -with tempfile.TemporaryDirectory(prefix='digitalrain-cursor-test-') as directory: - runtime = Path(directory) - harness = runtime / 'test.c' - harness.write_text(HARNESS) - binary = runtime / 'test' - flags = shlex.split(subprocess.check_output( - ['pkg-config', '--cflags', '--libs', 'json-c', 'wayland-client', - 'wayland-egl', 'egl', 'glesv2', 'freetype2'], text=True)) - subprocess.run(['cc', '-std=c11', '-O2', '-I' + str(package), - '-DDIGITALRAIN_SOURCE="' + str(args.source.resolve()) + '"', - str(harness), str(package / 'tomlc17.c'), *map(str, sorted((package).glob('*-protocol.c'))), - *flags, '-lpthread', '-lm', '-o', str(binary)], check=True) - failures = [] - for case in ['idle', 'active', 'fade', 'fade-crossing']: - try: - check(binary, runtime, case) - except AssertionError as error: - failures.append(case) - print(f'FAIL {case}: {error}') - if failures: - raise SystemExit('Failed: ' + ', '.join(failures)) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-glyph-compositing.c b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-glyph-compositing.c deleted file mode 100644 index f8b1e27c7f79..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-glyph-compositing.c +++ /dev/null @@ -1,76 +0,0 @@ -/* Exercise the production renderer with a known solid/antialiased glyph. - * No Wayland connection is needed: an EGL pbuffer provides the framebuffer. */ -#define main digitalrain_program_main -#ifndef DIGITALRAIN_SOURCE -#define DIGITALRAIN_SOURCE "../digitalrain.c" -#endif -#include DIGITALRAIN_SOURCE -#undef main -#include <assert.h> - -static void draw_glyph(unsigned char coverage, float fade, unsigned char pixel[4]) { - glBindTexture(GL_TEXTURE_2D, S.atlas.texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, 1, 1, 0, - GL_LUMINANCE, GL_UNSIGNED_BYTE, &coverage); - glClearColor(0, 0, 0, 0); - glClear(GL_COLOR_BUFFER_BIT); - S.fade_opacity = fade; - render_frame(); - glReadPixels(8, 8, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); - assert(glGetError() == GL_NO_ERROR); -} - -int main(void) { - EGLDisplay display = eglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA, - EGL_DEFAULT_DISPLAY, NULL); - assert(display != EGL_NO_DISPLAY && eglInitialize(display, NULL, NULL)); - const EGLint config_attrs[] = {EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_NONE}; - EGLConfig config; - EGLint count; - assert(eglChooseConfig(display, config_attrs, &config, 1, &count) && count == 1); - const EGLint context_attrs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; - EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, context_attrs); - const EGLint surface_attrs[] = {EGL_WIDTH, 16, EGL_HEIGHT, 16, EGL_NONE}; - EGLSurface surface = eglCreatePbufferSurface(display, config, surface_attrs); - assert(eglMakeCurrent(display, surface, surface, context)); - assert(render_init_shaders()); - S.surface_width = S.surface_height = 16; - render_set_projection(16, 16); - S.num_depth_layers = 1; - render_cell_t cell = {.codepoint = 'A', .age = 5}; - render_column_t column = {.active = true, .trail_len = 10, .color = {1, .5, .25}}; - S.layers[0] = (depth_layer_t){.cols = 1, .rows = 1, .cell_w = 16, - .cell_h = 16, .font_scale = 1, .opacity = .2, - .cells = &cell, .columns = &column}; - S.atlas.glyph_count = 1; - S.atlas.glyphs[0] = (glyph_info_t){.codepoint = 'A', .u1 = 1, .v1 = 1, - .width = 16, .height = 16, .bearing_y = 16}; - glGenTextures(1, &S.atlas.texture); - glBindTexture(GL_TEXTURE_2D, S.atlas.texture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - unsigned char pixel[4]; - draw_glyph(255, 1, pixel); - printf("Dim far-layer trail RGBA: %u %u %u %u\n", pixel[0], pixel[1], pixel[2], pixel[3]); - fflush(stdout); - assert(pixel[3] == 255 && pixel[0] > 0 && pixel[0] < 20); - /* The dim stroke must cover a white application pixel, not blend with it. */ - glClearColor(1, 1, 1, 1); glClear(GL_COLOR_BUFFER_BIT); - render_frame(); glReadPixels(8, 8, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); - assert(pixel[0] < 20 && pixel[1] < 20 && pixel[2] < 20); - draw_glyph(128, 1, pixel); - assert(pixel[3] >= 127 && pixel[3] <= 129); /* antialiasing remains */ - draw_glyph(255, .5, pixel); - assert(pixel[3] >= 127 && pixel[3] <= 129); /* phase fades remain */ - draw_glyph(0, 1, pixel); - assert(pixel[3] == 0); /* empty background remains transparent */ - glDeleteTextures(1, &S.atlas.texture); - glDeleteBuffers(1, &S.vbo); glDeleteProgram(S.shader_program); free(S.vertex_buf); - eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - eglDestroySurface(display, surface); eglDestroyContext(display, context); eglTerminate(display); - puts("Glyph coverage, dimming, antialiasing, fade and transparent background: PASS"); - return 0; -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-hide-desktop.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-hide-desktop.py deleted file mode 100755 index 179c19855c53..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-hide-desktop.py +++ /dev/null @@ -1,305 +0,0 @@ -"""Pixel/lifecycle regression in a private three-output Wayfire session. - -Run as a regular user: python3 tests/test-hide-desktop.py [--existing-tests] -Needs built files/, Wayfire, grim, wlr-randr, swaybg, pkg-config, C++, and Pillow. -No installed plugins, desktop settings or real display sockets are modified. -""" -import argparse -import io -import json -import os -from pathlib import Path -import shlex -import shutil -import signal -import socket -import struct -import subprocess -import tempfile -import time - -from PIL import Image - -parser = argparse.ArgumentParser() -parser.add_argument('--existing-tests', action='store_true') -parser.add_argument('--upgrade-from', type=Path, help='Old helper library to load before the candidate') -args = parser.parse_args() -assert os.geteuid() != 0, 'Run as a regular user' -package = Path(__file__).resolve().parents[1] -runtime = Path(tempfile.mkdtemp(prefix='digitalrain-desktop-test-')) -children = [] -connections = [] -env = os.environ.copy() -for key in ['WAYLAND_DISPLAY', 'WAYFIRE_SOCKET', '_WAYFIRE_SOCKET', 'DISPLAY', - 'WAYFIRE_DEFAULT_CONFIG_BACKEND', 'WAYFIRE_CONFIG_FILE', 'LD_LIBRARY_PATH']: - env.pop(key, None) -env.update(XDG_RUNTIME_DIR=str(runtime), WLR_BACKENDS='headless', - WLR_HEADLESS_OUTPUTS='3', WLR_RENDERER='gles2', - WLR_RENDER_DRM_DEVICE=env.get('WLR_RENDER_DRM_DEVICE', '/dev/dri/renderD128'), - WAYFIRE_PLUGIN_PATH=f'{runtime}:{package}', - WAYFIRE_PLUGIN_XML_PATH=f'{runtime}:{package}') - - -def spawn(command): - with (runtime / f'child-{len(children)}.log').open('w') as log: - process = subprocess.Popen(command, env=env, stdout=log, stderr=log) - children.append(process) - return process - - -def until(check, timeout=6): - deadline = time.monotonic() + timeout - while time.monotonic() < deadline: - if check(): - return - time.sleep(.05) - raise AssertionError('Timed out waiting for expected state') - - -def read(sock, count): - result = b'' - while len(result) < count: - part = sock.recv(count - len(result)) - if not part: - raise RuntimeError('IPC disconnected') - result += part - return result - - -def request(sock, method, data=None): - payload = json.dumps(dict(method=method, data=data or {})).encode() - sock.sendall(struct.pack('=I', len(payload)) + payload) - return json.loads(read(sock, struct.unpack('=I', read(sock, 4))[0])) - - -def connect(): - sock = socket.socket(socket.AF_UNIX) - sock.settimeout(3) - sock.connect(env['WAYFIRE_SOCKET']) - connections.append(sock) - return sock - - -def ipc(method, data=None): - with connect() as sock: - return request(sock, method, data) - - -def hold(output, fade_ms=0): - sock = connect() - assert request(sock, 'digitalrain/hide_desktop', dict(output=output, fade_ms=fade_ms)) == {'result': 'ok'} - return sock - - -def shot(output='HEADLESS-1'): - png = subprocess.check_output(['grim', '-o', output, '-'], env=env) - return Image.open(io.BytesIO(png)).convert('RGB') - - -def pixels(color, output='HEADLESS-1'): - return sum(p == color for p in shot(output).get_flattened_data()) - - -def solid(key, layer, output='HEADLESS-1', close=False, x=0, y=0, w=100, h=80, - r=0.0, g=0.0, b=1.0): - data = dict(key=key, output=output, layer=layer, x=x, y=y, w=w, h=h, r=r, g=g, b=b) - if close: - data['close'] = True - assert ipc('test/solid', data) == {'result': 'ok'} - - -def rain(output='HEADLESS-1', hide=True, transparent=True, timeout=0, black_timeout=0, fade_frames=6): - cmd = [str(package / 'digitalrain'), '--render', '--output', output, - '--color', 'green', '--fade-frames', str(fade_frames), '--transparent-timeout', str(timeout), - '--black-rain-timeout', str(black_timeout)] - if transparent: - cmd += ['--transparent'] - if hide: - cmd += ['--hide-desktop'] - return spawn(cmd) - - -def dismiss(process): - process.send_signal(signal.SIGUSR1) - assert process.wait(timeout=4) == 0 - - -try: - flags = shlex.split(subprocess.check_output( - ['pkg-config', '--cflags', '--libs', 'wayfire', 'wf-utils'], text=True)) - subprocess.run(['c++', '-std=c++17', '-fPIC', '-shared', '-DWAYFIRE_PLUGIN', - '-DWLR_USE_UNSTABLE', str(package / 'tests/desktop-test.cpp'), - '-o', str(runtime / 'libdesktop-test.so'), *flags], check=True) - (runtime / 'desktop-test.xml').write_text( - '<wayfire><plugin name="desktop-test"><_short>Test</_short></plugin></wayfire>') - if args.upgrade_from: - shutil.copyfile(args.upgrade_from, runtime / 'libdigitalrain-desktop.so') - config = runtime / 'wayfire.ini' - plugins = 'ipc ipc-rules digitalrain-desktop desktop-test' - - def configure(enabled=True): - config.write_text('[core]\nplugins = ' + (plugins if enabled else plugins.replace('digitalrain-desktop ', '')) + - '\nxwayland = false\nvwidth = 1\nvheight = 1\n' - '[workarounds]\nenable_so_unloading = false\n') - - configure() - compositor = spawn(['wayfire', '-c', str(config)]) - until(lambda: list(runtime.glob('wayfire-*.socket')) and - [p for p in runtime.glob('wayland-*') if not p.name.endswith('.lock')]) - env['WAYFIRE_SOCKET'] = str(next(runtime.glob('wayfire-*.socket'))) - env['WAYLAND_DISPLAY'] = next(p.name for p in runtime.glob('wayland-*') if not p.name.endswith('.lock')) - if args.upgrade_from: - assert 'digitalrain/show_desktop' not in ipc('list-methods')['methods'] - (runtime / 'libdigitalrain-desktop.so').unlink() - ipc('wayfire/reload-plugins') - until(lambda: 'digitalrain/show_desktop' in ipc('list-methods')['methods']) - print('Upgrade from resident old helper without restarting Wayfire: PASS', flush=True) - assert {o['name'] for o in ipc('window-rules/list-outputs')} == { - 'HEADLESS-1', 'HEADLESS-2', 'HEADLESS-3'} - cmd = ['wlr-randr'] - for i in range(1, 4): - cmd += ['--output', f'HEADLESS-{i}', '--custom-mode', '640x480', '--pos', f'{(i-1)*640},0'] - subprocess.run(cmd, env=env, check=True) - background = spawn(['swaybg', '-c', 'cc0000']) - red, blue = (204, 0, 0), (0, 0, 255) - until(lambda: pixels(red) > 300000) - for index, layer in enumerate([1, 2, 3, 4, 5, 8]): - solid(str(layer), layer, x=index * 100, y=100) - solid('second', 2, output='HEADLESS-2', y=100) - until(lambda: pixels(blue) == 48000) - baseline = shot().tobytes() - - a = hold('HEADLESS-1') - until(lambda: pixels(blue) == 0) - assert pixels(red) == 640 * 480, 'wallpaper must remain completely visible' - assert pixels(blue, 'HEADLESS-2') == 8000, 'other output changed' - assert request(a, 'digitalrain/hide_desktop', {'output': 'HEADLESS-1'}) == {'result': 'ok'} - b = hold('HEADLESS-1') - a.close() - time.sleep(.1) - assert pixels(blue) == 0, 'one renderer must not release another renderer' - solid('new', 3, y=300) - assert pixels(blue) == 0, 'new windows must also stay hidden' - solid('lock', 7, y=250, r=1.0, b=1.0) - until(lambda: pixels((255, 0, 255)) == 8000) - solid('lock', 7, close=True) - b.close() - until(lambda: pixels(blue) == 56000) - solid('new', 3, close=True) - until(lambda: shot().tobytes() == baseline) - print('All app/panel layers hidden; wallpaper/lock retained; outputs isolated; balanced lifetime: PASS', flush=True) - - a = hold('HEADLESS-1', fade_ms=2000) - time.sleep(.6) - fading = shot() - for index in range(6): - r, g, b = fading.getpixel((index * 100 + 50, 140)) - assert 20 < r < 180 and g == 0 and 30 < b < 240, (index, (r, g, b)) - assert fading.getpixel((600, 400)) == red, 'wallpaper must not fade' - # Resume reverses from the current opacity without an abrupt jump. - before = fading.getpixel((50, 140)) - assert request(a, 'digitalrain/show_desktop') == {'result': 'ok'} - immediate = shot().getpixel((50, 140)) - assert abs(immediate[2] - before[2]) < 20, (before, immediate) - time.sleep(.4) - recovering = shot().getpixel((50, 140)) - assert before[2] < recovering[2] < 250, (before, recovering) - until(lambda: shot().tobytes() == baseline) - a.close() - print('All six desktop layers fade smoothly; wake reverses a partial fade without jumping: PASS', flush=True) - - a = hold('HEADLESS-1', fade_ms=1000) - until(lambda: pixels(red) == 640 * 480) - assert request(a, 'digitalrain/show_desktop') == {'result': 'ok'} - time.sleep(.35) - fading = shot() - for index in range(6): - r, g, b = fading.getpixel((index * 100 + 50, 140)) - assert 20 < r < 180 and g == 0 and 30 < b < 240, (index, (r, g, b)) - assert pixels(blue, 'HEADLESS-2') == 8000 - # A repeated wake must not restart the animation. - assert request(a, 'digitalrain/show_desktop') == {'result': 'ok'} - until(lambda: shot().tobytes() == baseline) - a.close() - print('All six hidden desktop layers fade back in; repeated wake is idempotent: PASS', flush=True) - - p = rain(hide=False) - time.sleep(.7) - assert pixels(blue) > 20000, 'default behavior must preserve apps' - dismiss(p) - until(lambda: shot().tobytes() == baseline) - p = rain(fade_frames=30) - time.sleep(.4) - fading = list(shot().get_flattened_data()) - assert sum(20 < r < 180 and b > g + 30 for r, g, b in fading) > 10000 - assert any(g > 0 for r, g, b in fading), 'rain must fade in at the same time' - time.sleep(.8) - until(lambda: pixels(blue) == 0) - assert pixels(red) > 150000, 'transparent stage must show wallpaper' - assert any(g > 0 for r, g, b in shot().get_flattened_data()), 'rain missing' - p.send_signal(signal.SIGUSR1) - time.sleep(.3) - recovering = list(shot().get_flattened_data()) - assert sum(20 < r < 180 and b > g + 30 for r, g, b in recovering) > 10000 - assert sum(pixel == blue for pixel in recovering) < 1000, 'resume must fade, not jump' - assert p.wait(timeout=4) == 0 - until(lambda: shot().tobytes() == baseline) - p = rain() - until(lambda: pixels(blue) == 0) - p.kill() - p.wait(timeout=3) - until(lambda: shot().tobytes() == baseline) - print('Default off; wallpaper-only rain; desktop fade-in with rain fade-out; SIGKILL recovery: PASS', flush=True) - - p = rain(timeout=1, black_timeout=.6, fade_frames=30) - until(lambda: pixels(blue) == 0) - until(lambda: shot().getbbox() is None) - assert p.poll() is None - p.send_signal(signal.SIGUSR1) - time.sleep(.4) - recovering = list(shot().get_flattened_data()) - assert any(r > 10 and b > 10 and g == 0 for r, g, b in recovering), 'desktop must fade in from black' - assert p.wait(timeout=4) == 0 - until(lambda: shot().tobytes() == baseline) - print('Wallpaper rain -> black rain -> solid black -> desktop fade-in: PASS', flush=True) - - a = hold('HEADLESS-1') - until(lambda: pixels(blue) == 0) - configure(False) - until(lambda: 'digitalrain/hide_desktop' not in ipc('list-methods')['methods']) - until(lambda: shot().tobytes() == baseline) - a.close() - p = rain() - time.sleep(.7) - assert p.poll() is None and pixels(blue) > 20000, 'missing plugin must fall back' - dismiss(p) - until(lambda: shot().tobytes() == baseline) - configure() - until(lambda: 'digitalrain/hide_desktop' in ipc('list-methods')['methods']) - a = hold('HEADLESS-2') - subprocess.run(['wlr-randr', '--output', 'HEADLESS-2', '--off'], env=env, check=True) - a.close() - subprocess.run(['wlr-randr', '--output', 'HEADLESS-2', '--on'], env=env, check=True) - assert compositor.poll() is None - assert shot().tobytes() == baseline - print('Plugin unload/missing plugin and output removal recover safely: PASS', flush=True) - - for key in ['1', '2', '3', '4', '5', '8', 'second']: - solid(key, 2, close=True) - background.terminate() - background.wait(timeout=3) - if args.existing_tests: - subprocess.run(['python3', str(package / 'tests/test-black-rain-timeout.py'), - str(package / 'digitalrain')], env=env, check=True) -finally: - for sock in connections: - sock.close() - for child in reversed(children): - if child.poll() is None: - child.terminate() - try: - child.wait(timeout=5) - except subprocess.TimeoutExpired: - child.kill() - child.wait() - print(f'Test artifacts: {runtime}', flush=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-inhibition.c b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-inhibition.c deleted file mode 100644 index d9985245d7db..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-inhibition.c +++ /dev/null @@ -1,69 +0,0 @@ -#define main digitalrain_program_main -#ifndef DIGITALRAIN_SOURCE -#define DIGITALRAIN_SOURCE "../digitalrain.c" -#endif -#include DIGITALRAIN_SOURCE -#undef main -#include <assert.h> - -int main(void) { - struct json_object *response = json_tokener_parse("{\"outputs\":[\"DP-2\",\"HDMI-A-1\"]}"); - assert(mgr_response_inhibits_output(response, "DP-2")); - assert(mgr_response_inhibits_output(response, "HDMI-A-1")); - assert(!mgr_response_inhibits_output(response, "DP-1")); - assert(!mgr_response_inhibits_output(response, "DP")); - json_object_put(response); - const char *invalid[] = {"{}", "{\"error\":\"unavailable\"}", - "{\"outputs\":null}", "{\"outputs\":\"DP-2\"}", - "{\"outputs\":[false,12,null,{}]}", "{\"outputs\":[]}"}; - for (size_t i = 0; i < sizeof(invalid) / sizeof(invalid[0]); i++) { - response = json_tokener_parse(invalid[i]); - assert(!mgr_response_inhibits_output(response, "DP-2")); - json_object_put(response); - } - assert(!mgr_response_inhibits_output(NULL, "DP-2")); - pthread_mutex_init(&G.lock, NULL); - /* Typing on DP-3 after brief pauses must not postpone idle DP-1/DP-2. - * Their 300-second countdowns have already expired in this scenario. */ - double idle_since = mgr_monotime() - 310.0; - G.output_count = 3; - mgr_init_output(&G.outputs[0], "DP-1", idle_since); - mgr_init_output(&G.outputs[1], "DP-2", idle_since); - mgr_init_output(&G.outputs[2], "DP-3", idle_since); - snprintf(G.focused_output, sizeof(G.focused_output), "DP-3"); - for (int i = 0; i < 5; ++i) { - mgr_idle_handle_idled(NULL, NULL); - assert(G.seat_idle); - double resumed_at = mgr_monotime(); - mgr_idle_handle_resumed(NULL, NULL); - assert(!G.seat_idle); - assert(G.outputs[0].last_input == idle_since); - assert(G.outputs[1].last_input == idle_since); - assert(G.outputs[2].last_input >= resumed_at); - assert(mgr_monotime() - G.outputs[0].last_input >= 300.0); - } - mgr_update_focused_output("DP-2"); - double left_dp3 = G.outputs[2].last_input; - mgr_idle_handle_idled(NULL, NULL); - mgr_idle_handle_resumed(NULL, NULL); - assert(G.outputs[0].last_input == idle_since); - assert(G.outputs[2].last_input == left_dp3); - assert(G.outputs[1].last_input > idle_since); - - /* A temporarily unknown focus cannot justify resetting all outputs. */ - G.focused_output[0] = '\0'; - mgr_idle_handle_resumed(NULL, NULL); - assert(G.outputs[0].last_input == idle_since); - assert(G.outputs[2].last_input == left_dp3); - - G.manual_activation = 1; - G.last_blank_time = mgr_monotime(); - mgr_idle_handle_resumed(NULL, NULL); - assert(G.manual_activation); /* spurious early resume must not undo explicit activation */ - G.last_blank_time -= 4; - mgr_idle_handle_resumed(NULL, NULL); - assert(!G.manual_activation); - pthread_mutex_destroy(&G.lock); - puts("Passed independent output idle timers, focus changes, inhibition filtering, and explicit activation resume checks."); - return 0; -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-live-config-alerts.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-live-config-alerts.py deleted file mode 100755 index 77878d5f113c..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-live-config-alerts.py +++ /dev/null @@ -1,80 +0,0 @@ -"""Production manager config/alert regression in a private, compositor-free runtime.""" -import json, os, pathlib, signal, socket, subprocess, sys, tempfile, time, tomllib -binary = str(pathlib.Path(sys.argv[1]).resolve()) -def until(fn, timeout=5): - deadline = time.monotonic()+timeout - while time.monotonic()<deadline: - if fn(): return - time.sleep(.04) - raise AssertionError('timed out') -with tempfile.TemporaryDirectory(prefix='digitalrain-live-') as directory: - root=pathlib.Path(directory); cfg=root/'config.toml' - base=f'''schema_version = 1 -[global] -TIMEOUT = 3600 -FOCUSED_TIMEOUT = 3600 -CHECK_INTERVAL = 1 -LOG_FILE = "{root}/manager.log" -[matrix] -COLOR = '#00ff00' # retain this comment -MIN_SPEED = 0.01 -[audio] -ENABLED = false -''' - cfg.write_text(base) - env=os.environ.copy();env.update(XDG_RUNTIME_DIR=directory, DIGITALRAIN_CONFIG=str(cfg), WAYLAND_DISPLAY='absent-test-display', WAYFIRE_SOCKET=str(root/'absent-ipc'), _WAYFIRE_SOCKET=str(root/'absent-ipc')) - with (root/'launch.log').open('w') as log: - manager=subprocess.Popen([binary], env=env, stdout=log, stderr=log) - def api(method, **arguments): - with socket.socket(socket.AF_UNIX,socket.SOCK_SEQPACKET) as conn: - conn.settimeout(4);conn.connect(str(root/'digitalrain/control.sock')) - conn.send(json.dumps(dict(version=1,method=method,**arguments)).encode()) - raw=conn.recv(16384);assert raw;return json.loads(raw) - try: - until(lambda:(root/'digitalrain/control.sock').exists()) - assert api('schema')['matrix.MIN_SPEED']['value']==.01 - assert api('settings',settings={'matrix.COLOR':'#123456','matrix.FONT_SIZE':18},save=True).get('error') is None - parsed=tomllib.loads(cfg.read_text());assert parsed['matrix']['COLOR']=='#123456' and parsed['matrix']['FONT_SIZE']==18 - assert '# retain this comment' in cfg.read_text() - assert 'error' not in api('settings',settings={'audio.ENABLED':0,'audio.SPEED_AMOUNT':.5},save=True) - parsed=tomllib.loads(cfg.read_text());assert parsed['audio']['ENABLED'] is False and parsed['audio']['SPEED_AMOUNT']==.5 - assert 'error' not in api('settings', settings={'matrix.FONT_SIZE':22}) - assert 'error' not in api('reload') - assert api('schema')['matrix.FONT_SIZE']['value']==18 - revision=api('status')['settings_revision'];good=cfg.read_text() - cfg.write_text('[matrix]\nFONT_SIZE = "broken"\n');until(lambda:bool(api('status')['config_error'])) - assert api('schema')['matrix.FONT_SIZE']['value']==18 - cfg.write_text(good);until(lambda:not api('status')['config_error']) - replacement=root/'next';replacement.write_text(base.replace("'#00ff00'", '"cyan"'));replacement.replace(cfg) - until(lambda:api('schema')['matrix.COLOR']['value']=='cyan') - assert api('schema')['matrix.FONT_SIZE']['value']==12 - # Missing-parent, dotted keys, inline tables, escaped/multiline string source spans. - for document in [f'global.LOG_FILE="{root}/manager.log"\nmatrix.COLOR="green"\n', f'[global]\nLOG_FILE="{root}/manager.log"\nmatrix_dummy=1\n']: - if 'dummy' in document: continue - cfg.write_text(document);assert 'error' not in api('reload') - assert 'error' not in api('settings',settings={'matrix.FONT_SIZE':16},save=True), cfg.read_text() - assert tomllib.loads(cfg.read_text())['matrix']['FONT_SIZE']==16 - cfg.write_text(f'global = {{ LOG_FILE="{root}/manager.log" }}\nmatrix = {{ COLOR = """green""" }}\n') - assert 'error' not in api('reload') - assert 'error' not in api('settings',settings={'matrix.COLOR':'blue','matrix.FONT_SIZE':20},save=True), cfg.read_text() - assert tomllib.loads(cfg.read_text())['matrix']=={'COLOR':'blue','FONT_SIZE':20} - assert 'error' in api('settings',settings={'matrix.MIN_SPEED':-1}) - assert 'error' in api('settings',settings={'matrix.MIN_SPEED':10,'matrix.MAX_SPEED':1}) - assert 'error' in api('alert',source='test',id='x',severity='error',seconds=0) - assert api('alert',source='test',id='x',severity='error',seconds=1)['accepted'] - assert api('status')['alert_count']==1 - api('alert',source='test',id='x',severity='warning',seconds=1) - assert api('list_alerts')['total']==1 - until(lambda:api('list_alerts')['total']==0) - for i in range(64): assert api('alert',source='test',id=str(i),severity='info',seconds=60)['accepted'] - assert 'error' in api('alert',source='test',id='overflow',severity='error',seconds=60) - assert len(api('list_alerts')['alerts'])==16 and api('list_alerts')['next_offset']==16 - assert len(api('list_alerts',offset=48)['alerts'])==16 - api('clear_alert',source='test',id='0');api('clear_alert',source='test',id='0') - assert api('list_alerts')['total']==63 - assert api('status')['audio']['state']=='disabled' - children=pathlib.Path(f'/proc/{manager.pid}/task/{manager.pid}/children').read_text() - assert not children.strip(), children - print('TOML validation, save preservation, atomic replacement, reset defaults, alert limits/expiry, disabled audio: PASS') - finally: - manager.terminate();manager.wait(8) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-live-render.c b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-live-render.c deleted file mode 100644 index 2909fc4501be..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-live-render.c +++ /dev/null @@ -1,68 +0,0 @@ -#define main digitalrain_program_main -#include "../digitalrain.c" -#undef main -#include <assert.h> -static bool fail_allocation; -void *__real_calloc(size_t n, size_t size); -void *__wrap_calloc(size_t n, size_t size) { - if (fail_allocation) { fail_allocation = false; return NULL; } - return __real_calloc(n, size); -} -int main(void) { - EGLDisplay display = eglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA, - EGL_DEFAULT_DISPLAY, NULL); - assert(display != EGL_NO_DISPLAY && eglInitialize(display, NULL, NULL)); - const EGLint config_attrs[] = {EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_NONE}; - EGLConfig config; - EGLint count; - assert(eglChooseConfig(display, config_attrs, &config, 1, &count) && count == 1); - const EGLint context_attrs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; - EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, context_attrs); - const EGLint surface_attrs[] = {EGL_WIDTH, 128, EGL_HEIGHT, 128, EGL_NONE}; - EGLSurface surface = eglCreatePbufferSurface(display, config, surface_attrs); - assert(eglMakeCurrent(display, surface, surface, context)); - assert(render_init_shaders()); - S.surface_width = S.surface_height = 128; - render_set_projection(128, 128); - - assert(render_init_freetype(16)); - dr_config_packet p = {.magic=DR_LIVE_MAGIC, .kind=DR_CONFIG, .revision=1}; - cf_defaults(&p.config); p.config.matrix_depth_layers=2; - S.phase=PHASE_RUNNING; S.phase_frame=17; S.fade_frames=30; S.render_start_time=42; S.black_start_time=43; - assert(render_apply_config(&p, false)); - GLuint old_texture=S.atlas.texture; render_column_t *old_columns=S.layers[0].columns; - p.revision++; p.config.matrix_font_size=20; - fail_allocation=true; - assert(!render_apply_config(&p, false)); - assert(S.atlas.texture==old_texture && S.layers[0].columns==old_columns); - assert(S.applied_revision==1 && S.live_error && S.phase==PHASE_RUNNING && S.phase_frame==17); - assert(S.render_start_time==42 && S.black_start_time==43); - assert(render_apply_config(&p, false)); - assert(S.font_size==20 && S.applied_revision==2 && !S.live_error); - assert(S.phase==PHASE_RUNNING && S.render_start_time==42 && S.black_start_time==43); - dr_effect_packet effects={.magic=DR_LIVE_MAGIC,.kind=DR_EFFECTS,.count=2}; - snprintf(effects.effects[0].output,64,"top"); - snprintf(effects.effects[1].output,64,"bottom"); - effects.effects[0].width=effects.effects[1].width=128; - effects.effects[0].height=effects.effects[1].height=64; - effects.effects[1].y=64; - effects.effects[0].until=render_monotime()+60;effects.effects[0].color[0]=1; - effects.effects[1].until=render_monotime()+60;effects.effects[1].color[2]=1; - assert(dr_effect_valid(&effects)); render_apply_effects(&effects); - S.effect_mix[0]=S.effect_mix[1]=1; - float brightness; rgb_t top=render_column_color(32,32,(rgb_t){0,1,0},&brightness); - rgb_t bottom=render_column_color(32,96,(rgb_t){0,1,0},&brightness); - assert(top.r==1 && top.b==0 && bottom.r==0 && bottom.b==1); - dr_effect_packet clear={.magic=DR_LIVE_MAGIC,.kind=DR_EFFECTS};render_apply_effects(&clear); - assert(S.effects.effects[0].color[0]==1 && S.effects.effects[0].until==0); - effects.effects[0].energy=NAN; assert(!dr_effect_valid(&effects)); - render_layers_free(S.layers);glDeleteTextures(1,&S.atlas.texture); - FT_Done_Face(S.ft_face);FT_Done_FreeType(S.ft_library); - glDeleteBuffers(1,&S.vbo);glDeleteProgram(S.shader_program);free(S.vertex_buf); - eglMakeCurrent(display,EGL_NO_SURFACE,EGL_NO_SURFACE,EGL_NO_CONTEXT); - eglDestroySurface(display,surface);eglDestroyContext(display,context);eglTerminate(display); - puts("Live resource rollback, retry, phase clocks and vertical monitor effects: PASS"); - return 0; -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-manager-lock.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-manager-lock.py deleted file mode 100755 index 60fbca18b80c..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-manager-lock.py +++ /dev/null @@ -1,150 +0,0 @@ -"""Regression for manager ownership. Run with /path/to/built/digitalrain. - -Uses private runtime directories and deliberately absent Wayland/IPC sockets; -it never connects to the real desktop. Exercises the production binary. -""" -import os -from pathlib import Path -import signal -import subprocess -import sys -import tempfile -import time - - -binary = str(Path(sys.argv[1]).resolve()) -processes = [] - - -def until(check, timeout=5): - deadline = time.monotonic() + timeout - while time.monotonic() < deadline: - if check(): - return - time.sleep(.025) - raise AssertionError('Timed out waiting for expected state') - - -def setup(base): - base.mkdir(mode=0o700) - config = base / 'config' - config.write_text('[global]\nTIMEOUT=3600\nFOCUSED_TIMEOUT=3600\n' - 'CHECK_INTERVAL=1\nINHIBIT_VIDEO=false\n' - f'LOG_FILE="{base}/manager.log"\n') - env = os.environ.copy() - env.update(XDG_RUNTIME_DIR=str(base), WAYLAND_DISPLAY='absent-test-display', - WAYFIRE_SOCKET=str(base / 'absent-ipc'), - _WAYFIRE_SOCKET=str(base / 'absent-ipc'), DIGITALRAIN_CONFIG=str(config)) - return env - - -def launch(env): - log = Path(env['XDG_RUNTIME_DIR']) / f'launch-{len(processes)}.log' - with log.open('w') as stream: - process = subprocess.Popen([binary], env=env, stdout=stream, stderr=stream) - processes.append(process) - return process - - -def start(env): - process = launch(env) - pidfile = Path(env['XDG_RUNTIME_DIR']) / 'digitalrain/pid' - until(lambda: pidfile.exists() and pidfile.read_text().strip() == str(process.pid)) - logfile = Path(env['XDG_RUNTIME_DIR']) / 'manager.log' - until(lambda: 'Started' in logfile.read_text()) - assert process.poll() is None - return process - - -def stop(process): - process.terminate() - assert process.wait(timeout=5) == 0 - - -def command(env, *args): - return subprocess.run([binary, *args], env=env, capture_output=True, - text=True, timeout=12) - - -def lock_descriptors(pid): - paths = Path(f'/proc/{pid}/fd').iterdir() - result = [] - for path in paths: - try: - if os.readlink(path).endswith('/manager.lock'): - result.append(path) - except FileNotFoundError: - pass - return result - - -with tempfile.TemporaryDirectory(prefix='digitalrain-lock-test-') as temporary: - base = Path(temporary) - env = setup(base / 'first') - state = base / 'first/digitalrain' - logfile = base / 'first/manager.log' - try: - manager = start(env) - inode = (state / 'manager.lock').stat().st_ino - assert len(lock_descriptors(manager.pid)) == 1 - marker = b'KEEP EXISTING MANAGER LOG\n' - with logfile.open('r+b') as stream: - stream.write(marker) - duplicate = command(env) - assert duplicate.returncode == 0 and 'already running' in duplicate.stderr, duplicate - assert (state / 'pid').read_text().strip() == str(manager.pid) - assert logfile.read_bytes().startswith(marker), 'duplicate truncated the log' - assert manager.poll() is None - assert (state / 'manager.lock').stat().st_ino == inode - print('Duplicate exits cleanly without overwriting PID/log or disturbing owner: PASS', flush=True) - - help_result = command(env, '--render', '--help') - assert help_result.returncode == 0 and 'already running' not in help_result.stderr - assert command(env, '--activate').returncode == 0 - manager.send_signal(signal.SIGUSR1) - until(lambda: 'All outputs activated' in logfile.read_text()) - assert command(env, '--lock').returncode != 0, 'missing compositor must not report a lock' - assert (state / 'control.sock').stat().st_mode & 0o777 == 0o600 - assert command(env, '--status').returncode == 0 - print('Renderer/IPC bypass ownership check; missing compositor cannot confirm lock: PASS', flush=True) - - manager.kill() - assert manager.wait(timeout=5) == -signal.SIGKILL - assert (state / 'pid').read_text().strip() == str(manager.pid) - manager = start(env) - assert (state / 'manager.lock').stat().st_ino == inode - stop(manager) - assert not (state / 'pid').exists() - assert (state / 'manager.lock').stat().st_ino == inode - print('Crash recovery with stale PID/socket and clean shutdown: PASS', flush=True) - - contenders = [launch(env) for _ in range(8)] - until(lambda: sum(p.poll() is None for p in contenders) == 1) - winner = next(p for p in contenders if p.poll() is None) - until(lambda: (state / 'pid').exists() and - (state / 'pid').read_text().strip() == str(winner.pid)) - assert all(p.poll() == 0 for p in contenders if p is not winner) - assert command(env).returncode == 0 - assert (state / 'pid').read_text().strip() == str(winner.pid) - second = start(setup(base / 'second')) - assert winner.poll() is None - stop(second) - stop(winner) - print('Eight simultaneous starts elect one owner; separate runtime directories coexist: PASS', flush=True) - - invalid = setup(base / 'invalid') - (base / 'invalid/digitalrain/manager.lock').mkdir(parents=True) - result = command(invalid) - assert result.returncode != 0 and 'cannot open manager lock' in result.stderr - assert not (base / 'invalid/digitalrain/pid').exists() - assert not (base / 'invalid/manager.log').exists() - print('Lock errors prevent unlocked startup before touching PID/log: PASS', flush=True) - finally: - for process in reversed(processes): - if process.poll() is None: - process.terminate() - try: - process.wait(timeout=5) - except subprocess.TimeoutExpired: - process.kill() - process.wait() diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-native-lock.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-native-lock.py deleted file mode 100755 index 2c874584d611..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-native-lock.py +++ /dev/null @@ -1,225 +0,0 @@ -"""Native locker integration in a fresh headless Wayfire, never the desktop. -Run unprivileged with --auth-helper pointing to a test-service PAM worker. -Production binaries contain no test password or authentication bypass. -""" -import argparse, io, json, os, pathlib, shlex, shutil, signal, socket, struct, subprocess, tempfile, time -from PIL import Image -p=argparse.ArgumentParser();p.add_argument('--auth-helper',required=True);p.add_argument('--binary-dir',type=pathlib.Path);a=p.parse_args() -assert os.getuid()!=0 -pkg=pathlib.Path(__file__).resolve().parents[1] -binaries=a.binary_dir or pkg -runtime=pathlib.Path(tempfile.mkdtemp(prefix='digitalrain-lock-test-')) -children=[] -(runtime/'bin').mkdir() -env=os.environ.copy() -for k in ['WAYLAND_DISPLAY','WAYFIRE_SOCKET','_WAYFIRE_SOCKET','DISPLAY','WAYFIRE_CONFIG_FILE','WAYFIRE_DEFAULT_CONFIG_BACKEND','LD_LIBRARY_PATH']:env.pop(k,None) -env.update(XDG_CONFIG_HOME=str(runtime/'config'),XDG_STATE_HOME=str(runtime/'state'),XDG_RUNTIME_DIR=str(runtime),WLR_BACKENDS='headless',WLR_HEADLESS_OUTPUTS='3',WLR_RENDERER='gles2',WLR_RENDER_DRM_DEVICE='/dev/dri/renderD128',WAYFIRE_PLUGIN_PATH=f'{runtime}:{pkg}',WAYFIRE_PLUGIN_XML_PATH=f'{runtime}:{pkg}') -def spawn(argv): - with (runtime/f'child-{len(children)}.log').open('w') as f:q=subprocess.Popen(argv,env=env,stdout=f,stderr=f) - children.append(q);return q -def until(fn,timeout=8): - end=time.monotonic()+timeout - while time.monotonic()<end: - try: - r=fn() - if r:return r - except (ConnectionError,FileNotFoundError):pass - time.sleep(.05) - raise AssertionError(f'timed out: {fn}') -def read(s,n): - b=b'' - while len(b)<n: - v=s.recv(n-len(b)) - if not v:raise ConnectionError() - b+=v - return b -def ipc(method,data=None): - with socket.socket(socket.AF_UNIX) as s: - s.settimeout(4);s.connect(env['WAYFIRE_SOCKET']);b=json.dumps(dict(method=method,data=data or {})).encode();s.sendall(struct.pack('=I',len(b))+b) - return json.loads(read(s,struct.unpack('=I',read(s,4))[0])) -def locker(method='status'): - with socket.socket(socket.AF_UNIX,socket.SOCK_SEQPACKET) as s: - s.settimeout(4);s.connect(str(runtime/'digitalrain/lock.sock'));s.send(json.dumps(dict(method=method)).encode());return json.loads(s.recv(16384)) -def shot(output='VIRTUAL-1'): - return Image.open(io.BytesIO(subprocess.check_output(['grim','-o',output,'-'],env=env,timeout=4))).convert('RGB') -def type_password(text): - codes={'r':19,'a':30,'i':23,'n':49,'-':12,'t':20,'e':18,'s':31,'7':8,'x':45} - for c in text:ipc('test/key',dict(code=codes[c]));time.sleep(.02) - ipc('test/key',dict(code=28)) -try: - flags=shlex.split(subprocess.check_output(['pkg-config','--cflags','--libs','wayfire','wf-utils'],text=True)) - for name in ['lock-test','desktop-test']: - subprocess.run(['c++','-std=c++17','-fPIC','-shared','-DWAYFIRE_PLUGIN','-DWLR_USE_UNSTABLE',str(pkg/f'tests/{name}.cpp'),'-o',str(runtime/f'lib{name}.so'),*flags],check=True) - (runtime/f'{name}.xml').write_text(f'<wayfire><plugin name="{name}"><_short>Test</_short></plugin></wayfire>') - for name,src in [('digitalrain',binaries/'digitalrain'),('digitalrain-lock',binaries/'digitalrain-lock'),('digitalrain-auth',pathlib.Path(a.auth_helper))]:shutil.copy2(src,runtime/'bin'/name) - if (binaries/'digitalrain-audio').exists():shutil.copy2(binaries/'digitalrain-audio',runtime/'bin/digitalrain-audio') - conf=runtime/'wayfire.ini';conf.write_text('[core]\nplugins = ipc ipc-rules session-lock digitalrain-idle digitalrain-cursor digitalrain-power lock-test desktop-test\nxwayland = false\nstitch_enabled = true\nstitch_outputs = HEADLESS-1 HEADLESS-2 HEADLESS-3\nwindow_scope = virtual\n'+''.join(f'[output:HEADLESS-{i}]\nmode = 320x240@60\nposition = {(i-1)*320},0\n' for i in range(1,4))) - cfg=runtime/'rain.toml';cfg.write_text('[global]\nLOG_FILE = "'+str(runtime/'manager.log')+'"\nTIMEOUT = 999\nFOCUSED_TIMEOUT = 999\nCHECK_INTERVAL = 1\nINHIBIT_VIDEO = false\n[matrix]\nBLACK_RAIN_TIMEOUT = 2\nFONT_SIZE = 12\nDEPTH_LAYERS = 2\n') - env['DIGITALRAIN_CONFIG']=str(cfg) - compositor=spawn(['wayfire','-c',str(conf)]) - until(lambda:list(runtime.glob('wayfire-*.socket'))) - env['WAYFIRE_SOCKET']=str(next(runtime.glob('wayfire-*.socket'))) - env['WAYLAND_DISPLAY']=next(x.name for x in runtime.glob('wayland-*') if not x.name.endswith('.lock')) - until(lambda:len(ipc('window-rules/list-outputs'))==1 and ipc('window-rules/list-outputs')[0]['name']=='VIRTUAL-1') - ipc('digitalrain/hide_cursor') - bg=spawn(['swaybg','-c','cc0000']);until(lambda:(204,0,0) in shot().get_flattened_data()) - lock=spawn([str(runtime/'bin/digitalrain-lock')]) - until(lambda:locker().get('state')=='locked') - until(lambda:all(ipc('test/lock-state').values())) - time.sleep(.3);img=shot();img.save(runtime/'locked.png');assert (204,0,0) not in img.get_flattened_data() - duplicate=spawn([str(runtime/'bin/digitalrain-lock')]);assert duplicate.wait(4)!=0 - until(lambda:locker().get('phase')=='black');time.sleep(.2) - black=shot();black.save(runtime/'black.png');assert set(black.get_flattened_data())=={(0,0,0)} - before=pathlib.Path(f'/proc/{lock.pid}/stat').read_text().split();time.sleep(.7);after=pathlib.Path(f'/proc/{lock.pid}/stat').read_text().split() - assert sum(map(int,after[13:15]))-sum(map(int,before[13:15]))<=2 - locker('wake');lock.send_signal(signal.SIGUSR1);assert lock.poll() is None - type_password('x');time.sleep(.7);assert lock.poll() is None and locker()['state']=='locked' - print('Coverage, black idle, duplicate rejection, wake isolation and bad password: PASS',flush=True) - time.sleep(2);type_password('rain-test-7');assert lock.wait(8)==0 - until(lambda:not any(ipc('test/lock-state').values())) - print('Authenticated PAM unlock: PASS',flush=True) - def api(method, **arguments): - with socket.socket(socket.AF_UNIX,socket.SOCK_SEQPACKET) as s: - s.settimeout(10);s.connect(str(runtime/'digitalrain/control.sock'));s.send(json.dumps(dict(version=1,method=method,**arguments)).encode());return json.loads(s.recv(16384)) - manager=spawn([str(runtime/'bin/digitalrain')]);until(lambda:(runtime/'digitalrain/control.sock').exists()) - assert len(api('status')['outputs'])==3 - assert api('schema')['matrix.FONT_SIZE']['minimum']==6 - assert 'error' in api('settings',settings={'matrix.FONT_SIZE':0}) - assert 'error' in api('settings',settings={'global.LOCK_CMD':'bad'}) - original=cfg.read_text();api('settings',settings={'matrix.FONT_SIZE':16});assert cfg.read_text()==original - api('settings',settings={'matrix.FONT_SIZE':14},save=True);assert 'FONT_SIZE = 14' in cfg.read_text() - inhibited=api('inhibit',output='HEADLESS-1',seconds=2,reason='test') - assert api('status')['inhibitors'][0]['id']==inhibited['id'] - api('uninhibit',id=inhibited['id']);assert not api('status')['inhibitors'] - api('black',output='HEADLESS-1');until(lambda:api('status')['outputs'][0]['phase']=='black') - api('wake',output='HEADLESS-1');until(lambda:all(not o['saver'] for o in api('status')['outputs'])) - before=cfg.read_text();api('preview',output='HEADLESS-2',seconds=1,settings={'matrix.COLOR':'blue'}) - until(lambda:all(not o['saver'] for o in api('status')['outputs']));assert cfg.read_text()==before - assert 'error' in api('unlock') - # Runtime changes retain renderer processes and per-output phase clocks. - api('settings',settings={'matrix.COLOR':'green','matrix.FADE_FRAMES':1,'matrix.BLACK_RAIN_TIMEOUT':0,'matrix.HEAD_WHITE_MIN':0,'matrix.HEAD_WHITE_MAX':0}) - api('activate',output='HEADLESS-1');api('activate',output='HEADLESS-3') - until(lambda:all(o['applied_revision']==api('status')['settings_revision'] for o in [o for o in api('status')['outputs'] if o['name']!='HEADLESS-2'])) - renderer_pids=pathlib.Path(f'/proc/{manager.pid}/task/{manager.pid}/children').read_text().split() - def dominant(region, channel): - image=shot().crop((region*320,0,(region+1)*320,240)) - sums=[sum(pixel[j] for pixel in image.get_flattened_data()) for j in range(3)] - return sums[channel]>max(sums[(channel+1)%3],sums[(channel+2)%3])*1.3 and sums[channel]>500 - until(lambda:dominant(0,1) and dominant(2,1)) - api('alert',source='build',id='failed',severity='error',seconds=20,output='HEADLESS-1') - until(lambda:dominant(0,0));assert dominant(2,1) - api('settings',settings={'matrix.COLOR':'blue','matrix.FONT_SIZE':20,'matrix.DEPTH_LAYERS':3}) - until(lambda:all(o['font_size']==20 and o['depth_layers']==3 for o in [o for o in api('status')['outputs'] if o['name']!='HEADLESS-2'])) - assert pathlib.Path(f'/proc/{manager.pid}/task/{manager.pid}/children').read_text().split()==renderer_pids - until(lambda:dominant(0,0) and dominant(2,2)) - api('alert',source='backup',id='warning',severity='warning',seconds=20,output='HEADLESS-1') - assert api('status')['alerts_by_output']['HEADLESS-1']['severity']=='error' - api('clear_alert',source='backup',id='warning');api('clear_alert',source='build',id='failed') - until(lambda:dominant(0,2)) - api('black',output='HEADLESS-1');until(lambda:api('status')['outputs'][0]['phase']=='black') - api('alert',source='build',id='black',severity='error',seconds=1,output='HEADLESS-1') - time.sleep(.3);assert set(shot().crop((0,0,320,240)).get_flattened_data())=={(0,0,0)} - api('wake');until(lambda:all(not o['saver'] for o in api('status')['outputs'])) - api('reload');until(lambda:api('status')['alert_count']==0) - print('Live colors/resources retain renderer PIDs; per-monitor alerts, priority, restoration and black policy: PASS',flush=True) - # A real private PipeWire helper starts/stops according to visible rain. - if (runtime/'bin/digitalrain-audio').exists(): - audio_server=spawn(['pipewire']);until(lambda:(runtime/'pipewire-0').exists()) - audio_policy=spawn(['dbus-run-session','--','wireplumber','--profile','policy']) - time.sleep(.8) - subprocess.run(['pw-cli','create-node','adapter','{ factory.name = support.null-audio-sink node.name = rain-test-source media.class = Audio/Sink object.linger = true audio.position = [ FL FR ] }'],env=env,check=True,stdout=subprocess.DEVNULL) - api('settings',settings={'audio.SOURCE':'rain-test-source','audio.ENABLED':1,'matrix.BLACK_RAIN_TIMEOUT':0}) - assert api('status')['audio']['state']=='idle' - api('activate',output='HEADLESS-1');until(lambda:api('status')['audio']['state']=='monitoring') - def audio_pid(): - for pid in pathlib.Path(f'/proc/{manager.pid}/task/{manager.pid}/children').read_text().split(): - try: - if pathlib.Path(f'/proc/{pid}/comm').read_text().strip()=='digitalrain-aud':return int(pid) - if b'digitalrain-audio' in pathlib.Path(f'/proc/{pid}/cmdline').read_bytes():return int(pid) - except FileNotFoundError:pass - return None - assert audio_pid() - api('black',output='HEADLESS-1');until(lambda:api('status')['outputs'][0]['phase']=='black') - until(lambda:api('status')['audio']['state']=='idle' and not audio_pid()) - api('wake');api('activate',output='HEADLESS-1');until(lambda:api('status')['audio']['state']=='monitoring') - old_audio=audio_pid();os.kill(old_audio,signal.SIGKILL) - until(lambda:audio_pid() not in (None,old_audio) and api('status')['audio']['state']=='monitoring',timeout=10) - # Server loss must back off instead of spawning a helper each tick. - audio_server.terminate();audio_server.wait(5) - until(lambda:api('status')['audio']['state']!='monitoring') - seen=set();end=time.monotonic()+4 - while time.monotonic()<end: - pid=audio_pid() - if pid:seen.add(pid) - time.sleep(.02) - assert len(seen)<=4,seen - audio_server=spawn(['pipewire']);until(lambda:(runtime/'pipewire-0').exists()) - # Reconnect policy and recreate only the private virtual source. - audio_policy.terminate();audio_policy.wait(5) - audio_policy=spawn(['dbus-run-session','--','wireplumber','--profile','policy']);time.sleep(.8) - subprocess.run(['pw-cli','create-node','adapter','{ factory.name = support.null-audio-sink node.name = rain-test-source media.class = Audio/Sink object.linger = true audio.position = [ FL FR ] }'],env=env,check=True,stdout=subprocess.DEVNULL) - until(lambda:api('status')['audio']['state']=='monitoring',timeout=35) - api('settings',settings={'audio.ENABLED':0});until(lambda:api('status')['audio']['state']=='disabled' and not audio_pid()) - api('wake');api('reload');audio_policy.terminate();audio_policy.wait(5);audio_server.terminate();audio_server.wait(5) - print('Real audio helper visibility lifecycle, black shutdown, crash recovery and disable: PASS',flush=True) - # Event clients receive state changes, not per-frame or countdown traffic. - with socket.socket(socket.AF_UNIX,socket.SOCK_SEQPACKET) as watch: - watch.settimeout(3);watch.connect(str(runtime/'digitalrain/control.sock')) - watch.send(json.dumps(dict(version=1,method='watch')).encode());watch.recv(16384) - time.sleep(.4);watch.settimeout(.1) - try: - while watch.recv(16384):pass - except TimeoutError:pass - watch.settimeout(.4) - try:watch.recv(16384);raise AssertionError('unchanged state produced an event') - except TimeoutError:pass - api('inhibit',seconds=1,reason='event test');watch.settimeout(3);assert json.loads(watch.recv(16384))['inhibitors'] - with socket.socket(socket.AF_UNIX,socket.SOCK_SEQPACKET) as bad: - bad.settimeout(2);bad.connect(str(runtime/'digitalrain/control.sock'));bad.send(b'{'*17000);assert bad.recv(16384)==b'' - assert api('status')['version']==1 - messages=[dict(jsonrpc='2.0',id=1,method='initialize',params=dict(protocolVersion='2025-11-25')), - dict(jsonrpc='2.0',method='notifications/initialized'),dict(jsonrpc='2.0',id=2,method='tools/list'), - dict(jsonrpc='2.0',id=3,method='tools/call',params=dict(name='status')), - dict(jsonrpc='2.0',id=4,method='resources/read',params=dict(uri='digitalrain://schema'))] - mcp=subprocess.run(['python3',str(pkg / 'digitalrain-mcp.py')],input=''.join(json.dumps(m)+'\n' for m in messages),text=True,capture_output=True,env=env,check=True) - replies=[json.loads(x) for x in mcp.stdout.splitlines()];assert len(replies)==4 and all('result' in x for x in replies),replies - assert not replies[2]['result']['isError'] - print('IPC settings/preview/inhibition, state events, packet limits and MCP: PASS',flush=True) - command=spawn([str(runtime/'bin/digitalrain'),'--lock']);assert command.wait(10)==0 - until(lambda:api('lock_status')['state']=='locked') - lock_pid=locker()['pid'] - api('settings',settings={'matrix.BLACK_RAIN_TIMEOUT':0,'matrix.COLOR':'green','matrix.FONT_SIZE':16,'matrix.HEAD_WHITE_MIN':0,'matrix.HEAD_WHITE_MAX':0,'alerts.SHOW_WHILE_LOCKED':1}) - api('alert',source='lock-test',id='regional',severity='error',seconds=20,output='HEADLESS-1') - until(lambda:dominant(0,0) and dominant(1,1)) - assert locker()['pid']==lock_pid and all(ipc('test/lock-state').values()) - api('clear_alert',source='lock-test',id='regional');until(lambda:dominant(0,1)) - print('Locked appearance reload and physical-region alerts preserve authentication and PID: PASS',flush=True) - manager.terminate();assert manager.wait(6)==0;assert locker()['state']=='locked' - manager=spawn([str(runtime/'bin/digitalrain')]);until(lambda:(runtime/'digitalrain/control.sock').exists()) - until(lambda:api('lock_status')['state']=='locked');api('wake');assert locker()['state']=='locked' - old_pid=locker()['pid'];os.kill(old_pid,signal.SIGKILL) - until(lambda:locker()['pid']!=old_pid and locker()['state']=='locked') - assert all(ipc('test/lock-state').values()) - type_password('rain-test-7');until(lambda:api('lock_status')['state']=='unlocked') - manager.terminate();manager.wait(6) - print('CLI lock confirmation and manager restart/wake preserve session lock: PASS',flush=True) - lock=spawn([str(runtime/'bin/digitalrain-lock')]);until(lambda:locker()['state']=='locked') - lock.kill();lock.wait(4);assert all(ipc('test/lock-state').values()) - recovered=spawn([str(runtime/'bin/digitalrain-lock')]);until(lambda:locker()['state']=='locked') - ipc('test/add-output');time.sleep(.6);assert all(ipc('test/lock-state').values()) - ipc('test/remove-output',dict(output='HEADLESS-2'));time.sleep(.6);assert recovered.poll() is None - type_password('rain-test-7');assert recovered.wait(8)==0 - print('Crash remains locked, replacement recovery and hot-plug: PASS',flush=True) - cfg.write_text(cfg.read_text().replace('BLACK_RAIN_TIMEOUT = 2', 'BLACK_RAIN_TIMEOUT = 1')+'\nPOWER_OFF = true\n') - powered=spawn([str(runtime/'bin/digitalrain-lock')]);until(lambda:locker()['state']=='locked') - until(lambda:all(not enabled for enabled in ipc('test/output-power').values())) - locker('wake');until(lambda:all(ipc('test/output-power').values()));assert all(ipc('test/lock-state').values()) - type_password('rain-test-7');assert powered.wait(8)==0 - print('Opt-in locked monitor power-off and wake retain protection: PASS',flush=True) -finally: - for q in reversed(children): - if q.poll() is None: - q.terminate() - try:q.wait(4) - except subprocess.TimeoutExpired:q.kill();q.wait() - print('Artifacts:',runtime,flush=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-stitched-outputs.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-stitched-outputs.py deleted file mode 100755 index ff27cad9f197..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-stitched-outputs.py +++ /dev/null @@ -1,356 +0,0 @@ -"""Physical monitor regressions in an isolated stitched Wayfire session. - -Run as a regular user. Requires built files/, Wayfire, grim, swaybg, mpv, -pkg-config, C++, and Pillow. No real desktop sockets or settings are used. -""" -import argparse -import io -import json -import os -from pathlib import Path -import shlex -import shutil -import signal -import socket -import struct -import subprocess -import tempfile -import time - -from PIL import Image - -parser = argparse.ArgumentParser() -parser.add_argument('--upgrade-from', type=Path, help='Directory of old helper libraries for live upgrade validation') -parser.add_argument('--binary', type=Path, help='Test an installed renderer/manager binary') -args = parser.parse_args() -assert os.geteuid() != 0, 'Run as a regular user' -package = Path(__file__).resolve().parents[1] -binary = str(args.binary or package / 'digitalrain') -runtime = Path(tempfile.mkdtemp(prefix='digitalrain-stitched-test-')) -children = [] -connections = [] -env = os.environ.copy() -for key in ['WAYLAND_DISPLAY', 'WAYFIRE_SOCKET', '_WAYFIRE_SOCKET', 'DISPLAY', - 'WAYFIRE_DEFAULT_CONFIG_BACKEND', 'WAYFIRE_CONFIG_FILE', 'LD_LIBRARY_PATH']: - env.pop(key, None) -env.update(XDG_RUNTIME_DIR=str(runtime), WLR_BACKENDS='headless', - WLR_HEADLESS_OUTPUTS='3', WLR_RENDERER='gles2', - WLR_RENDER_DRM_DEVICE=env.get('WLR_RENDER_DRM_DEVICE', '/dev/dri/renderD128'), - WAYFIRE_PLUGIN_PATH=f'{runtime}:{package}', - WAYFIRE_PLUGIN_XML_PATH=f'{runtime}:{package}') - - -def spawn(command): - with (runtime / f'child-{len(children)}.log').open('w') as log: - process = subprocess.Popen(command, env=env, stdout=log, stderr=log) - children.append(process) - return process - - -def until(check, timeout=6): - deadline = time.monotonic() + timeout - while time.monotonic() < deadline: - if check(): - return - time.sleep(.05) - raise AssertionError('Timed out waiting for expected state') - - -def read(sock, count): - result = b'' - while len(result) < count: - part = sock.recv(count - len(result)) - if not part: - raise RuntimeError('IPC disconnected') - result += part - return result - - -def request(sock, method, data=None): - payload = json.dumps(dict(method=method, data=data or {})).encode() - sock.sendall(struct.pack('=I', len(payload)) + payload) - return json.loads(read(sock, struct.unpack('=I', read(sock, 4))[0])) - - -def connect(): - sock = socket.socket(socket.AF_UNIX) - sock.settimeout(3) - sock.connect(env['WAYFIRE_SOCKET']) - connections.append(sock) - return sock - - -def ipc(method, data=None): - with connect() as sock: - return request(sock, method, data) - - -def hold(output, fade_ms=0): - sock = connect() - assert request(sock, 'digitalrain/hide_desktop', dict(output=output, fade_ms=fade_ms)) == {'result': 'ok'} - return sock - - -def shot(output='VIRTUAL-1'): - png = subprocess.check_output(['grim', '-o', output, '-'], env=env) - return Image.open(io.BytesIO(png)).convert('RGB') - - -def pixels(color, output='VIRTUAL-1'): - return sum(p == color for p in shot(output).get_flattened_data()) - - -def solid(key, layer, output='VIRTUAL-1', close=False, x=0, y=0, w=100, h=80, - r=0.0, g=0.0, b=1.0): - data = dict(key=key, output=output, layer=layer, x=x, y=y, w=w, h=h, r=r, g=g, b=b) - if close: - data['close'] = True - assert ipc('test/solid', data) == {'result': 'ok'} - - -def rain(output='VIRTUAL-1', hide=True, transparent=True, timeout=0, black_timeout=0, fade_frames=6, power=False): - cmd = [binary, '--render', '--output', output, - '--color', 'green', '--fade-frames', str(fade_frames), '--transparent-timeout', str(timeout), - '--black-rain-timeout', str(black_timeout), '--power-off', str(int(power))] - if transparent: - cmd += ['--transparent'] - if hide: - cmd += ['--hide-desktop'] - return spawn(cmd) - - -def dismiss(process): - process.send_signal(signal.SIGUSR1) - assert process.wait(timeout=4) == 0 - - -try: - flags = shlex.split(subprocess.check_output( - ['pkg-config', '--cflags', '--libs', 'wayfire', 'wf-utils'], text=True)) - subprocess.run(['c++', '-std=c++17', '-fPIC', '-shared', '-DWAYFIRE_PLUGIN', - '-DWLR_USE_UNSTABLE', str(package / 'tests/desktop-test.cpp'), - '-o', str(runtime / 'libdesktop-test.so'), *flags], check=True) - (runtime / 'desktop-test.xml').write_text( - '<wayfire><plugin name="desktop-test"><_short>Test</_short></plugin></wayfire>') - if args.upgrade_from: - for helper in ('desktop', 'idle', 'sampler'): - shutil.copyfile(args.upgrade_from / f'libdigitalrain-{helper}.so', runtime / f'libdigitalrain-{helper}.so') - config = runtime / 'wayfire.ini' - def configure(stitched=True, power=True): - power_plugin = ' digitalrain-power' if power else '' - config.write_text(f'[core]\nplugins = ipc ipc-rules digitalrain-desktop digitalrain-idle digitalrain-sampler digitalrain-cursor desktop-test{power_plugin}\n' - 'xwayland = false\nvwidth = 1\nvheight = 1\nwindow_scope = virtual\n' - f'stitch_enabled = {str(stitched).lower()}\nstitch_outputs = HEADLESS-1 HEADLESS-2 HEADLESS-3\n' - '[workarounds]\nenable_so_unloading = false\n' + ''.join( - f'[output:HEADLESS-{i}]\nmode = 256x192@60\nposition = {(i-1)*256},0\n' for i in range(1,4))) - configure() - compositor = spawn(['wayfire', '-c', str(config)]) - until(lambda: list(runtime.glob('wayfire-*.socket'))) - env['WAYFIRE_SOCKET'] = str(next(runtime.glob('wayfire-*.socket'))) - env['WAYLAND_DISPLAY'] = next(p.name for p in runtime.glob('wayland-*') if not p.name.endswith('.lock')) - until(lambda: [o['name'] for o in ipc('window-rules/list-outputs')] == ['VIRTUAL-1']) - if args.upgrade_from: - assert set(ipc('digitalrain/sample_desktop')['outputs']) == {'VIRTUAL-1'} - for helper in ('desktop', 'idle', 'sampler'): - (runtime / f'libdigitalrain-{helper}.so').unlink() - ipc('wayfire/reload-plugins') - upgraded = ipc('digitalrain/sample_desktop') - assert set(upgraded['outputs']) == {'HEADLESS-1', 'HEADLESS-2', 'HEADLESS-3'}, upgraded - print('Resident old helpers upgraded without a compositor restart: PASS', flush=True) - assert ipc('digitalrain/hide_cursor') == {'result': 'ok'} # includes software cursor - background = spawn(['swaybg', '-c', 'cc0000']) - red, blue = (204, 0, 0), (0, 0, 255) - try: - until(lambda: pixels(red) == 768 * 192) - except AssertionError: - frame = shot(); frame.save(runtime/'background.png') - from collections import Counter - print('Background:', frame.size, Counter(frame.get_flattened_data()).most_common(5), flush=True) - raise - solid('span', 2, x=200, y=50, w=400, h=80) - until(lambda: pixels(blue) == 32000) - baseline = shot() - a = hold('HEADLESS-2') - until(lambda: pixels(blue) == (400-256)*80) - hidden = shot() - assert hidden.crop((0,0,256,192)).tobytes() == baseline.crop((0,0,256,192)).tobytes() - assert hidden.crop((512,0,768,192)).tobytes() == baseline.crop((512,0,768,192)).tobytes() - assert set(hidden.crop((256,0,512,192)).get_flattened_data()) == {red} - solid('new', 3, x=300, y=140, w=60, h=30) - time.sleep(.15) - assert set(shot().crop((256,0,512,192)).get_flattened_data()) == {red} - solid('new', 3, close=True) - b = hold('HEADLESS-1') - until(lambda: pixels(blue) == 88*80) - a.close() - until(lambda: pixels(blue) == (400-56)*80) - b.close() - until(lambda: shot().tobytes() == baseline.tobytes()) - print('Seam-spanning windows, new views, independent hide owners and disconnect recovery: PASS', flush=True) - a = hold('HEADLESS-2', 600) - time.sleep(.25) - intermediate = shot() - assert intermediate.getpixel((300,70)) not in (red,blue) - assert intermediate.getpixel((210,70)) == blue - assert intermediate.getpixel((550,70)) == blue - until(lambda: shot().getpixel((300,70)) == red) - request(a, 'digitalrain/show_desktop') - time.sleep(.25) - assert shot().getpixel((300,70)) not in (red,blue) - until(lambda: shot().tobytes() == baseline.tobytes()) - a.close() - print('Physical-region fade out and wake fade preserve neighboring pixels: PASS', flush=True) - r = rain('HEADLESS-2', timeout=1, black_timeout=1) - until(lambda: set(shot().crop((256,0,512,192)).get_flattened_data()) == {(0,0,0)}, timeout=6) - image = shot() - assert ipc('test/output-power')['HEADLESS-2'], 'default black must keep the monitor powered' - assert image.crop((0,0,256,192)).tobytes() == baseline.crop((0,0,256,192)).tobytes() - assert image.crop((512,0,768,192)).tobytes() == baseline.crop((512,0,768,192)).tobytes() - dismiss(r) - until(lambda: shot().tobytes() == baseline.tobytes()) - samples = ipc('digitalrain/sample_desktop')['outputs'] - assert set(samples) == {'HEADLESS-1','HEADLESS-2','HEADLESS-3'}, samples - assert all('luminance' in s for s in samples.values()), samples - assert samples['HEADLESS-1']['luminance'] != samples['HEADLESS-2']['luminance'] - print('Region-sized renderer, black timeout, wake and per-monitor sampling: PASS', flush=True) - - topology = ipc('window-rules/list-outputs') - r = rain('HEADLESS-2', hide=False, timeout=1, black_timeout=1, power=True) - until(lambda: not ipc('test/output-power')['HEADLESS-2']) - state = ipc('test/output-power') - assert state['HEADLESS-1'] and state['HEADLESS-3'] and state['VIRTUAL-1'], state - assert ipc('window-rules/list-outputs') == topology - dismiss(r) - until(lambda: ipc('test/output-power')['HEADLESS-2']) - until(lambda: shot().tobytes() == baseline.tobytes()) - r = rain('HEADLESS-2', hide=False, timeout=1, black_timeout=1, power=True) - until(lambda: not ipc('test/output-power')['HEADLESS-2']) - r.kill(); r.wait(timeout=3) - until(lambda: ipc('test/output-power')['HEADLESS-2']) - print('Opt-in physical monitor power-off, unchanged topology, wake and crash recovery: PASS', flush=True) - - a, b = connect(), connect() - for client in (a, b): - assert request(client, 'digitalrain/power_off', dict(output='HEADLESS-2')) == {'result':'ok'} - a.close() - time.sleep(.1) - assert not ipc('test/output-power')['HEADLESS-2'] - b.close() - until(lambda: ipc('test/output-power')['HEADLESS-2']) - assert 'error' in ipc('digitalrain/power_off', dict(output='VIRTUAL-1')) - assert ipc('window-rules/set-output-power', dict(output_name='HEADLESS-2', enabled=False)) == {'result':'ok'} - assert 'error' in ipc('digitalrain/power_off', dict(output='HEADLESS-2')) - assert not ipc('test/output-power')['HEADLESS-2'] - ipc('window-rules/set-output-power', dict(output_name='HEADLESS-2', enabled=True)) - a = connect() - assert request(a, 'digitalrain/power_off', dict(output='HEADLESS-2')) == {'result':'ok'} - configure(power=False) - until(lambda: ipc('test/output-power')['HEADLESS-2']) - a.close() - r = rain('HEADLESS-2', hide=False, timeout=1, black_timeout=1, power=True) - until(lambda: set(shot().crop((256,0,512,192)).get_flattened_data()) == {(0,0,0)}) - time.sleep(.3) - assert ipc('test/output-power')['HEADLESS-2'] and r.poll() is None - dismiss(r) - configure() - until(lambda: 'digitalrain/power_off' in ipc('list-methods')['methods']) - print('Power ownership, external-off preservation, plugin unload and missing-helper fallback: PASS', flush=True) - - video = spawn(['mpv', '--no-config', '--vo=gpu', '--gpu-context=wayland', - '--no-audio', '--stop-screensaver=yes', '--title=digitalrain-inhibitor-test', - 'av://lavfi:color=c=white:s=64x64:r=5']) - def video_view(): - return next((v for v in ipc('window-rules/list-views') if v.get('title') == 'digitalrain-inhibitor-test'), None) - until(video_view) - vid = video_view()['id'] - def place_video(x, width): - result = ipc('window-rules/configure-view', dict(id=vid, output_name='VIRTUAL-1', - geometry=dict(x=x,y=20,width=width,height=100))) - assert result == {'result':'ok'}, result - place_video(280, 180) - until(lambda: ipc('digitalrain/idle_inhibitors')['outputs'] == ['HEADLESS-2']) - place_video(200, 400) - until(lambda: set(ipc('digitalrain/idle_inhibitors')['outputs']) == {'HEADLESS-1','HEADLESS-2','HEADLESS-3'}) - video.terminate(); video.wait(timeout=5) - until(lambda: ipc('digitalrain/idle_inhibitors')['outputs'] == []) - print('Real video inhibitor follows physical placement, spans members, and releases on exit: PASS', flush=True) - - saver_config = runtime / 'digitalrain.toml' - saver_config.write_text('[global]\nTIMEOUT=2\nFOCUSED_TIMEOUT=4\nCHECK_INTERVAL=1\n' - f'INHIBIT_VIDEO=false\nLOG_FILE="{runtime}/manager.log"\n' - '[matrix]\nCOLOR="green"\nTRANSPARENT=true\nHIDE_DESKTOP=true\nFADE_FRAMES=2\n') - env['DIGITALRAIN_CONFIG'] = str(saver_config) - ipc('test/activity', dict(x=600.0,y=120.0)) - manager = spawn([binary]) - def running(): - pids = Path(f'/proc/{manager.pid}/task/{manager.pid}/children').read_text().split() - result = set() - for pid in pids: - try: args = Path(f'/proc/{pid}/cmdline').read_bytes().split(b'\0') - except FileNotFoundError: continue - if b'--output' in args: result.add(args[args.index(b'--output')+1].decode()) - return result - def active_for(x, seconds): - deadline = time.monotonic()+seconds - while time.monotonic()<deadline: - ipc('test/activity', dict(x=float(x),y=120.0)) - time.sleep(.15) - active_for(600, 5) - assert running() == {'HEADLESS-1','HEADLESS-2'}, running() - active_for(100, 4) - assert running() == {'HEADLESS-2','HEADLESS-3'}, running() - print('Real manager timers: active member stays awake; crossing wakes only that member: PASS', flush=True) - configure(False) - until(lambda: len(ipc('window-rules/list-outputs')) == 3) - active_for(100, 2) - configure(True) - until(lambda: [o['name'] for o in ipc('window-rules/list-outputs')] == ['VIRTUAL-1']) - active_for(100, 3) - assert manager.poll() is None - log = (runtime/'manager.log').read_text() - assert 'renderer exited immediately' not in log, log - assert 'Removed or reconfigured output: HEADLESS-' in log - manager.terminate() - assert manager.wait(timeout=6) == 0 - print('Group/ungroup while running removes stale topology and preserves manager: PASS', flush=True) - - saver_config.write_text('[global]\nTIMEOUT=1\nFOCUSED_TIMEOUT=1\nCHECK_INTERVAL=1\n' - f'INHIBIT_VIDEO=false\nLOG_FILE="{runtime}/power-manager.log"\n' - '[matrix]\nCOLOR="green"\nTRANSPARENT=true\nFADE_FRAMES=2\n' - 'TRANSPARENT_TIMEOUT=1\nBLACK_RAIN_TIMEOUT=1\nPOWER_OFF=true\n' - '[output_power_off]\nHEADLESS-3=false\n') - manager = spawn([binary]) - until(lambda: not ipc('test/output-power')['HEADLESS-1'] and - not ipc('test/output-power')['HEADLESS-2'], timeout=12) - assert ipc('test/output-power')['HEADLESS-3'] - assert running() == {'HEADLESS-1','HEADLESS-2','HEADLESS-3'} - manager.kill(); manager.wait(timeout=3) - until(lambda: all(ipc('test/output-power').values())) - print('Manager passes power policy and per-output overrides; manager death restores monitors: PASS', flush=True) - - configure(False) - until(lambda: len(ipc('window-rules/list-outputs')) == 3) - standalone_topology = ipc('window-rules/list-outputs') - r = rain('HEADLESS-2', hide=False, transparent=False, black_timeout=1, power=True) - until(lambda: not ipc('test/output-power')['HEADLESS-2']) - assert ipc('window-rules/list-outputs') == standalone_topology - assert ipc('test/output-power')['HEADLESS-1'] and ipc('test/output-power')['HEADLESS-3'] - dismiss(r) - until(lambda: ipc('test/output-power')['HEADLESS-2']) - a = connect() - assert request(a, 'digitalrain/power_off', dict(output='HEADLESS-2')) == {'result':'ok'} - assert ipc('test/remove-output', dict(output='HEADLESS-2')) == {'result':'ok'} - a.close() - until(lambda: 'HEADLESS-2' not in ipc('test/output-power')) - assert compositor.poll() is None - print('Standalone monitor power/wake and unplugging a sleeping monitor: PASS', flush=True) -finally: - for connection in connections: - connection.close() - for child in reversed(children): - if child.poll() is None: - child.terminate() - try: child.wait(timeout=5) - except subprocess.TimeoutExpired: - child.kill(); child.wait() - print(f'Test artifacts: {runtime}', flush=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-topology.c b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-topology.c deleted file mode 100644 index 9466b6a7d251..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tests/test-topology.c +++ /dev/null @@ -1,47 +0,0 @@ -#define main digitalrain_program_main -#include "../digitalrain.c" -#undef main -#include <assert.h> - -int main(void) { - const char *topology = "[{\"name\":\"VIRTUAL-1\",\"geometry\":{\"x\":100,\"y\":20,\"width\":512,\"height\":192}," - "\"members\":[{\"name\":\"DP-1\",\"geometry\":{\"x\":0,\"y\":0,\"width\":256,\"height\":192}}," - "{\"name\":\"DP-2\",\"geometry\":{\"x\":256,\"y\":0,\"width\":256,\"height\":192}}]}," - "{\"name\":\"HDMI-A-1\",\"geometry\":{\"x\":612,\"y\":20,\"width\":192,\"height\":108}}]"; - saver_target_t targets[MAX_OUTPUTS]; - struct json_object *outputs = json_tokener_parse(topology); - int n = parse_targets(outputs, targets); - assert(n == 3); - assert(targets[1].region && targets[1].x == 256 && targets[1].layout_x == 100); - assert(!strcmp(targets[1].desktop, "VIRTUAL-1")); - assert(!targets[2].region && targets[2].x == 0 && targets[2].layout_x == 612); - pthread_mutex_init(&G.lock, NULL); - mgr_reconcile_targets(targets, n); - G.outputs[0].last_input = 42; - G.outputs[0].timeout_override = 17; - mgr_reconcile_targets(targets, n); - assert(G.outputs[0].last_input == 42 && G.outputs[0].timeout_override == 17); - pid_t child = fork(); - assert(child >= 0); - if (!child) { for (;;) pause(); } - G.outputs[1].pids[0] = child; - G.outputs[1].pid_count = 1; - strcpy(G.outputs[1].state, "off"); - mgr_reconcile_targets(targets + 2, 1); - int status; - assert(waitpid(child, &status, 0) == child && WIFSIGNALED(status)); - assert(G.output_count == 1 && !strcmp(G.outputs[0].name, "HDMI-A-1")); - assert(!mgr_find_output("DP-2")); - json_object_put(outputs); - outputs = json_tokener_parse("[]"); - assert(parse_targets(outputs, targets) == 0); - json_object_put(outputs); - assert(parse_targets(NULL, targets) == -1); - outputs = json_tokener_parse("[{\"name\":\"bad\"}]"); - assert(parse_targets(outputs, targets) == -1); - json_object_put(outputs); - mgr_reconcile_targets(targets, 0); - assert(G.output_count == 0); - pthread_mutex_destroy(&G.lock); - puts("Topology parsing, stable idle state, disappearing outputs and renderer cleanup: PASS"); -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tomlc17.LICENSE b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tomlc17.LICENSE deleted file mode 100644 index 173779e2145a..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tomlc17.LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024-2026, CK Tan - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tomlc17.UPSTREAM b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tomlc17.UPSTREAM deleted file mode 100644 index 7c4dc6cca1f7..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tomlc17.UPSTREAM +++ /dev/null @@ -1,3 +0,0 @@ -https://github.com/cktan/tomlc17 -Commit: 8d3766ddafa6515417f4885ae6878ef69fac401e -Unmodified source/header; MIT license in tomlc17.LICENSE. Supports TOML 1.0 and 1.1. diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tomlc17.c b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tomlc17.c deleted file mode 100644 index 6afa46945591..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tomlc17.c +++ /dev/null @@ -1,3291 +0,0 @@ -/* Copyright (c) 2024-2026, CK Tan. - * https://github.com/cktan/tomlc17/blob/main/LICENSE - */ -#include "tomlc17.h" -#include <assert.h> -#include <ctype.h> -#include <errno.h> -#include <limits.h> -#include <math.h> -#include <stdarg.h> -#include <stdbool.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -const toml_datum_t DATUM_ZERO = {0}; - -static toml_option_t toml_option = {0, realloc, free}; - -#define MALLOC(n) toml_option.mem_realloc(0, n) -#define REALLOC(p, n) toml_option.mem_realloc(p, n) -#define FREE(p) toml_option.mem_free(p) - -#define DO(x) \ - if (x) \ - return -1; \ - else \ - (void)0 - -// Copy string src to dst where dst is limited to dstsz that includes -// NUL. Return 0 on success, -1 otherwise (because src[] is longer than dst[]). -static inline int copystring(char *dst, int dstsz, const char *src) { - int srcsz = strlen(src) + 1; - if (srcsz > dstsz) { - return -1; - } - memcpy(dst, src, srcsz); - return 0; -} - -/* - * Error buffer - */ -typedef struct ebuf_t ebuf_t; -struct ebuf_t { - char *ptr; - int len; -}; - -/* - * Format an error into ebuf[]. Always return -1. - */ -static int SETERROR(ebuf_t ebuf, int lineno, const char *fmt, ...) { - va_list args; - va_start(args, fmt); - char *p = ebuf.ptr; - char *q = p + ebuf.len; - if (lineno) { - snprintf(p, p < q ? q - p : 0, "(line %d) ", lineno); - p += strlen(p); - } - vsnprintf(p, p < q ? q - p : 0, fmt, args); - va_end(args); - return -1; -} - -/* - * Memory pool. A pool is a pair of page_t lists: small allocations - * (<= PAGE_LARGE_THRESHOLD bytes) are bump-allocated out of - * PAGE_SMALL_SIZE pages, handed out piecemeal until a page fills up - * and a fresh one is linked in front. Large allocations get their - * own exactly-sized page, used once and never shared. This avoids - * ever mallocing one giant block sized for the whole document. - */ -#define PAGE_SMALL_SIZE 4096 -#define PAGE_LARGE_THRESHOLD 1024 -// Small allocations must always fit in a fresh small page. -static_assert(PAGE_SMALL_SIZE > PAGE_LARGE_THRESHOLD, - "page/threshold invariant"); - -#define PAGE_MAGIC 0x50414745u // "PAGE" - -typedef struct page_t page_t; -struct page_t { - uint32_t magic; // PAGE_MAGIC, checked to catch corruption/use-after-free - int top; // offset of first free byte in data[] - int max; // size of data[] - page_t *next; // link to next page in list - char data[1]; // first byte starts here -}; - -#define POOL_MAGIC 0x504f4f4cu // "POOL" - -typedef struct pool_t pool_t; -struct pool_t { - uint32_t magic; // POOL_MAGIC, checked to catch corruption/use-after-free - page_t *small; // most recent small page, bump-allocated piecemeal - page_t *large; // most recent large page, each used for one alloc only -}; - -/** - * Create a page of `size` bytes. Return the page on success, or - * NULL if out of memory. - */ -static page_t *page_create(int size) { - if (!(0 <= size && size <= (1 << 30))) { // [0..1GB] - return NULL; - } - size_t totalsz = (size_t)&((page_t *)0)->data[size]; - page_t *page = MALLOC(totalsz); - if (!page) { - return NULL; - } - page->magic = PAGE_MAGIC; - page->top = 0; - page->max = size; - page->next = NULL; - return page; -} - -/** - * Create an empty memory pool. Return the memory pool on success, - * or NULL if out of memory. pool->small is always non-NULL: a - * pool holds at least one small page for its whole lifetime. - */ -static pool_t *pool_create(void) { - pool_t *pool = MALLOC(sizeof(pool_t)); - if (!pool) { - return NULL; - } - pool->magic = POOL_MAGIC; - pool->small = page_create(PAGE_SMALL_SIZE); - if (!pool->small) { - FREE(pool); - return NULL; - } - pool->large = NULL; - return pool; -} - -/** - * Destroy a memory pool, freeing every page in it. - */ -static void pool_destroy(pool_t *pool) { - if (!pool) { - return; - } - assert(pool->magic == POOL_MAGIC); - pool->magic = 0; // catch use-after-free/double-destroy - for (page_t *p = pool->small; p;) { - page_t *next = p->next; - assert(p->magic == PAGE_MAGIC); - p->magic = 0; // catch use-after-free/double-destroy - FREE(p); - p = next; - } - for (page_t *p = pool->large; p;) { - page_t *next = p->next; - assert(p->magic == PAGE_MAGIC); - p->magic = 0; // catch use-after-free/double-destroy - FREE(p); - p = next; - } - FREE(pool); -} - -/** - * Allocate n bytes from pool. Return the memory allocated on - * success, or NULL if out of memory. - */ -static char *pool_alloc(pool_t *pool, int n) { - assert(pool->magic == POOL_MAGIC); - if (n > PAGE_LARGE_THRESHOLD) { - // Large alloc: own exactly-sized page, used once. - page_t *page = page_create(n); - if (!page) { - return NULL; - } - page->next = pool->large; - pool->large = page; - page->top = page->max; - return page->data; - } - - assert(pool->small->magic == PAGE_MAGIC); - if (pool->small->top + n > pool->small->max) { - // Current small page doesn't have room: abandon its leftover - // space and start a fresh one. - page_t *page = page_create(PAGE_SMALL_SIZE); - if (!page) { - return NULL; - } - page->next = pool->small; - pool->small = page; - } - char *ret = pool->small->data + pool->small->top; - pool->small->top += n; - return ret; -} - -// Cell: expandable buffer that behaves like realloc -typedef struct cell_t cell_t; -struct cell_t { - uint32_t top, max; - // first byte of data starts here (i.e. &(*this)[1]) -}; - -static char *cell_realloc(char *p, int size) { - assert(size >= 0); - if (p == NULL) { - // first malloc - cell_t *tmp = REALLOC(NULL, size + sizeof(cell_t)); - if (!tmp) { - return 0; // out of memory - } - cell_t *cp = tmp; - cp->max = size; - cp->top = size; - return (char *)&cp[1]; - } - - // obtain a handle to cell info - cell_t *cp = (cell_t *)(p - sizeof(cell_t)); - if ((uint32_t)size <= cp->max) { - // cell is big enough for the new size. DONE. - cp->top = size; - return p; - } - - // need to expand. add 30% margin. - int newmax = size * 1.3 + 100; - cell_t *tmp = REALLOC(cp, newmax + sizeof(cell_t)); - if (!tmp) { - return 0; // out of memory - } - cp = tmp; - cp->max = newmax; - cp->top = size; - return (char *)&cp[1]; -} - -static void cell_free(char *p) { - if (p) { - cell_t *cp = (cell_t *)(p - sizeof(cell_t)); - FREE(cp); - } -} - -/* This is a string view. */ -typedef struct span_t span_t; -struct span_t { - const char *ptr; - int len; -}; - -/* Represents a multi-part key */ -#define KEYPARTMAX 10 -typedef struct keypart_t keypart_t; -struct keypart_t { - int nspan; - span_t span[KEYPARTMAX]; -}; - -static int utf8_to_ucs(const char *s, int len, uint32_t *ret); -static int ucs_to_utf8(uint32_t code, char buf[4]); - -// Maximum levels of brackets and braces to prevent -// stack overflow during recursive descent of the parser. -#define BRACKET_LEVEL_MAX 30 -#define BRACE_LEVEL_MAX 30 -#define TABLE_MAX (1 << 14) // 16k -#define ARRAY_MAX (1 << 14) // 16k - -// Buffer size for scanning a number or a timestamp. TOML does not limit the -// length of either literal; tomlc17 does, like the limits above: a literal that -// fills the buffer is rejected as too long, so LITBUF_SIZE - 2 bytes is the -// longest one accepted. That accommodates "9_007_199_254_740_991.0", -// picosecond-precision timestamps, and every other spelling of ordinary length. -// The checks test the length of the literal itself, not of the copy, because -// scan_copystr() also copies whatever follows the literal. -#define LITBUF_SIZE 120 - -enum toktyp_t { - TOK_DOT = 1, - TOK_EQUAL, - TOK_COMMA, - TOK_LBRACK, // [ - TOK_LLBRACK, // [[ - TOK_RBRACK, // ] - TOK_RRBRACK, // ]] - TOK_LBRACE, // { - TOK_RBRACE, // } - TOK_LIT, - TOK_STRING, // "string" - TOK_MLSTRING, // """multi-line-string""" - TOK_LITSTRING, // 'lit-string' - TOK_MLLITSTRING, // '''multi-line-lit-string''' - TOK_TIME, - TOK_DATE, - TOK_DATETIME, - TOK_DATETIMETZ, - TOK_INTEGER, - TOK_FLOAT, - TOK_BOOL, - TOK_ENDL, - TOK_FIN = -5000, // EOF -}; -typedef enum toktyp_t toktyp_t; -typedef struct scanner_t scanner_t; - -/* Remember the current state of a scanner */ -typedef struct scanner_state_t scanner_state_t; -struct scanner_state_t { - scanner_t *sp; - const char *cur; // points into scanner_t::src[] - int lineno; // current line number - const char *line_start; -}; - -// A scan token -typedef struct token_t token_t; -struct token_t { - toktyp_t toktyp; - int lineno; - int colno; - span_t str; - - // values represented by str - union { - const char *escp; // point to an esc char in str - int64_t int64; - double fp64; - bool b1; - struct { - // validity depends on toktyp for TIME, DATE, DATETIME, DATETIMETZ - int year, month, day, hour, minute, sec, usec; - int tz; // +- minutes - } tsval; - } u; -}; - -// Scanner object -struct scanner_t { - const char *src; // src[] - const char *endp; // end of src[], i.e. src + len - const char *cur; // current char in src[] - int lineno; // line number of current char - const char *line_start; - char *errmsg; // set to ebuf.ptr if there was an error - ebuf_t ebuf; // buffer to store error message - - int bracket_level; // count depth of [ ] - int brace_level; // count depth of { } -}; -static void scan_init(scanner_t *sp, const char *src, int len, char *errbuf, - int errbufsz); -static int scan_key(scanner_t *sp, token_t *tok); -static int scan_value(scanner_t *sp, token_t *tok); -// restore scanner to state before tok was returned -static scanner_state_t scan_mark(scanner_t *sp); -static void scan_restore(scanner_t *sp, scanner_state_t state); - -#ifndef min -static inline int min(int a, int b) { return a < b ? a : b; } -#endif - -// Copy up to dstsz - 1 chars from the current position of the scanner -// to dst, and always terminate dst[] with a NUL if dstsz > 0. -static void scan_copystr(scanner_t *sp, char *dst, int dstsz) { - assert(dstsz > 0); - int len = min(sp->endp - sp->cur, dstsz - 1); // account for NUL - if (len > 0) { - memcpy(dst, sp->cur, len); - dst[len] = '\0'; - } -} - -// Parser object -typedef struct parser_t parser_t; -struct parser_t { - scanner_t scanner; - toml_datum_t toptab; // top table - toml_datum_t *curtab; // current table - pool_t *pool; // memory pool for strings - const char *srcname; // source name copied into the pool, or NULL - ebuf_t ebuf; // buffer to store last error message -}; - -// Find key in tab and return its index. If not found, return -1. -static int tab_find(toml_datum_t *tab, span_t key) { - assert(tab->type == TOML_TABLE); - for (int i = 0, top = tab->u.tab.size; i < top; i++) { - if (tab->u.tab.len[i] == key.len && - 0 == memcmp(tab->u.tab.key[i], key.ptr, key.len)) { - return i; - } - } - return -1; -} - -// Put key into tab dictionary. Return a pointer to the datum for the key. -// If the key already exists, returns a pointer to its existing datum (the -// datum is NOT zeroed — callers must check datum->type to detect duplicates). -// If the key is new, appends it with a zero datum and returns a pointer to it. -// Returns NULL on allocation failure. -// -// Dual-use: tab_add() uses the non-zero type to detect and reject duplicates; -// datum_copy/datum_merge() use it to locate or create a slot for writing. -static toml_datum_t *tab_emplace(toml_datum_t *tab, span_t key, - const char **reason) { - assert(tab->type == TOML_TABLE); - int i = tab_find(tab, key); - if (i >= 0) { - return &tab->u.tab.value[i]; - } - - // Expand pkey[], plen[] and value[]. - int N = tab->u.tab.size; - if (N >= TABLE_MAX) { - *reason = "table too large"; - return NULL; - } - - { - char **pkey = - (char **)cell_realloc((char *)tab->u.tab.key, sizeof(*pkey) * (N + 1)); - int *plen = - (int *)cell_realloc((char *)tab->u.tab.len, sizeof(*plen) * (N + 1)); - toml_datum_t *value = (toml_datum_t *)cell_realloc( - (char *)tab->u.tab.value, sizeof(*value) * (N + 1)); - - // on success, must save new pointers in tab->u.tab because the - // old memory areas are gone. - if (pkey) { - tab->u.tab.key = (const char **)pkey; - } - if (plen) { - tab->u.tab.len = plen; - } - if (value) { - tab->u.tab.value = value; - } - - // if any fail, it is safe to bail out. - if (!pkey || !plen || !value) { - *reason = "out of memory"; - return NULL; - } - } - - // There is sufficient space in all the arrays for one more element. - // Append the new key. The value is set to DATUM_ZERO. Caller will - // overwrite with a valid datum. - tab->u.tab.size = N + 1; - tab->u.tab.key[N] = (char *)key.ptr; - tab->u.tab.len[N] = key.len; - tab->u.tab.value[N] = DATUM_ZERO; - return &tab->u.tab.value[N]; -} - -// Add a new key in tab. Return 0 on success, -1 otherwise. -// On error, *reason will point to an error message. -static int tab_add(toml_datum_t *tab, span_t newkey, toml_datum_t newvalue, - const char **reason) { - assert(tab->type == TOML_TABLE); - toml_datum_t *pvalue = tab_emplace(tab, newkey, reason); - if (!pvalue) { - return -1; - } - if (pvalue->type) { - *reason = "duplicate key"; - return -1; - } - *pvalue = newvalue; - return 0; -} - -// Add a new element into an array. Return 0 on success, -1 otherwise. -// On error, *reason will point to an error message. -static toml_datum_t *arr_emplace(toml_datum_t *arr, const char **reason) { - assert(arr->type == TOML_ARRAY); - int n = arr->u.arr.size; - if (n >= ARRAY_MAX) { - *reason = "array too large"; - return NULL; - } - toml_datum_t *elem = (toml_datum_t *)cell_realloc((char *)arr->u.arr.elem, - sizeof(*elem) * (n + 1)); - if (!elem) { - *reason = "out of memory"; - return NULL; - } - arr->u.arr.elem = elem; - arr->u.arr.size = n + 1; - elem[n] = DATUM_ZERO; - return &elem[n]; -} - -// ------------------- parser section -static int parse_norm(parser_t *pp, token_t tok, span_t *ret_span); -static int parse_val(parser_t *pp, token_t tok, toml_datum_t *ret); -static int parse_keyvalue_expr(parser_t *pp, token_t tok); -static int parse_std_table_expr(parser_t *pp, token_t tok); -static int parse_array_table_expr(parser_t *pp, token_t tok); - -static toml_datum_t mkdatum(toml_type_t ty) { - toml_datum_t ret = {0}; - ret.type = ty; - if (ty == TOML_DATE || ty == TOML_TIME || ty == TOML_DATETIME || - ty == TOML_DATETIMETZ) { - ret.u.ts.year = -1; - ret.u.ts.month = -1; - ret.u.ts.day = -1; - ret.u.ts.hour = -1; - ret.u.ts.minute = -1; - ret.u.ts.second = -1; - ret.u.ts.usec = -1; - ret.u.ts.tz = -1; - } - return ret; -} - -static toml_datum_t mkdatum_at(toml_type_t ty, int lineno, int colno) { - toml_datum_t ret = mkdatum(ty); - ret.lineno = lineno; - ret.colno = colno; - return ret; -} - -// Recursively free any dynamically allocated memory in the datum tree -static void datum_free(toml_datum_t *datum) { - if (datum->type == TOML_TABLE) { - for (int i = 0, top = datum->u.tab.size; i < top; i++) { - datum_free(&datum->u.tab.value[i]); - } - cell_free((char *)(void *)datum->u.tab.key); - cell_free((char *)datum->u.tab.len); - cell_free((char *)datum->u.tab.value); - } else if (datum->type == TOML_ARRAY) { - for (int i = 0, top = datum->u.arr.size; i < top; i++) { - datum_free(&datum->u.arr.elem[i]); - } - cell_free((char *)datum->u.arr.elem); - } - // other types do not allocate memory - *datum = DATUM_ZERO; -} - -// Maps each distinct source-name pointer to a single copy in the destination -// pool (deduplicated), so the merged pool holds at most one copy per distinct -// source pointer. Dedup is by pointer identity, not string content: every -// datum from one parse shares one source pointer (set_source_recursive), so -// dedup is exact within a tree, but two trees with content-identical source -// names (e.g. the same filename parsed twice) still get separate copies. -typedef struct srcmap_t srcmap_t; -struct srcmap_t { - pool_t *pool; - const char **olds; // cell buffer of old pointers - const char **news; // cell buffer of new (copied) pointers - int n, cap; -}; - -// Returns the deduplicated copy of src, or NULL for a NULL input or on -// out-of-memory. Callers distinguish the two: a non-NULL src that yields NULL -// is a hard failure. -static const char *dedup_source(srcmap_t *m, const char *src) { - if (!src) { - return NULL; - } - // Pointer-identity match (see srcmap_t above). The scan is O(n) in the - // number of distinct source pointers seen so far, not in the (much - // larger) number of datums, since repeats short-circuit here. - for (int i = 0; i < m->n; i++) { - if (m->olds[i] == src) { - return m->news[i]; - } - } - // Grow the memo first, so an allocation failure aborts cleanly. - if (m->n == m->cap) { - int newcap = m->cap ? m->cap * 2 : 4; - const char **no = - (const char **)cell_realloc((char *)m->olds, sizeof(*no) * newcap); - if (!no) { - return NULL; // out of memory - } - m->olds = no; - const char **nn = - (const char **)cell_realloc((char *)m->news, sizeof(*nn) * newcap); - if (!nn) { - return NULL; // out of memory - } - m->news = nn; - m->cap = newcap; - } - int len = (int)strlen(src) + 1; - char *p = pool_alloc(m->pool, len); - if (!p) { - return NULL; // out of memory - } - memcpy(p, src, len); - m->olds[m->n] = src; - m->news[m->n] = p; - m->n++; - return p; -} - -// Make a deep copy of src to dst. -// Return 0 on success, -1 otherwise. -static int datum_copy(toml_datum_t *dst, toml_datum_t src, srcmap_t *sm, - const char **reason) { - *dst = mkdatum_at(src.type, src.lineno, src.colno); - dst->flag = src.flag; - switch (src.type) { - case TOML_STRING: - // String bytes live in src's pool; copy them into dst's pool so dst - // stays independently owned. - dst->u.str.ptr = pool_alloc(sm->pool, src.u.str.len + 1); - if (!dst->u.str.ptr) { - *reason = "out of memory"; - goto bail; - } - dst->u.str.len = src.u.str.len; - memcpy((char *)dst->u.str.ptr, src.u.str.ptr, src.u.str.len + 1); - break; - case TOML_TABLE: - // Recreate each entry in dst: the key bytes are pool-copied (like a - // string value), and the value is deep-copied recursively. - for (int i = 0; i < src.u.tab.size; i++) { - char *keycopy = pool_alloc(sm->pool, src.u.tab.len[i] + 1); - if (!keycopy) { - *reason = "out of memory"; - goto bail; - } - memcpy(keycopy, src.u.tab.key[i], src.u.tab.len[i] + 1); - span_t newkey = {keycopy, src.u.tab.len[i]}; - toml_datum_t *pvalue = tab_emplace(dst, newkey, reason); - if (!pvalue) { - goto bail; - } - if (datum_copy(pvalue, src.u.tab.value[i], sm, reason)) { - goto bail; - } - } - break; - case TOML_ARRAY: - // Deep-copy each element into a freshly emplaced slot in dst. - for (int i = 0; i < src.u.arr.size; i++) { - toml_datum_t *pelem = arr_emplace(dst, reason); - if (!pelem) { - goto bail; - } - if (datum_copy(pelem, src.u.arr.elem[i], sm, reason)) { - goto bail; - } - } - break; - default: - // Every other type (int, float, bool, timestamp, invalid) is a plain - // value with no pool-owned pointers, so a struct copy is a full copy. - *dst = src; - break; - } - - dst->source = dedup_source(sm, src.source); - if (src.source && !dst->source) { - *reason = "out of memory"; - goto bail; - } - return 0; - -bail: - datum_free(dst); - return -1; -} - -// Check if datum is an array of tables. An empty array is deliberately -// excluded: it has no elements to type-check, so treating it as an array -// of tables here would be true only vacuously, and datum_merge's caller -// relies on this to distinguish "override with an empty array" from -// "append zero table elements" (see below). -static inline bool is_array_of_tables(toml_datum_t datum) { - bool ret = (datum.type == TOML_ARRAY) && (datum.u.arr.size > 0); - for (int i = 0; ret && i < datum.u.arr.size; i++) { - ret = (datum.u.arr.elem[i].type == TOML_TABLE); - } - return ret; -} - -// Merge src into dst. Return 0 on success, -1 otherwise. -static int datum_merge(toml_datum_t *dst, toml_datum_t src, srcmap_t *sm, - const char **reason) { - if (dst->type != src.type) { - datum_free(dst); - return datum_copy(dst, src, sm, reason); - } - switch (src.type) { - case TOML_TABLE: - // for key-value in src: - // override key-value in dst. - for (int i = 0; i < src.u.tab.size; i++) { - char *keycopy = pool_alloc(sm->pool, src.u.tab.len[i] + 1); - if (!keycopy) { - *reason = "out of memory"; - return -1; - } - memcpy(keycopy, src.u.tab.key[i], src.u.tab.len[i] + 1); - span_t key; - key.ptr = keycopy; - key.len = src.u.tab.len[i]; - toml_datum_t *pvalue = tab_emplace(dst, key, reason); - if (!pvalue) { - return -1; - } - if (pvalue->type) { - DO(datum_merge(pvalue, src.u.tab.value[i], sm, reason)); - } else { - datum_free(pvalue); - DO(datum_copy(pvalue, src.u.tab.value[i], sm, reason)); - } - } - return 0; - case TOML_ARRAY: - if (is_array_of_tables(*dst) && is_array_of_tables(src)) { - // append src array to dst - for (int i = 0; i < src.u.arr.size; i++) { - toml_datum_t *pelem = arr_emplace(dst, reason); - if (!pelem) { - return -1; - } - DO(datum_copy(pelem, src.u.arr.elem[i], sm, reason)); - } - return 0; - } - // fallthru - default: - break; - } - datum_free(dst); - return datum_copy(dst, src, sm, reason); -} - -// Compare the content of a and b. -static bool datum_equiv(toml_datum_t a, toml_datum_t b) { - if (a.type != b.type) { - return false; - } - int N; - switch (a.type) { - case TOML_STRING: - return a.u.str.len == b.u.str.len && - 0 == memcmp(a.u.str.ptr, b.u.str.ptr, a.u.str.len); - case TOML_INT64: - return a.u.int64 == b.u.int64; - case TOML_FP64: - return a.u.fp64 == b.u.fp64 || (isnan(a.u.fp64) && isnan(b.u.fp64)); - case TOML_BOOLEAN: - return !!a.u.boolean == !!b.u.boolean; - case TOML_DATE: - return a.u.ts.year == b.u.ts.year && a.u.ts.month == b.u.ts.month && - a.u.ts.day == b.u.ts.day; - case TOML_TIME: - return a.u.ts.hour == b.u.ts.hour && a.u.ts.minute == b.u.ts.minute && - a.u.ts.second == b.u.ts.second && a.u.ts.usec == b.u.ts.usec; - case TOML_DATETIME: - return a.u.ts.year == b.u.ts.year && a.u.ts.month == b.u.ts.month && - a.u.ts.day == b.u.ts.day && a.u.ts.hour == b.u.ts.hour && - a.u.ts.minute == b.u.ts.minute && a.u.ts.second == b.u.ts.second && - a.u.ts.usec == b.u.ts.usec; - case TOML_DATETIMETZ: - return a.u.ts.year == b.u.ts.year && a.u.ts.month == b.u.ts.month && - a.u.ts.day == b.u.ts.day && a.u.ts.hour == b.u.ts.hour && - a.u.ts.minute == b.u.ts.minute && a.u.ts.second == b.u.ts.second && - a.u.ts.usec == b.u.ts.usec && a.u.ts.tz == b.u.ts.tz; - case TOML_ARRAY: - N = a.u.arr.size; - if (N != b.u.arr.size) { - return false; - } - for (int i = 0; i < N; i++) { - if (!datum_equiv(a.u.arr.elem[i], b.u.arr.elem[i])) { - return false; - } - } - return true; - case TOML_TABLE: - // Tables are unordered maps: match each key of a against any key of b, - // not positionally. Equal sizes plus a full one-way key match implies a - // bijection because keys are unique within a table. - N = a.u.tab.size; - if (N != b.u.tab.size) { - return false; - } - for (int i = 0; i < N; i++) { - int len = a.u.tab.len[i]; - int j = 0; - for (; j < N; j++) { - if (len == b.u.tab.len[j] && - 0 == memcmp(a.u.tab.key[i], b.u.tab.key[j], len)) { - break; - } - } - if (j == N) { - return false; - } - if (!datum_equiv(a.u.tab.value[i], b.u.tab.value[j])) { - return false; - } - } - return true; - default: - break; - } - return false; -} - -/** - * Override values in r1 using r2. Return a new result. All results - * (i.e., r1, r2 and the returned result) must be freed using toml_free() - * after use. - * - * LOGIC: - * ret = copy of r1 - * for each item x in r2: - * if x is not in ret: - * override - * elif x in ret is NOT of the same type: - * override - * elif x is an array of tables: - * append r2.x to ret.x - * elif x is a table: - * merge r2.x to ret.x - * else: - * override - */ -toml_result_t toml_merge(const toml_result_t *r1, const toml_result_t *r2) { - const char *reason = ""; - toml_result_t ret = {0}; - pool_t *pool = 0; - srcmap_t sm = {0}; - if (!r1->ok) { - reason = "param error: r1 not ok"; - goto bail; - } - if (!r2->ok) { - reason = "param error: r2 not ok"; - goto bail; - } - pool = pool_create(); - if (!pool) { - reason = "out of memory"; - goto bail; - } - // sm is scoped to this single merge call: its olds[]/news[] memo arrays - // are just bookkeeping to dedup source-name pointers while copying below, - // and are freed once the copy/merge is done. The deduplicated string - // copies they produced live on in `pool`, which becomes the result's pool. - sm.pool = pool; - - // Make a copy of r1 - if (datum_copy(&ret.toptab, r1->toptab, &sm, &reason)) { - goto bail; - } - - // Merge r2 into the result - if (datum_merge(&ret.toptab, r2->toptab, &sm, &reason)) { - goto bail; - } - - cell_free((char *)(void *)sm.olds); - cell_free((char *)(void *)sm.news); - ret.ok = 1; - ret.__internal = pool; - return ret; - -bail: - cell_free((char *)(void *)sm.olds); - cell_free((char *)(void *)sm.news); - pool_destroy(pool); - snprintf(ret.errmsg, sizeof(ret.errmsg), "%s", reason); - return ret; -} - -bool toml_equiv(const toml_result_t *r1, const toml_result_t *r2) { - if (!(r1->ok && r2->ok)) { - return false; - } - return datum_equiv(r1->toptab, r2->toptab); -} - -/** - * Find a key in a toml_table. Return the value of the key if found, - * or a TOML_UNKNOWN otherwise. - */ -toml_datum_t toml_get(toml_datum_t datum, const char *key) { - if (datum.type == TOML_TABLE) { - int n = datum.u.tab.size; - if (n > 0) { - int key_len = strlen(key); - const char **pkey = datum.u.tab.key; - int *plen = datum.u.tab.len; - toml_datum_t *pvalue = datum.u.tab.value; - for (int i = 0; i < n; i++) { - if (plen[i] == key_len && 0 == memcmp(pkey[i], key, key_len)) { - return pvalue[i]; - } - } - } - } - return DATUM_ZERO; -} - -/** - * Locate a value starting from a toml_table. Return the value of the key if - * found, or a TOML_UNKNOWN otherwise. - * - * Note: the multipart-key is separated by DOT, and must not have any escape - * chars. - */ -toml_datum_t toml_seek(toml_datum_t table, const char *multipart_key) { - if (table.type != TOML_TABLE) { - return DATUM_ZERO; - } - - // Make a mutable copy of the multipart_key for splitting - char buf[256]; - if (copystring(buf, sizeof(buf), multipart_key)) { - // if the multipart_key is longer than buffer, just - // signal a not-found. - return DATUM_ZERO; - } - - // Go through the multipart name part by part. - char *p = buf; - toml_datum_t datum = table; - while (datum.type == TOML_TABLE) { - char *q = strchr(p, '.'); - if (q) { - // traverse to next key - *q = 0; - datum = toml_get(datum, p); - p = q + 1; - continue; - } - - // At end of last keypart. - // look up p in the final table - return toml_get(datum, p); - } - - return DATUM_ZERO; -} - -/** - * Return the default options. - */ -toml_option_t toml_default_option(void) { - toml_option_t opt = {0, realloc, free}; - return opt; -} - -/** - * Override the current options. - */ -void toml_set_option(toml_option_t opt) { toml_option = opt; } - -/** - * Free the result returned by toml_parse(). - */ -void toml_free(toml_result_t result) { - datum_free(&result.toptab); - pool_destroy((pool_t *)result.__internal); -} - -/** - * Parse a toml document. - */ -toml_result_t toml_parse_file_ex(const char *fname) { - toml_result_t result = {0}; - FILE *fp = fopen(fname, "r"); - if (!fp) { - snprintf(result.errmsg, sizeof(result.errmsg), "fopen %s: %s", fname, - strerror(errno)); - return result; - } - result = toml_parse_file_named(fp, fname); - fclose(fp); - return result; -} - -toml_result_t toml_parse_file(FILE *fp) { - return toml_parse_file_named(fp, NULL); -} - -/** - * Parse a toml document from a file, tagging datums with name. - */ -toml_result_t toml_parse_file_named(FILE *fp, const char *name) { - toml_result_t result = {0}; - if (!fp) { - snprintf(result.errmsg, sizeof(result.errmsg), "fp is NULL"); - return result; - } - char *buf = 0; - int top = 0; // number of bytes read into buf[] - enum { CHUNKSZ = 8 * 1024 }; // bytes to read per iteration - - // Read file into memory. cell_realloc handles capacity growth, so we only - // need to ask for room for one more chunk each pass. Drive the loop off - // fread's return value rather than feof(): feof() only reports true after - // a read has already hit EOF. - for (;;) { - if (top > INT_MAX - CHUNKSZ) { - snprintf(result.errmsg, sizeof(result.errmsg), "file is too big"); - break; - } - char *tmp = cell_realloc(buf, top + CHUNKSZ); - if (!tmp) { - snprintf(result.errmsg, sizeof(result.errmsg), "out of memory"); - break; - } - buf = tmp; - - errno = 0; - size_t n = fread(buf + top, 1, CHUNKSZ, fp); - top += n; - if (n < CHUNKSZ) { // short read => EOF or error - if (ferror(fp)) { - snprintf(result.errmsg, sizeof(result.errmsg), "%s", - errno ? strerror(errno) : "Error reading file"); - break; - } - if (feof(fp)) { - break; - } - // small probability of short read due to signal, etc. - } - } - // error? - if (result.errmsg[0]) { - cell_free(buf); - return result; - } - result = toml_parse_named(buf, top, name); - cell_free(buf); - return result; -} - -static void set_source_recursive(toml_datum_t *datum, const char *source) { - datum->source = source; - switch (datum->type) { - case TOML_ARRAY: - for (int i = 0, top = datum->u.arr.size; i < top; i++) { - set_source_recursive(&datum->u.arr.elem[i], source); - } - break; - case TOML_TABLE: - for (int i = 0, top = datum->u.tab.size; i < top; i++) { - set_source_recursive(&datum->u.tab.value[i], source); - } - break; - default: - break; - } -} - -/** - * Parse a toml document. - */ -toml_result_t toml_parse(const char *src, int len) { - return toml_parse_named(src, len, NULL); -} - -toml_result_t toml_parse_named(const char *src, int len, const char *name) { - toml_result_t result = {0}; - parser_t parser = {0}; - parser_t *pp = &parser; - - // Check that src is not NULL. - if (!src) { - snprintf(result.errmsg, sizeof(result.errmsg), "src is NULL"); - goto bail; - } - - // A UTF-8 BOM at the very start is an encoding artifact rather than content, - // so drop it before scanning. Everywhere else U+FEFF stays exactly as it is - // and remains an error, which is what bom-not-at-start expects. - if (len >= 3 && (unsigned char)src[0] == 0xEF && - (unsigned char)src[1] == 0xBB && (unsigned char)src[2] == 0xBF) { - src += 3; - len -= 3; - } - - // If user insists, check that src[] is a valid utf8 string. - if (toml_option.check_utf8) { - int line = 1; // keeps track of line number - for (int i = 0; i < len;) { - uint32_t ch; - int n = utf8_to_ucs(src + i, len - i, &ch); - if (n < 0) { - snprintf(result.errmsg, sizeof(result.errmsg), - "invalid UTF8 char on line %d", line); - goto bail; - } - if (0xD800 <= ch && ch <= 0xDFFF) { - // explicitly prohibit surrogates (non-scalar unicode code point) - snprintf(result.errmsg, sizeof(result.errmsg), - "invalid UTF8 char \\u%04x on line %d", ch, line); - goto bail; - } - line += (ch == '\n' ? 1 : 0); - i += n; - } - } - - // Initialize parser - pp->toptab = mkdatum_at(TOML_TABLE, 1, 1); - pp->curtab = &pp->toptab; - pp->ebuf.ptr = result.errmsg; // parse error will be printed into pp->ebuf - pp->ebuf.len = sizeof(result.errmsg); - - // Alloc memory pool - int namelen = name ? (int)strlen(name) + 1 : 0; - pp->pool = pool_create(); - if (!pp->pool) { - snprintf(result.errmsg, sizeof(result.errmsg), "out of memory"); - goto bail; - } - if (name) { - char *p = pool_alloc(pp->pool, namelen); - if (!p) { - snprintf(result.errmsg, sizeof(result.errmsg), "out of memory"); - goto bail; - } - memcpy(p, name, namelen); - pp->srcname = p; - } - - // Initialize scanner. Scan error will be printed into pp->ebuf. - scan_init(&pp->scanner, src, len, pp->ebuf.ptr, pp->ebuf.len); - - // Keep parsing until FIN - for (;;) { - token_t tok; - if (scan_key(&pp->scanner, &tok)) { - goto bail; - } - // break on FIN - if (tok.toktyp == TOK_FIN) { - break; - } - switch (tok.toktyp) { - case TOK_ENDL: // skip blank lines - continue; - case TOK_LBRACK: - if (parse_std_table_expr(pp, tok)) { - goto bail; - } - break; - case TOK_LLBRACK: - if (parse_array_table_expr(pp, tok)) { - goto bail; - } - break; - default: - // non-blank line: parse an expression - if (parse_keyvalue_expr(pp, tok)) { - goto bail; - } - break; - } - // each expression must be followed by newline - if (scan_key(&pp->scanner, &tok)) { - goto bail; - } - if (tok.toktyp == TOK_FIN || tok.toktyp == TOK_ENDL) { - continue; - } - SETERROR(pp->ebuf, tok.lineno, "ENDL expected"); - goto bail; - } - - // return result - result.ok = true; - set_source_recursive(&pp->toptab, pp->srcname); - result.toptab = pp->toptab; - result.__internal = (void *)pp->pool; - return result; - -bail: - // return error - datum_free(&pp->toptab); - pool_destroy(pp->pool); - result.ok = false; - if (result.errmsg[0] == '\0') { - assert(0); - snprintf(result.errmsg, sizeof(result.errmsg), "Error near line %d\n", - pp->scanner.lineno); - } - return result; -} - -// Convert a (LITSTRING, LIT, MLLITSTRING, MLSTRING, or STRING) token to a -// datum. -static int token_to_string(parser_t *pp, token_t tok, toml_datum_t *ret) { - *ret = mkdatum_at(TOML_STRING, tok.lineno, tok.colno); - span_t span; - DO(parse_norm(pp, tok, &span)); - ret->u.str.ptr = (char *)span.ptr; - ret->u.str.len = span.len; - return 0; -} - -// Convert a TIME/DATE/DATETIME/DATETIMETZ to a datum -static int token_to_timestamp(parser_t *pp, token_t tok, toml_datum_t *ret) { - (void)pp; - static const toml_type_t map[] = {[TOK_TIME] = TOML_TIME, - [TOK_DATE] = TOML_DATE, - [TOK_DATETIME] = TOML_DATETIME, - [TOK_DATETIMETZ] = TOML_DATETIMETZ}; - switch (tok.toktyp) { - case TOK_TIME: - case TOK_DATE: - case TOK_DATETIME: - case TOK_DATETIMETZ: - break; - default: - assert(0 && "unexpected token type"); - return -1; - } - - *ret = mkdatum_at(map[tok.toktyp], tok.lineno, tok.colno); - ret->u.ts.year = tok.u.tsval.year; - ret->u.ts.month = tok.u.tsval.month; - ret->u.ts.day = tok.u.tsval.day; - ret->u.ts.hour = tok.u.tsval.hour; - ret->u.ts.minute = tok.u.tsval.minute; - ret->u.ts.second = tok.u.tsval.sec; - ret->u.ts.usec = tok.u.tsval.usec; - ret->u.ts.tz = tok.u.tsval.tz; - return 0; -} - -// Convert an int64 token to a datum. -static int token_to_int64(parser_t *pp, token_t tok, toml_datum_t *ret) { - (void)pp; - assert(tok.toktyp == TOK_INTEGER); - *ret = mkdatum_at(TOML_INT64, tok.lineno, tok.colno); - ret->u.int64 = tok.u.int64; - return 0; -} - -// Convert a fp64 token to a datum. -static int token_to_fp64(parser_t *pp, token_t tok, toml_datum_t *ret) { - (void)pp; - assert(tok.toktyp == TOK_FLOAT); - *ret = mkdatum_at(TOML_FP64, tok.lineno, tok.colno); - ret->u.fp64 = tok.u.fp64; - return 0; -} - -// Convert a boolean token to a datum. -static int token_to_boolean(parser_t *pp, token_t tok, toml_datum_t *ret) { - (void)pp; - assert(tok.toktyp == TOK_BOOL); - *ret = mkdatum_at(TOML_BOOLEAN, tok.lineno, tok.colno); - ret->u.boolean = tok.u.b1; - return 0; -} - -// Parse a multipart key. Return 0 on success, -1 otherwise. -static int parse_key(parser_t *pp, token_t tok, keypart_t *ret_keypart) { - ret_keypart->nspan = 0; - // key = simple-key | dotted_key - // simple-key = STRING | LITSTRING | LIT - // dotted-key = simple-key (DOT simple-key)+ - if (tok.toktyp != TOK_STRING && tok.toktyp != TOK_LITSTRING && - tok.toktyp != TOK_LIT) { - return SETERROR(pp->ebuf, tok.lineno, "missing key"); - } - - int n = 0; - span_t *kpspan = ret_keypart->span; - - // Normalize the first keypart - if (parse_norm(pp, tok, &kpspan[n])) { - return SETERROR(pp->ebuf, tok.lineno, - "unable to normalize string; probably a unicode issue"); - } - n++; - - // Scan and normalize the second to last keypart - while (1) { - scanner_state_t mark = scan_mark(&pp->scanner); - - // Eat the dot if it is there - DO(scan_key(&pp->scanner, &tok)); - - // If not a dot, we are done with keyparts. - if (tok.toktyp != TOK_DOT) { - scan_restore(&pp->scanner, mark); - break; - } - - // Scan the n-th key - DO(scan_key(&pp->scanner, &tok)); - - if (tok.toktyp != TOK_STRING && tok.toktyp != TOK_LITSTRING && - tok.toktyp != TOK_LIT) { - return SETERROR(pp->ebuf, tok.lineno, "expects a string in dotted-key"); - } - - if (n >= KEYPARTMAX) { - return SETERROR(pp->ebuf, tok.lineno, "too many key parts"); - } - - // Normalize the n-th key. - DO(parse_norm(pp, tok, &kpspan[n])); - n++; - } - - // This key has n parts. - ret_keypart->nspan = n; - return 0; -} - -// Starting at toptab, descend following keypart[]. If a key does not -// exist in the current table, create a new table entry for the -// key. Returns the final table represented by the key. -static toml_datum_t *descend_keypart(parser_t *pp, int lineno, int colno, - toml_datum_t *toptab, keypart_t *keypart, - bool stdtabexpr) { - toml_datum_t *tab = toptab; // current tab - - for (int i = 0; i < keypart->nspan; i++) { - const char *reason; - // Find the i-th keypart - int j = tab_find(tab, keypart->span[i]); - // Not found: add a new (key, tab) pair. - if (j < 0) { - toml_datum_t newtab = mkdatum_at(TOML_TABLE, lineno, colno); - newtab.flag |= stdtabexpr ? TOML_FLAG_STDEXPR : 0; - if (tab_add(tab, keypart->span[i], newtab, &reason)) { - SETERROR(pp->ebuf, lineno, "%s", reason); - return NULL; - } - tab = &tab->u.tab.value[tab->u.tab.size - 1]; // descend - continue; - } - - // Found: extract the value of the key. - toml_datum_t *value = &tab->u.tab.value[j]; - - // If the value is a table, descend. - if (value->type == TOML_TABLE) { - tab = value; // descend - continue; - } - - // If the value is an array: locate the last entry and descend. - if (value->type == TOML_ARRAY) { - // If empty: error. - if (value->u.arr.size <= 0) { - SETERROR(pp->ebuf, lineno, "array %s has no elements", - keypart->span[i].ptr); - return NULL; - } - - // Extract the last element of the array. - value = &value->u.arr.elem[value->u.arr.size - 1]; - - // It must be a table! - if (value->type != TOML_TABLE) { - SETERROR(pp->ebuf, lineno, "array %s must be array of tables", - keypart->span[i].ptr); - return NULL; - } - tab = value; // descend - continue; - } - - // key not found - SETERROR(pp->ebuf, lineno, "cannot locate table at key %s", - keypart->span[i].ptr); - return NULL; - } - - // Return the table corresponding to the keypart[]. - return tab; -} - -// Recursively set flags on datum -static void set_flag_recursive(toml_datum_t *datum, uint32_t flag) { - datum->flag |= flag; - switch (datum->type) { - case TOML_ARRAY: - for (int i = 0, top = datum->u.arr.size; i < top; i++) { - set_flag_recursive(&datum->u.arr.elem[i], flag); - } - break; - case TOML_TABLE: - for (int i = 0, top = datum->u.tab.size; i < top; i++) { - set_flag_recursive(&datum->u.tab.value[i], flag); - } - break; - default: - break; - } -} - -// Parse an inline array. -static int parse_inline_array(parser_t *pp, token_t tok, - toml_datum_t *ret_datum) { - assert(tok.toktyp == TOK_LBRACK); - *ret_datum = mkdatum_at(TOML_ARRAY, tok.lineno, tok.colno); - int need_comma = 0; - - // loop until RBRACK - for (;;) { - // skip ENDL - do { - DO(scan_value(&pp->scanner, &tok)); - } while (tok.toktyp == TOK_ENDL); - - // If got an RBRACK: done! - if (tok.toktyp == TOK_RBRACK) { - break; - } - - // If got a COMMA: check if it is expected. - if (tok.toktyp == TOK_COMMA) { - if (need_comma) { - need_comma = 0; - continue; - } - return SETERROR(pp->ebuf, tok.lineno, - "syntax error while parsing array: unexpected comma"); - } - - // Not a comma, but need a comma: error! - if (need_comma) { - return SETERROR(pp->ebuf, tok.lineno, - "syntax error while parsing array: missing comma"); - } - - // This is a valid value! Obtain the value. - toml_datum_t value = DATUM_ZERO; - if (parse_val(pp, tok, &value)) { - datum_free(&value); - return -1; - } - - // Add the value to the array. - const char *reason; - toml_datum_t *pelem = arr_emplace(ret_datum, &reason); - if (!pelem) { - datum_free(&value); - return SETERROR(pp->ebuf, tok.lineno, "while parsing array: %s", reason); - } - *pelem = value; - - // Need comma before the next value. - need_comma = 1; - } - - // Set the INLINE flag for all things in this array. - set_flag_recursive(ret_datum, TOML_FLAG_INLINED); - return 0; -} - -// Parse an inline table. -static int parse_inline_table(parser_t *pp, token_t tok, - toml_datum_t *ret_datum) { - assert(tok.toktyp == TOK_LBRACE); - *ret_datum = mkdatum_at(TOML_TABLE, tok.lineno, tok.colno); - bool need_comma = 0; - bool was_comma = 0; - - // loop until RBRACE - for (;;) { - DO(scan_key(&pp->scanner, &tok)); - - // Got an RBRACE: done! - if (tok.toktyp == TOK_RBRACE) { - if (was_comma) { - /* - return SETERROR(pp->ebuf, tok.lineno, - "extra comma before closing brace"); - */ - // extra comma before RBRACE is allowed for v1.1 - (void)0; - } - break; - } - - // Got a comma: check if it is expected. - if (tok.toktyp == TOK_COMMA) { - if (need_comma) { - need_comma = 0, was_comma = 1; - continue; - } - return SETERROR(pp->ebuf, tok.lineno, "unexpected comma"); - } - - // Newline not allowed in inline table. - // newline is allowed in v1.1 - if (tok.toktyp == TOK_ENDL) { - // return SETERROR(pp->ebuf, tok.lineno, "unexpected newline"); - continue; - } - - // Not a comma, but need a comma: error! - if (need_comma) { - return SETERROR(pp->ebuf, tok.lineno, "missing comma"); - } - - // Get the keyparts - keypart_t keypart = {0}; - int keylineno = tok.lineno; - int keycolno = tok.colno; - DO(parse_key(pp, tok, &keypart)); - - // Descend to one keypart before last - assert(keypart.nspan > 0); - span_t lastkeypart = keypart.span[--keypart.nspan]; - toml_datum_t *tab = - descend_keypart(pp, keylineno, keycolno, ret_datum, &keypart, false); - if (!tab) { - return -1; - } - - // If tab is a previously declared inline table: error. - if (tab->flag & TOML_FLAG_INLINED) { - return SETERROR(pp->ebuf, tok.lineno, "inline table cannot be extended"); - } - - // We are explicitly defining it now. - tab->flag |= TOML_FLAG_EXPLICIT; - - // match EQUAL - DO(scan_value(&pp->scanner, &tok)); - - if (tok.toktyp != TOK_EQUAL) { - if (tok.toktyp == TOK_ENDL) { - return SETERROR(pp->ebuf, tok.lineno, "unexpected newline"); - } else { - return SETERROR(pp->ebuf, tok.lineno, "missing '='"); - } - } - - // obtain the value - DO(scan_value(&pp->scanner, &tok)); - toml_datum_t value = DATUM_ZERO; - if (parse_val(pp, tok, &value)) { - datum_free(&value); - return -1; - } - - // Add the value to tab. - const char *reason; - if (tab_add(tab, lastkeypart, value, &reason)) { - datum_free(&value); - return SETERROR(pp->ebuf, tok.lineno, "%s", reason); - } - need_comma = 1, was_comma = 0; - } - - set_flag_recursive(ret_datum, TOML_FLAG_INLINED); - return 0; -} - -// Parse a value. -static int parse_val(parser_t *pp, token_t tok, toml_datum_t *ret) { - *ret = DATUM_ZERO; // initialize - - // val = string / boolean / array / inline-table / date-time / float / integer - switch (tok.toktyp) { - case TOK_STRING: - case TOK_MLSTRING: - case TOK_LITSTRING: - case TOK_MLLITSTRING: - return token_to_string(pp, tok, ret); - case TOK_TIME: - case TOK_DATE: - case TOK_DATETIME: - case TOK_DATETIMETZ: - return token_to_timestamp(pp, tok, ret); - case TOK_INTEGER: - return token_to_int64(pp, tok, ret); - case TOK_FLOAT: - return token_to_fp64(pp, tok, ret); - case TOK_BOOL: - return token_to_boolean(pp, tok, ret); - case TOK_LBRACK: // inline-array - return parse_inline_array(pp, tok, ret); - case TOK_LBRACE: // inline-table - return parse_inline_table(pp, tok, ret); - default: - return SETERROR(pp->ebuf, tok.lineno, "missing value"); - } -} - -// Parse a standard table expression, and set the curtab of the parser -// to the table referenced. A standard table expression is a line -// like [a.b.c.d]. -static int parse_std_table_expr(parser_t *pp, token_t tok) { - // std-table = [ key ] - // Eat the [ - assert(tok.toktyp == TOK_LBRACK); // [ ate by caller - - // Read the first keypart - DO(scan_key(&pp->scanner, &tok)); - - // Extract the keypart[] - int keylineno = tok.lineno; - int keycolno = tok.colno; - keypart_t keypart; - DO(parse_key(pp, tok, &keypart)); - - // Eat the ] - DO(scan_key(&pp->scanner, &tok)); - if (tok.toktyp != TOK_RBRACK) { - return SETERROR(pp->ebuf, tok.lineno, "missing right-bracket"); - } - - // Descend to one keypart before last. - span_t lastkeypart = keypart.span[--keypart.nspan]; - - // Descend keypart from the toptab. - toml_datum_t *tab = - descend_keypart(pp, keylineno, keycolno, &pp->toptab, &keypart, true); - if (!tab) { - return -1; - } - - // Look for the last keypart in the final tab - int j = tab_find(tab, lastkeypart); - if (j < 0) { - // If not found: add it. - if (tab->flag & TOML_FLAG_INLINED) { - return SETERROR(pp->ebuf, keylineno, "inline table cannot be extended"); - } - const char *reason; - toml_datum_t newtab = mkdatum_at(TOML_TABLE, keylineno, keycolno); - newtab.flag |= TOML_FLAG_STDEXPR; - if (tab_add(tab, lastkeypart, newtab, &reason)) { - return SETERROR(pp->ebuf, keylineno, "%s", reason); - } - // this is the new tab - tab = &tab->u.tab.value[tab->u.tab.size - 1]; - } else { - // Found: check for errors - tab = &tab->u.tab.value[j]; - if (tab->flag & TOML_FLAG_EXPLICIT) { - /* - This is not OK: - [x.y.z] - [x.y.z] - - but this is OK: - [x.y.z] - [x] - */ - return SETERROR(pp->ebuf, keylineno, "table defined more than once"); - } - if (!(tab->flag & TOML_FLAG_STDEXPR)) { - /* - [t1] # OK - t2.t3.v = 0 # OK - [t1.t2] # should FAIL - t2 was non-explicit but was not - created by std-table-expr - */ - return SETERROR(pp->ebuf, keylineno, "table defined before"); - } - } - - // Set explicit flag on tab - tab->flag |= TOML_FLAG_EXPLICIT; - tab->lineno = keylineno; - tab->colno = keycolno; - - // Set tab as curtab of the parser - pp->curtab = tab; - return 0; -} - -// Parse an array table expression, and set the curtab of the parser -// to the table referenced. A standard array table expression is a line -// like [[a.b.c.d]]. -static int parse_array_table_expr(parser_t *pp, token_t tok) { - // array-table = [[ key ]] - assert(tok.toktyp == TOK_LLBRACK); // [[ ate by caller - - // Read the first keypart - DO(scan_key(&pp->scanner, &tok)); - - int keylineno = tok.lineno; - int keycolno = tok.colno; - keypart_t keypart; - DO(parse_key(pp, tok, &keypart)); - - // eat the ]] - token_t rrb; - DO(scan_key(&pp->scanner, &rrb)); - if (rrb.toktyp != TOK_RRBRACK) { - return SETERROR(pp->ebuf, rrb.lineno, "missing ']]'"); - } - - // remove the last keypart from keypart[] - assert(keypart.nspan > 0); - span_t lastkeypart = keypart.span[--keypart.nspan]; - - // descend intermediate keys from toptab - toml_datum_t *tab = - descend_keypart(pp, keylineno, keycolno, &pp->toptab, &keypart, true); - if (!tab) - return -1; - - // For the final keypart, make sure entry at key is an array of tables - const char *reason; - int idx = tab_find(tab, lastkeypart); - if (idx == -1) { - // If not found, add an array of table. - if (tab->flag & TOML_FLAG_INLINED) { - return SETERROR(pp->ebuf, keylineno, "inline table cannot be extended"); - } - toml_datum_t newarr = mkdatum_at(TOML_ARRAY, keylineno, keycolno); - if (tab_add(tab, lastkeypart, newarr, &reason)) { - return SETERROR(pp->ebuf, keylineno, "%s", reason); - } - idx = tab_find(tab, lastkeypart); - assert(idx >= 0); - } - // Check that this is an array. - if (tab->u.tab.value[idx].type != TOML_ARRAY) { - return SETERROR(pp->ebuf, keylineno, "entry must be an array"); - } - // Add an empty table to the array - toml_datum_t *arr = &tab->u.tab.value[idx]; - if (arr->flag & TOML_FLAG_INLINED) { - return SETERROR(pp->ebuf, keylineno, "cannot extend a static array"); - } - toml_datum_t *pelem = arr_emplace(arr, &reason); - if (!pelem) { - return SETERROR(pp->ebuf, keylineno, "%s", reason); - } - *pelem = mkdatum_at(TOML_TABLE, keylineno, keycolno); - - // Set the last element of this array as curtab of the parser - pp->curtab = &arr->u.arr.elem[arr->u.arr.size - 1]; - assert(pp->curtab->type == TOML_TABLE); - - return 0; -} - -// Parse an expression. A toml doc is just a list of expressions. -static int parse_keyvalue_expr(parser_t *pp, token_t tok) { - // Obtain the key - int keylineno = tok.lineno; - int keycolno = tok.colno; - keypart_t keypart; - DO(parse_key(pp, tok, &keypart)); - - // match the '=' - DO(scan_key(&pp->scanner, &tok)); - if (tok.toktyp != TOK_EQUAL) { - return SETERROR(pp->ebuf, tok.lineno, "expect '='"); - } - - // Locate the last table using keypart[] - const char *reason; - toml_datum_t *tab = pp->curtab; - for (int i = 0; i < keypart.nspan - 1; i++) { - int j = tab_find(tab, keypart.span[i]); - if (j < 0) { - if (i > 0 && (tab->flag & TOML_FLAG_EXPLICIT)) { - return SETERROR( - pp->ebuf, keylineno, - "cannot extend a previously defined table using dotted expression"); - } - toml_datum_t newtab = mkdatum_at(TOML_TABLE, keylineno, keycolno); - if (tab_add(tab, keypart.span[i], newtab, &reason)) { - return SETERROR(pp->ebuf, keylineno, "%s", reason); - } - tab = &tab->u.tab.value[tab->u.tab.size - 1]; - continue; - } - toml_datum_t *value = &tab->u.tab.value[j]; - if (value->type == TOML_TABLE) { - tab = value; - continue; - } - if (value->type == TOML_ARRAY) { - return SETERROR(pp->ebuf, keylineno, - "encountered previously declared array '%s'", - keypart.span[i].ptr); - } - return SETERROR(pp->ebuf, keylineno, "cannot locate table at '%s'", - keypart.span[i].ptr); - } - - // Check for disallowed situations. - if (tab->flag & TOML_FLAG_INLINED) { - return SETERROR(pp->ebuf, keylineno, "inline table cannot be extended"); - } - if (keypart.nspan > 1 && (tab->flag & TOML_FLAG_EXPLICIT)) { - return SETERROR( - pp->ebuf, keylineno, - "cannot extend a previously defined table using dotted expression"); - } - - // Obtain the value - DO(scan_value(&pp->scanner, &tok)); - toml_datum_t newval = DATUM_ZERO; - if (parse_val(pp, tok, &newval)) { - datum_free(&newval); - return -1; - } - - // Add a new key/value for tab. - if (tab_add(tab, keypart.span[keypart.nspan - 1], newval, &reason)) { - datum_free(&newval); - return SETERROR(pp->ebuf, keylineno, "%s", reason); - } - - return 0; -} - -// Normalize a LIT/STRING/MLSTRING/LITSTRING/MLLITSTRING -// -> unescape all escaped chars -// The returned string is allocated out of pp->pool -static int parse_norm(parser_t *pp, token_t tok, span_t *ret_span) { - // Allocate a buffer to store the normalized string. Add one - // extra-byte for terminating NUL. - char *p = pool_alloc(pp->pool, tok.str.len + 1); - if (!p) { - return SETERROR(pp->ebuf, tok.lineno, "out of memory"); - } - - // Copy from token string into buffer - memcpy(p, tok.str.ptr, tok.str.len); - p[tok.str.len] = 0; // additional NUL term for safety - - ret_span->ptr = p; - ret_span->len = tok.str.len; - - switch (tok.toktyp) { - case TOK_LIT: - case TOK_LITSTRING: - case TOK_MLLITSTRING: - // no need to handle escape chars - return 0; - - case TOK_STRING: - case TOK_MLSTRING: - // need to handle escape chars - break; - - default: - return SETERROR(pp->ebuf, 0, "internal: arg must be a string"); - } - - // if there is no escape char, then done! - if (!tok.u.escp) { - return 0; // success - } - - // p points to the backslash - p += (tok.u.escp - tok.str.ptr); - assert(p - ret_span->ptr == tok.u.escp - tok.str.ptr); - assert(*p == '\\'); - - // Normalize the escaped chars - char *dst = p; - while (*p) { - if (*p != '\\') { - *dst++ = *p++; - continue; - } - switch (p[1]) { - case '"': - case '\\': - *dst++ = p[1]; - p += 2; - continue; - case 'b': - *dst++ = '\b'; - p += 2; - continue; - case 't': - *dst++ = '\t'; - p += 2; - continue; - case 'n': - *dst++ = '\n'; - p += 2; - continue; - case 'f': - *dst++ = '\f'; - p += 2; - continue; - case 'r': - *dst++ = '\r'; - p += 2; - continue; - case 'e': - *dst++ = '\033'; - p += 2; - continue; - case 'x': { - char buf[3]; - memcpy(buf, p + 2, 2); - buf[2] = 0; - // There is no need to check for two hex digits here because - // the scanner already checked it. - int32_t ucs = strtol(buf, 0, 16); - int n = ucs_to_utf8(ucs, dst); - if (n < 0) { - return SETERROR(pp->ebuf, tok.lineno, "error converting UCS %s to UTF8", - buf); - } - dst += n; - p += 2 + 2; // \xNN - continue; - } - case 'u': - case 'U': { - char buf[9]; - int sz = (p[1] == 'u' ? 4 : 8); - memcpy(buf, p + 2, sz); - buf[sz] = 0; - // There is no need to check for 4 or 8 hex digits here because - // the scanner already checked it. - int32_t ucs = strtol(buf, 0, 16); - if (0xD800 <= ucs && ucs <= 0xDFFF) { - // explicitly prohibit surrogates (non-scalar unicode code point) - return SETERROR(pp->ebuf, tok.lineno, "invalid UTF8 char \\u%04x", ucs); - } - int n = ucs_to_utf8(ucs, dst); - if (n < 0) { - return SETERROR(pp->ebuf, tok.lineno, "error converting UCS %s to UTF8", - buf); - } - dst += n; - p += 2 + sz; // \uNNNN or \UNNNNNNNN - continue; - } - - case ' ': - case '\t': - case '\r': - // line-ending backslash - // --- allow for extra whitespace chars after backslash - // --- skip until newline - p++; // skip the escape char - p += strspn(p, " \t\r"); // skip whitespaces - if (*p != '\n') { - return SETERROR(pp->ebuf, tok.lineno, - "unexpected char after line-ending backslash"); - } - // fallthru - case '\n': - // skip all whitespaces including newline - p++; - p += strspn(p, " \t\r\n"); - continue; - default: - return SETERROR(pp->ebuf, tok.lineno, - "internal: unknown escape char \\%c", p[1]); - } - } - *dst = 0; - ret_span->len = dst - ret_span->ptr; - return 0; -} - -// =================================================================== -// == SCANNER SECTION -// =================================================================== - -// Get the next char -static int scan_get(scanner_t *sp) { - int ret = TOK_FIN; - const char *p = sp->cur; - if (p < sp->endp) { - ret = *p++; - if (ret == '\r' && p < sp->endp && *p == '\n') { - ret = *p++; - } - } - sp->cur = p; - if (ret == '\n') { - sp->lineno += 1; - sp->line_start = p; - } - return ret; -} - -// Check if the next char matches ch. -static inline bool scan_match(scanner_t *sp, int ch) { - const char *p = sp->cur; - // exact match? done. - if (p < sp->endp && *p == ch) { - return true; - } - // \n also matches \r\n - if (ch == '\n' && p + 1 < sp->endp) { - return p[0] == '\r' && p[1] == '\n'; - } - // not a match - return false; -} - -// Check if the next char is in accept[]. -static bool scan_matchany(scanner_t *sp, const char *accept) { - for (; *accept; accept++) { - if (scan_match(sp, *accept)) { - return true; - } - } - return false; -} - -// Check if the next n chars match ch. -static inline bool scan_nmatch(scanner_t *sp, int ch, int n) { - assert(ch != '\n'); // not handled - if (sp->cur + n > sp->endp) { - return false; - } - const char *p = sp->cur; - int i; - for (i = 0; i < n && p[i] == ch; i++) - ; - return i == n; -} - -// Initialize a token. -static inline token_t mktoken(scanner_t *sp, toktyp_t typ) { - token_t tok = {0}; - tok.toktyp = typ; - tok.str.ptr = sp->cur; - tok.lineno = sp->lineno; - tok.colno = (int)(sp->cur - sp->line_start) + 1; - return tok; -} - -#define S_GET() scan_get(sp) -#define S_MATCH(ch) scan_match(sp, (ch)) -#define S_MATCH3(ch) scan_nmatch(sp, (ch), 3) -#define S_MATCH4(ch) scan_nmatch(sp, (ch), 4) -#define S_MATCH6(ch) scan_nmatch(sp, (ch), 6) - -static inline bool is_valid_char(int ch) { - // i.e. (0x20 <= ch && ch <= 0x7e) || (ch & 0x80); - return isprint((unsigned char)ch) || (ch & 0x80); -} - -static inline bool is_hex_char(int ch) { - ch = toupper((unsigned char)ch); - return ('0' <= ch && ch <= '9') || ('A' <= ch && ch <= 'F'); -} - -// Initialize a scanner -static void scan_init(scanner_t *sp, const char *src, int len, char *errbuf, - int errbufsz) { - memset(sp, 0, sizeof(*sp)); - sp->src = src; - sp->endp = src + len; - sp->cur = src; - sp->lineno = 1; - sp->line_start = src; - sp->ebuf.ptr = errbuf; - sp->ebuf.len = errbufsz; -} - -// Scan escape sequence characters after a backslash. -// Return #char match on success, 0 if no match, -1 on error. -static int scan_escape_chars(scanner_t *sp) { - const char *p = sp->cur; - if (p >= sp->endp) { - return 0; - } - int ch = *p++; - if (ch && strchr("btnfre\"\\", ch)) { - return p - sp->cur; - } - int hex = (ch == 'x') ? 2 : (ch == 'u') ? 4 : (ch == 'U') ? 8 : 0; - if (hex) { - int i = 0; - for (; i < hex && p < sp->endp && is_hex_char(*p); i++, p++) - ; - if (i != hex) { - return SETERROR(sp->ebuf, sp->lineno, "expect %d hex digits after \\%c", - hex, ch); - } - return p - sp->cur; - } - return 0; -} - -// Scan """ ... """ -static int scan_multiline_string(scanner_t *sp, token_t *tok) { - assert(S_MATCH3('"')); - S_GET(), S_GET(), S_GET(); // skip opening """ - - // According to spec: trim first newline after """ - if (S_MATCH('\n')) { - S_GET(); - } - - *tok = mktoken(sp, TOK_MLSTRING); - // scan until terminating """ - const char *escp = NULL; - while (1) { - if (S_MATCH3('"')) { - if (S_MATCH4('"')) { - // special case... """abcd """" -> (abcd ") - // but sequences of 3 or more double quotes are not allowed - if (S_MATCH6('"')) { - return SETERROR(sp->ebuf, sp->lineno, - "detected sequences of 3 or more double quotes"); - } else { - ; // no problem - } - } else { - break; // found terminating """ - } - } - int ch = S_GET(); - if (ch == TOK_FIN) { - return SETERROR(sp->ebuf, sp->lineno, "unterminated \"\"\""); - } - // If non-escaped char ... - if (ch != '\\') { - if (!(is_valid_char(ch) || (ch && strchr(" \t\n", ch)))) { - return SETERROR(sp->ebuf, sp->lineno, "invalid char in string"); - } - continue; - } - // ch is backslash - if (!escp) { - escp = sp->cur - 1; /* mark the first esc position */ - assert(*escp == '\\'); - } - - // handle escape char - int cnt = scan_escape_chars(sp); - if (cnt < 0) { - return -1; - } - if (cnt > 0) { - sp->cur += cnt; // skip the escape sequence - continue; - } - assert(cnt == 0); - - // handle line-ending backslash - ch = S_GET(); - if (ch == ' ' || ch == '\t') { - // Although the spec does not allow for whitespace following a - // line-ending backslash, some standard tests expect it. - // Skip whitespace till EOL. - while (ch != TOK_FIN && ch && strchr(" \t", ch)) { - ch = S_GET(); - } - if (ch != '\n') { - // Got a backslash followed by whitespace, followed by some char - // before newline - return SETERROR(sp->ebuf, sp->lineno, "bad escape char in string"); - } - // fallthru - } - if (ch == '\n') { - // got a line-ending backslash - // - skip all whitespaces - while (scan_matchany(sp, " \t\n")) { - S_GET(); - } - continue; - } - return SETERROR(sp->ebuf, sp->lineno, "bad escape char in string"); - } - tok->str.len = sp->cur - tok->str.ptr; - tok->u.escp = escp; - - assert(S_MATCH3('"')); - S_GET(), S_GET(), S_GET(); - return 0; -} - -// Scan " ... " -static int scan_string(scanner_t *sp, token_t *tok) { - assert(S_MATCH('"')); - if (S_MATCH3('"')) { - return scan_multiline_string(sp, tok); - } - S_GET(); // skip opening " - - // scan until closing " - *tok = mktoken(sp, TOK_STRING); - const char *escp = NULL; - while (!S_MATCH('"')) { - int ch = S_GET(); - if (ch == TOK_FIN) { - return SETERROR(sp->ebuf, sp->lineno, "unterminated string"); - } - // If non-escaped char ... - if (ch != '\\') { - if (!(is_valid_char(ch) || ch == ' ' || ch == '\t')) { - return SETERROR(sp->ebuf, sp->lineno, "invalid char in string"); - } - continue; - } - // ch is backslash - if (!escp) { - escp = sp->cur - 1; - assert(*escp == '\\'); - } - - // handle escape char - int cnt = scan_escape_chars(sp); - if (cnt < 0) { - return -1; - } - if (cnt > 0) { - sp->cur += cnt; - continue; - } - return SETERROR(sp->ebuf, sp->lineno, "bad escape char in string"); - } - tok->str.len = sp->cur - tok->str.ptr; - tok->u.escp = escp; - - assert(S_MATCH('"')); - S_GET(); // skip the terminating " - return 0; -} - -// Scan ''' ... ''' -static int scan_multiline_litstring(scanner_t *sp, token_t *tok) { - assert(S_MATCH3('\'')); - S_GET(), S_GET(), S_GET(); // skip opening ''' - - // According to spec: trim first newline after ''' - if (S_MATCH('\n')) { - S_GET(); - } - - // scan until terminating ''' - *tok = mktoken(sp, TOK_MLLITSTRING); - while (1) { - if (S_MATCH3('\'')) { - if (S_MATCH4('\'')) { - // special case... '''abcd '''' -> (abcd ') - // but sequences of 3 or more single quotes are not allowed - if (S_MATCH6('\'')) { - return SETERROR(sp->ebuf, sp->lineno, - "sequences of 3 or more single quotes"); - } else { - ; // no problem - } - } else { - break; // found terminating ''' - } - } - int ch = S_GET(); - if (ch == TOK_FIN) { - return SETERROR(sp->ebuf, sp->lineno, - "unterminated multiline lit string"); - } - if (!(is_valid_char(ch) || (ch && strchr(" \t\n", ch)))) { - return SETERROR(sp->ebuf, sp->lineno, "invalid char in string"); - } - } - tok->str.len = sp->cur - tok->str.ptr; - - assert(S_MATCH3('\'')); - S_GET(), S_GET(), S_GET(); - return 0; -} - -// Scan ' ... ' -static int scan_litstring(scanner_t *sp, token_t *tok) { - assert(S_MATCH('\'')); - if (S_MATCH3('\'')) { - return scan_multiline_litstring(sp, tok); - } - S_GET(); // skip opening ' - - // scan until closing ' - *tok = mktoken(sp, TOK_LITSTRING); - while (!S_MATCH('\'')) { - int ch = S_GET(); - if (ch == TOK_FIN) { - return SETERROR(sp->ebuf, sp->lineno, "unterminated string"); - } - if (!(is_valid_char(ch) || ch == '\t')) { - return SETERROR(sp->ebuf, sp->lineno, "invalid char in string"); - } - } - tok->str.len = sp->cur - tok->str.ptr; - assert(S_MATCH('\'')); - S_GET(); - return 0; -} - -static bool is_valid_date(int year, int month, int day) { - if (!(1 <= year)) { - return false; - } - if (!(1 <= month && month <= 12)) { - return false; - } - int is_leap_year = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); - int days_in_month[] = { - 31, 28 + is_leap_year, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - return (1 <= day && day <= days_in_month[month - 1]); -} - -static bool is_valid_time(int hour, int minute, int sec, int usec) { - if (!(0 <= hour && hour <= 23)) { - return false; - } - if (!(0 <= minute && minute <= 59)) { - return false; - } - // RFC 3339 permits sec == 60 for leap seconds; reject it deliberately. - if (!(0 <= sec && sec <= 59)) { - return false; - } - if (!(0 <= usec)) { - return false; - } - return true; -} - -static bool is_valid_timezone(int minute) { - minute = (minute < 0 ? -minute : minute); - int hour = minute / 60; - minute = minute % 60; - if (!(0 <= hour && hour <= 23)) { - return false; - } - if (!(0 <= minute && minute < 60)) { - return false; - } - return true; -} - -// Read an int (without signs) from the string p. -static int read_int(const char *p, int *ret) { - const char *pp = p; - int64_t val = 0; - for (; isdigit((unsigned char)*p); p++) { - val = val * 10 + (*p - '0'); - if (val > INT_MAX) { - return 0; // overflowed - } - } - *ret = (int)val; - return p - pp; -} - -// Read a date as YYYY-MM-DD from p[]. Return #bytes consumed. -static int read_date(const char *p, int *year, int *month, int *day) { - const char *pp = p; - int n; - n = read_int(p, year); - if (n != 4 || p[4] != '-') { - return 0; - } - n = read_int(p += n + 1, month); - if (n != 2 || p[2] != '-') { - return 0; - } - n = read_int(p += n + 1, day); - if (n != 2) { - return 0; - } - p += 2; - assert(p - pp == 10); - return p - pp; -} - -// Read a time as HH:MM:SS.subsec from p[]. Return #bytes consumed. -static int read_time(const char *p, int *hour, int *minute, int *second, - int *usec) { - const char *pp = p; - int n; - *hour = *minute = *second = *usec = 0; - // scan hours - n = read_int(p, hour); - if (n != 2 || p[2] != ':') { - return 0; - } - p += 3; - - // scan minutes - n = read_int(p, minute); - if (n != 2) { - return 0; - } - if (p[2] != ':') { - // seconds are optional in v1.1 - p += 2; - return p - pp; - } - p += 3; - - // scan seconds - n = read_int(p, second); - if (n != 2) { - return 0; - } - p += 2; - - if (*p != '.') { - return p - pp; - } - p++; // skip the period - if (!isdigit((unsigned char)*p)) { - // trailing period - return 0; - } - int micro_factor = 100000; - while (isdigit((unsigned char)*p) && micro_factor) { - *usec += (*p - '0') * micro_factor; - micro_factor /= 10; - p++; - } - while (isdigit((unsigned char)*p)) - p++; // consume extra sub-microsecond digits - return p - pp; -} - -// Reads a timezone from p[]. Return #bytes consumed. -// tzhours and tzminutes restricted to 2-char integers only. -static int read_tzone(const char *p, char *tzsign, int *tzhour, int *tzminute) { - const char *pp = p; - - // Default values - *tzhour = *tzminute = 0; - *tzsign = '+'; - - // Look for Zulu - if (*p == 'Z' || *p == 'z') { - return 1; // done! tz is +00:00. - } - - // Look for +/- - *tzsign = *p++; - if (!(*tzsign == '+' || *tzsign == '-')) { - return 0; - } - - // Look for HH:MM - int n; - n = read_int(p, tzhour); - if (n != 2 || p[2] != ':') { - return 0; - } - n = read_int(p += 3, tzminute); - if (n != 2) { - return 0; - } - p += 2; - return p - pp; -} - -// Scan hh:mm:ss.xxxxx -static int scan_time(scanner_t *sp, token_t *tok) { - int lineno = sp->lineno; - char buffer[LITBUF_SIZE]; - scan_copystr(sp, buffer, sizeof(buffer)); - - char *p = buffer; - int hour, minute, sec, usec; - int len = read_time(p, &hour, &minute, &sec, &usec); - if (len == 0) { - return SETERROR(sp->ebuf, lineno, "invalid time"); - } - if (len + 1 >= (int)sizeof(buffer)) { - return SETERROR(sp->ebuf, lineno, "timestamp too long"); - } - if (!is_valid_time(hour, minute, sec, usec)) { - return SETERROR(sp->ebuf, lineno, "invalid time"); - } - - *tok = mktoken(sp, TOK_TIME); - tok->str.len = len; - sp->cur += len; - tok->u.tsval.year = -1; - tok->u.tsval.month = -1; - tok->u.tsval.day = -1; - tok->u.tsval.hour = hour; - tok->u.tsval.minute = minute; - tok->u.tsval.sec = sec; - tok->u.tsval.usec = usec; - tok->u.tsval.tz = -1; - return 0; -} - -// Scan a time, a date, a datetime, or a datatimetz -static int scan_timestamp(scanner_t *sp, token_t *tok) { - int year, month, day, hour, minute, sec, usec, tz; - year = month = day = hour = minute = sec = usec = tz = -1; - - int n; - // make a copy of sp->cur into buffer to ensure NUL terminated string - char buffer[LITBUF_SIZE]; - scan_copystr(sp, buffer, sizeof(buffer)); - - toktyp_t toktyp = TOK_FIN; - int lineno = sp->lineno; - - // See if this a TIME only - const char *p = buffer; - if (isdigit((unsigned char)p[0]) && isdigit((unsigned char)p[1]) && - p[2] == ':') { - n = read_time(buffer, &hour, &minute, &sec, &usec); - if (!n) { - return SETERROR(sp->ebuf, lineno, "invalid time"); - } - toktyp = TOK_TIME; - p += n; - goto done; - } - - // Try reading a DATE - n = read_date(p, &year, &month, &day); - if (!n) { - return SETERROR(sp->ebuf, lineno, "invalid date"); - } - toktyp = TOK_DATE; - p += n; - - // Check if there is no time component in addition - if (!((p[0] == 'T' || p[0] == ' ' || p[0] == 't') && - isdigit((unsigned char)p[1]) && isdigit((unsigned char)p[2]) && - p[3] == ':')) { - goto done; // no TIME component. we are done. - } - - // Read the TIME - n = read_time(p += 1, &hour, &minute, &sec, &usec); - if (!n) { - return SETERROR(sp->ebuf, lineno, "invalid timestamp"); - } - toktyp = TOK_DATETIME; - p += n; - - // Read the (optional) timezone - char tzsign; - int tzhour, tzminute; - n = read_tzone(p, &tzsign, &tzhour, &tzminute); - if (n == 0) { - goto done; // datetime only - } - toktyp = TOK_DATETIMETZ; - p += n; - - // Check tzminute range. This must be done here instead of is_valid_timezone() - // because we combine tzhour and tzminute into tz (by minutes only). - if (!(0 <= tzminute && tzminute < 60)) { - return SETERROR(sp->ebuf, lineno, "invalid timezone"); - } - tz = (tzhour * 60 + tzminute) * (tzsign == '-' ? -1 : 1); - goto done; // datetimetz - -done: - *tok = mktoken(sp, toktyp); - n = p - buffer; - if (n + 1 >= (int)sizeof(buffer)) { - return SETERROR(sp->ebuf, lineno, "timestamp too long"); - } - tok->str.len = n; - sp->cur += n; - - tok->u.tsval.year = year; - tok->u.tsval.month = month; - tok->u.tsval.day = day; - tok->u.tsval.hour = hour; - tok->u.tsval.minute = minute; - tok->u.tsval.sec = sec; - tok->u.tsval.usec = usec; - tok->u.tsval.tz = tz; - - // Do some error checks based on type - switch (tok->toktyp) { - case TOK_TIME: - if (!is_valid_time(hour, minute, sec, usec)) { - return SETERROR(sp->ebuf, lineno, "invalid time"); - } - break; - case TOK_DATE: - if (!is_valid_date(year, month, day)) { - return SETERROR(sp->ebuf, lineno, "invalid date"); - } - break; - case TOK_DATETIME: - case TOK_DATETIMETZ: - if (!is_valid_date(year, month, day)) { - return SETERROR(sp->ebuf, lineno, "invalid date"); - } - if (!is_valid_time(hour, minute, sec, usec)) { - return SETERROR(sp->ebuf, lineno, "invalid time"); - } - if (tok->toktyp == TOK_DATETIMETZ && !is_valid_timezone(tz)) { - return SETERROR(sp->ebuf, lineno, "invalid timezone"); - } - break; - default: - assert(0); - return SETERROR(sp->ebuf, lineno, "internal error"); - } - - return 0; -} - -// Given a toml number (int and float) in buffer[]: -// 1. squeeze out '_' -// 2. check for syntax restrictions -static int process_numstr(char *buffer, int base, const char **reason) { - // squeeze out _ - char *q = strchr(buffer, '_'); - if (q) { - for (int i = q - buffer; buffer[i]; i++) { - if (buffer[i] != '_') { - *q++ = buffer[i]; - continue; - } - int left = (i == 0) ? 0 : (unsigned char)buffer[i - 1]; - int right = (unsigned char)buffer[i + 1]; - if (!isdigit(left) && !(base == 16 && is_hex_char(left))) { - *reason = "underscore only allowed between digits"; - return -1; - } - if (!isdigit(right) && !(base == 16 && is_hex_char(right))) { - *reason = "underscore only allowed between digits"; - return -1; - } - } - *q = 0; - } - - // decimal points must be surrounded by digits. Also, convert to lowercase. - for (int i = 0; buffer[i]; i++) { - if (buffer[i] == '.') { - if (i == 0 || !isdigit((unsigned char)buffer[i - 1]) || - !isdigit((unsigned char)buffer[i + 1])) { - *reason = "decimal point must be surrounded by digits"; - return -1; - } - } else if ('A' <= buffer[i] && buffer[i] <= 'Z') { - buffer[i] = tolower((unsigned char)buffer[i]); - } - } - - if (base == 10) { - // check for leading 0: '+01' is an error! - q = buffer; - q += (*q == '+' || *q == '-') ? 1 : 0; - if (q[0] == '0' && isdigit((unsigned char)q[1])) { - *reason = "leading 0 in numbers"; - return -1; - } - } - - return 0; -} - -static int scan_float(scanner_t *sp, token_t *tok) { - char buffer[LITBUF_SIZE]; - scan_copystr(sp, buffer, sizeof(buffer)); - - int lineno = sp->lineno; - char *p = buffer; - p += (*p == '+' || *p == '-') ? 1 : 0; - if (0 == memcmp(p, "nan", 3) || (0 == memcmp(p, "inf", 3))) { - p += 3; - } else { - p += strspn(p, "_0123456789eE.+-"); - } - int len = p - buffer; - if (len + 1 >= (int)sizeof(buffer)) { - return SETERROR(sp->ebuf, lineno, "number too long"); - } - buffer[len] = 0; - - const char *reason; - if (process_numstr(buffer, 10, &reason)) { - return SETERROR(sp->ebuf, lineno, "%s", reason); - } - - errno = 0; - char *q; - double fp64 = strtod(buffer, &q); - // glibc sets ERANGE on underflow even when strtod's result is correctly - // rounded, e.g. 5e-324; accept such results, but still reject a value that - // underflowed to zero or overflowed to infinity. Test the raw bits rather - // than "fp64 != 0.0" and isfinite(): if the calling program enables - // denormals-are-zero (e.g. an app linked with -ffast-math, or built with - // Intel icx), an FP compare reads a subnormal as 0.0 (issue #49). - static_assert(sizeof(fp64) == sizeof(uint64_t), "double must be 64 bits"); - uint64_t fp64_bits; - memcpy(&fp64_bits, &fp64, sizeof(fp64)); - uint64_t fp64_mag = fp64_bits & 0x7fffffffffffffffULL; // drop the sign bit - int is_ok_subnormal = - (errno == ERANGE) && fp64_mag != 0 && fp64_mag < 0x7ff0000000000000ULL; - if ((errno && !is_ok_subnormal) || *q || q == buffer) { - return SETERROR(sp->ebuf, lineno, "error parsing float"); - } - - *tok = mktoken(sp, TOK_FLOAT); - tok->u.fp64 = fp64; - tok->str.len = len; - sp->cur += len; - return 0; -} - -static int scan_number(scanner_t *sp, token_t *tok) { - const char *reason; - char buffer[LITBUF_SIZE]; - scan_copystr(sp, buffer, sizeof(buffer)); - - char *p = buffer; - int lineno = sp->lineno; - // process %0x, %0o or %0b integers - if (p[0] == '0') { - const char *span = 0; - int base = 0; - switch (p[1]) { - case 'x': - base = 16; - span = "_0123456789abcdefABCDEF"; - break; - case 'o': - base = 8; - span = "_01234567"; - break; - case 'b': - base = 2; - span = "_01"; - break; - } - if (base) { - p += 2; - p += strspn(p, span); - int len = p - buffer; - if (len + 1 >= (int)sizeof(buffer)) { - return SETERROR(sp->ebuf, lineno, "number too long"); - } - buffer[len] = 0; - - if (process_numstr(buffer + 2, base, &reason)) { - return SETERROR(sp->ebuf, lineno, "%s", reason); - } - - // use strtoll to obtain the value - *tok = mktoken(sp, TOK_INTEGER); - char *q; - errno = 0; - tok->u.int64 = strtoll(buffer + 2, &q, base); - if (errno || *q || q == buffer + 2) { - return SETERROR(sp->ebuf, lineno, "error parsing integer"); - } - tok->str.len = len; - sp->cur += len; - return 0; - } - } - - // handle inf/nan - if (*p == '+' || *p == '-') { - p++; - } - if (*p == 'i' || *p == 'n') { - return scan_float(sp, tok); - } - - // regular int or float - p = buffer; - p += strspn(p, "0123456789_+-.eE"); - int len = p - buffer; - if (len + 1 >= (int)sizeof(buffer)) { - return SETERROR(sp->ebuf, lineno, "number too long"); - } - buffer[len] = 0; - - if (process_numstr(buffer, 10, &reason)) { - return SETERROR(sp->ebuf, lineno, "%s", reason); - } - - *tok = mktoken(sp, TOK_INTEGER); - char *q; - errno = 0; - tok->u.int64 = strtoll(buffer, &q, 10); - if (errno || *q || q == buffer) { - if (*q && strchr(".eE", *q)) { - return scan_float(sp, tok); // try to fit a float - } - return SETERROR(sp->ebuf, lineno, "error parsing integer"); - } - - tok->str.len = len; - sp->cur += len; - return 0; -} - -static int scan_bool(scanner_t *sp, token_t *tok) { - char buffer[10]; - scan_copystr(sp, buffer, sizeof(buffer)); - - int lineno = sp->lineno; - bool val = false; - const char *p = buffer; - if (0 == strncmp(p, "true", 4)) { - val = true; - p += 4; - } else if (0 == strncmp(p, "false", 5)) { - val = false; - p += 5; - } else { - return SETERROR(sp->ebuf, lineno, "invalid boolean value"); - } - if (*p && !strchr("# \r\n\t,}]", *p)) { - return SETERROR(sp->ebuf, lineno, "invalid boolean value"); - } - - int len = p - buffer; - *tok = mktoken(sp, TOK_BOOL); - tok->u.b1 = val; - tok->str.len = len; - sp->cur += len; - return 0; -} - -// Check if the next token may be TIME -static inline bool test_time(const char *p, const char *endp) { - return &p[2] < endp && isdigit((unsigned char)p[0]) && - isdigit((unsigned char)p[1]) && p[2] == ':'; -} - -// Check if the next token may be DATE -static inline bool test_date(const char *p, const char *endp) { - return &p[4] < endp && isdigit((unsigned char)p[0]) && - isdigit((unsigned char)p[1]) && isdigit((unsigned char)p[2]) && - isdigit((unsigned char)p[3]) && p[4] == '-'; -} - -// Check if the next token may be BOOL -static inline bool test_bool(const char *p, const char *endp) { - return &p[0] < endp && (*p == 't' || *p == 'f'); -} - -// Check if the next token may be NUMBER -static bool test_number(const char *p, const char *endp) { - if (&p[0] < endp && *p && strchr("0123456789+-._", *p)) { - return true; - } - if (&p[2] < endp) { - if (0 == memcmp(p, "nan", 3) || 0 == memcmp(p, "inf", 3)) { - return true; - } - } - return false; -} - -// Scan a literal that is not a string -static int scan_nonstring_literal(scanner_t *sp, token_t *tok) { - int lineno = sp->lineno; - if (test_time(sp->cur, sp->endp)) { - return scan_time(sp, tok); - } - - if (test_date(sp->cur, sp->endp)) { - return scan_timestamp(sp, tok); - } - - if (test_bool(sp->cur, sp->endp)) { - return scan_bool(sp, tok); - } - - if (test_number(sp->cur, sp->endp)) { - return scan_number(sp, tok); - } - return SETERROR(sp->ebuf, lineno, "invalid value"); -} - -// Return true if Unicode codepoint is allowed in a TOML 1.1 bare key. -// Ranges taken verbatim from the TOML 1.1 spec grammar for bare-key-char. -static bool is_unicode_bare_key_char(uint32_t cp) { - if (cp == 0xB2 || cp == 0xB3 || cp == 0xB9) - return true; - if (0xBC <= cp && cp <= 0xBE) - return true; - if (0xC0 <= cp && cp <= 0xD6) - return true; - if (0xD8 <= cp && cp <= 0xF6) - return true; - if (0xF8 <= cp && cp <= 0x37D) - return true; - if (0x37F <= cp && cp <= 0x1FFF) - return true; - if (cp == 0x200C || cp == 0x200D) - return true; - if (0x203F <= cp && cp <= 0x2040) - return true; - if (0x2070 <= cp && cp <= 0x218F) - return true; - if (0x2460 <= cp && cp <= 0x24FF) - return true; - if (0x2C00 <= cp && cp <= 0x2FEF) - return true; - if (0x3001 <= cp && cp <= 0xD7FF) - return true; - if (0xF900 <= cp && cp <= 0xFDCF) - return true; - if (0xFDF0 <= cp && cp <= 0xFFFD) - return cp != 0xFEFF; // BOM: stripped when leading, an error anywhere else - if (0x10000 <= cp && cp <= 0xEFFFF) - return true; - return false; -} - -// Scan a literal -static int scan_literal(scanner_t *sp, token_t *tok) { - *tok = mktoken(sp, TOK_LIT); - const char *p = sp->cur; - while (p < sp->endp) { - if (isalnum((unsigned char)*p) || *p == '_' || *p == '-') { - p++; - continue; - } - if ((unsigned char)*p >= 0x80) { - uint32_t cp; - int n = utf8_to_ucs(p, sp->endp - p, &cp); - if (n > 0 && is_unicode_bare_key_char(cp)) { - p += n; - continue; - } - } - break; - } - tok->str.len = p - tok->str.ptr; - sp->cur = p; - return 0; -} - -// Save the current state of the scanner -static scanner_state_t scan_mark(scanner_t *sp) { - scanner_state_t mark; - mark.sp = sp; - mark.cur = sp->cur; - mark.lineno = sp->lineno; - mark.line_start = sp->line_start; - return mark; -} - -// Restore the scanner state to a previously saved state -static void scan_restore(scanner_t *sp, scanner_state_t mark) { - assert(mark.sp == sp); - sp->cur = mark.cur; - sp->lineno = mark.lineno; - sp->line_start = mark.line_start; -} - -// Return the next token -static int scan_next(scanner_t *sp, bool keymode, token_t *tok) { - static const toktyp_t map[128] = { - ['\n'] = TOK_ENDL, ['.'] = TOK_DOT, ['='] = TOK_EQUAL, - [','] = TOK_COMMA, ['{'] = TOK_LBRACE, ['}'] = TOK_RBRACE}; -again: - *tok = mktoken(sp, TOK_FIN); - - int ch = S_GET(); - if (ch == TOK_FIN) { - return 0; - } - - tok->str.len = 1; - if (0 <= ch && ch < 128 && map[ch]) { - // map simple char to token type and done - tok->toktyp = map[ch]; - return 0; - } - - // handle char that require logic - switch (ch) { - case ' ': - case '\t': - goto again; // skip whitespace - - case '#': - // comment: skip until newline - while (!S_MATCH('\n')) { - ch = S_GET(); - if (ch == TOK_FIN) - break; - if ((0 <= ch && ch <= 0x8) || (0x0a <= ch && ch <= 0x1f) || - (ch == 0x7f)) { - return SETERROR(sp->ebuf, sp->lineno, "bad control char in comment"); - } - } - goto again; // skip comment - - case '[': - tok->toktyp = TOK_LBRACK; - if (keymode && S_MATCH('[')) { - S_GET(); - tok->toktyp = TOK_LLBRACK; - tok->str.len = 2; - } - break; - - case ']': - tok->toktyp = TOK_RBRACK; - if (keymode && S_MATCH(']')) { - S_GET(); - tok->toktyp = TOK_RRBRACK; - tok->str.len = 2; - } - break; - - case '"': - sp->cur--; - DO(scan_string(sp, tok)); - break; - - case '\'': - sp->cur--; - DO(scan_litstring(sp, tok)); - break; - - default: - sp->cur--; - DO(keymode ? scan_literal(sp, tok) : scan_nonstring_literal(sp, tok)); - break; - } - - return 0; -} - -// Check for stack overflow due to excessive number of brackets or braces -static int check_overflow(scanner_t *sp, token_t *tok) { - switch (tok->toktyp) { - case TOK_LBRACK: - sp->bracket_level++; - if (sp->bracket_level > BRACKET_LEVEL_MAX) { - return SETERROR(sp->ebuf, sp->lineno, "stack overflow"); - } - break; - case TOK_RBRACK: - sp->bracket_level--; - break; - case TOK_LBRACE: - sp->brace_level++; - if (sp->brace_level > BRACE_LEVEL_MAX) { - return SETERROR(sp->ebuf, sp->lineno, "stack overflow"); - } - break; - case TOK_RBRACE: - sp->brace_level--; - break; - default: - break; - } - return 0; -} - -static int scan_key(scanner_t *sp, token_t *tok) { - if (sp->errmsg) { - return -1; - } - if (scan_next(sp, true, tok) || check_overflow(sp, tok)) { - sp->errmsg = sp->ebuf.ptr; - return -1; - } - return 0; -} - -static int scan_value(scanner_t *sp, token_t *tok) { - if (sp->errmsg) { - return -1; - } - if (scan_next(sp, false, tok) || check_overflow(sp, tok)) { - sp->errmsg = sp->ebuf.ptr; - return -1; - } - return 0; -} - -/** - * Convert a char in utf8 into UCS, and store it in *ret. - * Return #bytes consumed or -1 on failure. - */ -static int utf8_to_ucs(const char *orig, int len, uint32_t *ret) { - const unsigned char *buf = (const unsigned char *)orig; - unsigned i = *buf++; - uint32_t v; - - /* 0x00000000 - 0x0000007F: - 0xxxxxxx - */ - if (0 == (i >> 7)) { - if (len < 1) - return -1; - v = i; - return *ret = v, 1; - } - /* 0x00000080 - 0x000007FF: - 110xxxxx 10xxxxxx - */ - if (0x6 == (i >> 5)) { - if (len < 2) - return -1; - v = i & 0x1f; - for (int j = 0; j < 1; j++) { - i = *buf++; - if (0x2 != (i >> 6)) - return -1; - v = (v << 6) | (i & 0x3f); - } - return *ret = v, (const char *)buf - orig; - } - - /* 0x00000800 - 0x0000FFFF: - 1110xxxx 10xxxxxx 10xxxxxx - */ - if (0xE == (i >> 4)) { - if (len < 3) - return -1; - v = i & 0x0F; - for (int j = 0; j < 2; j++) { - i = *buf++; - if (0x2 != (i >> 6)) - return -1; - v = (v << 6) | (i & 0x3f); - } - return *ret = v, (const char *)buf - orig; - } - - /* 0x00010000 - 0x001FFFFF: - 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - */ - if (0x1E == (i >> 3)) { - if (len < 4) - return -1; - v = i & 0x07; - for (int j = 0; j < 3; j++) { - i = *buf++; - if (0x2 != (i >> 6)) - return -1; - v = (v << 6) | (i & 0x3f); - } - return *ret = v, (const char *)buf - orig; - } - - if (0) { - // NOTE: these code points taking more than 4 bytes are not supported - - /* 0x00200000 - 0x03FFFFFF: - 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx - */ - if (0x3E == (i >> 2)) { - if (len < 5) - return -1; - v = i & 0x03; - for (int j = 0; j < 4; j++) { - i = *buf++; - if (0x2 != (i >> 6)) - return -1; - v = (v << 6) | (i & 0x3f); - } - return *ret = v, (const char *)buf - orig; - } - - /* 0x04000000 - 0x7FFFFFFF: - 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx - */ - if (0x7e == (i >> 1)) { - if (len < 6) - return -1; - v = i & 0x01; - for (int j = 0; j < 5; j++) { - i = *buf++; - if (0x2 != (i >> 6)) - return -1; - v = (v << 6) | (i & 0x3f); - } - return *ret = v, (const char *)buf - orig; - } - } - - return -1; -} - -/** - * Convert a UCS char to utf8 code, and return it in buf. - * Return #bytes used in buf to encode the char, or - * -1 on error. - */ -static int ucs_to_utf8(uint32_t code, char buf[4]) { - /* http://stackoverflow.com/questions/6240055/manually-converting-unicode-codepoints-into-utf-8-and-utf-16 - */ - /* The UCS code values 0xd800–0xdfff (UTF-16 surrogates) as well - * as 0xfffe and 0xffff (UCS noncharacters) should not appear in - * conforming UTF-8 streams. - */ - /* - * https://github.com/toml-lang/toml-test/issues/165 - * [0xd800, 0xdfff] and [0xfffe, 0xffff] are implicitly allowed by TOML, so - * we disable the check. - */ - if (0) { - if (0xd800 <= code && code <= 0xdfff) - return -1; - if (0xfffe <= code && code <= 0xffff) - return -1; - } - - /* 0x00000000 - 0x0000007F: - 0xxxxxxx - */ - if (code <= 0x7F) { - buf[0] = (unsigned char)code; - return 1; - } - - /* 0x00000080 - 0x000007FF: - 110xxxxx 10xxxxxx - */ - if (code <= 0x000007FF) { - buf[0] = (unsigned char)(0xc0 | (code >> 6)); - buf[1] = (unsigned char)(0x80 | (code & 0x3f)); - return 2; - } - - /* 0x00000800 - 0x0000FFFF: - 1110xxxx 10xxxxxx 10xxxxxx - */ - if (code <= 0x0000FFFF) { - buf[0] = (unsigned char)(0xe0 | (code >> 12)); - buf[1] = (unsigned char)(0x80 | ((code >> 6) & 0x3f)); - buf[2] = (unsigned char)(0x80 | (code & 0x3f)); - return 3; - } - - /* 0x00010000 - 0x001FFFFF: - 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - */ - if (code <= 0x001FFFFF) { - buf[0] = (unsigned char)(0xf0 | (code >> 18)); - buf[1] = (unsigned char)(0x80 | ((code >> 12) & 0x3f)); - buf[2] = (unsigned char)(0x80 | ((code >> 6) & 0x3f)); - buf[3] = (unsigned char)(0x80 | (code & 0x3f)); - return 4; - } - -#ifdef UNDEF - if (0) { - // NOTE: these code points taking more than 4 bytes are not supported - /* 0x00200000 - 0x03FFFFFF: - 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx - */ - if (code <= 0x03FFFFFF) { - buf[0] = (unsigned char)(0xf8 | (code >> 24)); - buf[1] = (unsigned char)(0x80 | ((code >> 18) & 0x3f)); - buf[2] = (unsigned char)(0x80 | ((code >> 12) & 0x3f)); - buf[3] = (unsigned char)(0x80 | ((code >> 6) & 0x3f)); - buf[4] = (unsigned char)(0x80 | (code & 0x3f)); - return 5; - } - - /* 0x04000000 - 0x7FFFFFFF: - 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx - */ - if (code <= 0x7FFFFFFF) { - buf[0] = (unsigned char)(0xfc | (code >> 30)); - buf[1] = (unsigned char)(0x80 | ((code >> 24) & 0x3f)); - buf[2] = (unsigned char)(0x80 | ((code >> 18) & 0x3f)); - buf[3] = (unsigned char)(0x80 | ((code >> 12) & 0x3f)); - buf[4] = (unsigned char)(0x80 | ((code >> 6) & 0x3f)); - buf[5] = (unsigned char)(0x80 | (code & 0x3f)); - return 6; - } - } -#endif - - return -1; -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tomlc17.h b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tomlc17.h deleted file mode 100644 index 3c367be1f2ad..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/tomlc17.h +++ /dev/null @@ -1,285 +0,0 @@ -/* Copyright (c) 2024-2026, CK Tan. - * https://github.com/cktan/tomlc17/blob/main/LICENSE - */ - -/** - * @file tomlc17.h - * @brief A TOML parser for C17. - * - * This library provides a simple and efficient way to parse TOML documents - * in C. It supports standard TOML features and provides an easy-to-use API - * for traversing the parsed data. - */ - -#ifndef TOMLC17_H -#define TOMLC17_H - -// A crude way to determine version. Manually changed. -#define TOMLC17_RELEASE_AFTER "260618" - -/* - * USAGE: - * - * 1. Call toml_parse(), toml_parse_file(), or toml_parse_file_ex() - * 2. Check result.ok - * 3. Use toml_get() or toml_seek() to query and traverse the - * result.toptab - * 4. Call toml_free() to release resources. - * - */ - -#include <stdbool.h> -#include <stdint.h> -#include <stdio.h> - -#ifdef __cplusplus -#define TOML_EXTERN extern "C" -#else -#define TOML_EXTERN extern -#endif - -/** - * @brief Enumeration of TOML data types. - */ -enum toml_type_t { - TOML_UNKNOWN = 0, /**< Unknown or invalid type */ - TOML_STRING, /**< String type */ - TOML_INT64, /**< 64-bit integer type */ - TOML_FP64, /**< 64-bit floating point type */ - TOML_BOOLEAN, /**< Boolean type */ - TOML_DATE, /**< Local date type */ - TOML_TIME, /**< Local time type */ - TOML_DATETIME, /**< Local datetime type */ - TOML_DATETIMETZ, /**< Offset datetime type */ - TOML_ARRAY, /**< Array type */ - TOML_TABLE, /**< Table type */ -}; -typedef enum toml_type_t toml_type_t; - -/** - * @brief Represents a single piece of TOML data. - * - * This structure is a node in a tree that represents a TOML document. - * The `u` union contains the actual value based on the `type` field. - */ -typedef struct toml_datum_t toml_datum_t; -struct toml_datum_t { - toml_type_t type; /**< Type of the datum */ - uint32_t flag; /**< Internal flag, do not use */ - int lineno; /**< 1-based source line number, 0 if synthesized */ - int colno; /**< 1-based source column number, 0 if synthesized */ - const char *source; /**< Source name (e.g. filename), NULL if not provided. - Owned by the result; valid until toml_free(). */ - union { - const char *s; /**< Shorthand for str.ptr */ - struct { - const char *ptr; /**< NUL terminated string pointer */ - int len; /**< Length of the string excluding the terminating NUL */ - } str; - int64_t int64; /**< 64-bit integer value */ - double fp64; /**< 64-bit floating point value */ - bool boolean; /**< Boolean value */ - struct { /**< Date and time components */ - int16_t year, month, day; - int16_t hour, minute, second; - int32_t usec; - int16_t tz; /**< Timezone offset in minutes */ - } ts; - struct { /**< Array data */ - int32_t size; /**< Number of elements in the array */ - toml_datum_t *elem; /**< Array of elements */ - } arr; - struct { /**< Table data */ - int32_t size; /**< Number of keys in the table */ - const char **key; /**< Array of keys */ - int *len; /**< Array of key lengths */ - toml_datum_t *value; /**< Array of values corresponding to keys */ - } tab; - } u; -}; - -/** - * @brief Bit values for toml_datum_t::flag. - * - * These record how a datum appeared in the source document. They are set - * by the parser and are informational for API users. - */ -#define TOML_FLAG_INLINED \ - 1 /**< appeared in inline form, e.g. {x = 1} or [1, 2] */ -#define TOML_FLAG_STDEXPR 2 /**< table created by a [table] header */ -#define TOML_FLAG_EXPLICIT 4 /**< table explicitly defined */ - -/** - * @brief Result of a TOML parsing operation. - */ -typedef struct toml_result_t toml_result_t; -struct toml_result_t { - bool ok; /**< True if parsing was successful */ - toml_datum_t toptab; /**< The top-level table (valid if ok is true) */ - char errmsg[200]; /**< Error message (valid if ok is false) */ - void *__internal; /**< Internal state, do not use */ -}; - -/** - * @brief Parse a TOML document from a string. - * - * @param src A string containing the TOML document. - * @param len The length of the string. src need not be NUL-terminated; - * only the first len bytes are read. - * @return A toml_result_t structure. Must be freed with toml_free(). - */ -TOML_EXTERN toml_result_t toml_parse(const char *src, int len); - -/** - * @brief Parse a TOML document, tagging every datum with a source name. - * - * @param src A string containing the TOML document. - * @param len The length of the string. src need not be NUL-terminated; - * only the first len bytes are read. - * @param name A source name (e.g. filename) copied into the result, or NULL. - * Every parsed datum's `source` is set to this name (or NULL). - * @return A toml_result_t structure. Must be freed with toml_free(). - */ -TOML_EXTERN toml_result_t toml_parse_named(const char *src, int len, - const char *name); - -/** - * @brief Parse a TOML document from a file pointer. - * - * @param fp A pointer to the open file. The caller is responsible for closing - * it. - * @return A toml_result_t structure. Must be freed with toml_free(). - * - * IMPORTANT: you are still responsible to fclose(fp). - */ -TOML_EXTERN toml_result_t toml_parse_file(FILE *fp); - -/** - * @brief Parse a TOML document from a file pointer, tagging datums with a name. - * - * @param fp A pointer to the open file. The caller is responsible for closing - * it. - * @param name A source name copied into the result, or NULL. - * @return A toml_result_t structure. Must be freed with toml_free(). - * - * IMPORTANT: you are still responsible to fclose(fp). - */ -TOML_EXTERN toml_result_t toml_parse_file_named(FILE *fp, const char *name); - -/** - * @brief Parse a TOML document from a file path. - * - * @param fname The path to the TOML file. - * @return A toml_result_t structure. Must be freed with toml_free(). - */ -TOML_EXTERN toml_result_t toml_parse_file_ex(const char *fname); - -/** - * @brief Release resources allocated for a TOML result. - * - * @param result The TOML result to free. - */ -TOML_EXTERN void toml_free(toml_result_t result); - -/** - * @brief Find a value for a specific key in a TOML table. - * - * @param table The TOML table to search in. - * @param key The key to look for. - * @return The value associated with the key, or a datum with type TOML_UNKNOWN - * if not found. - */ -TOML_EXTERN toml_datum_t toml_get(toml_datum_t table, const char *key); - -/** - * @brief Locate a value using a multipart-key (e.g., "a.b.c"). - * - * @param table The TOML table to start the search from. - * @param multipart_key A dot-separated key string. No escape characters - * allowed. Maximum length is 255 bytes. - * @return The value found, or a datum with type TOML_UNKNOWN if not found. - */ -TOML_EXTERN toml_datum_t toml_seek(toml_datum_t table, - const char *multipart_key); - -/** - * @brief OBSOLETE: use toml_get() instead. - * Find a key in a toml_table. Return the value of the key if found, - * or a TOML_UNKNOWN otherwise. - */ -static inline toml_datum_t toml_table_find(toml_datum_t table, - const char *key) { - return toml_get(table, key); -} - -/** - * @brief Merge two TOML results. - * - * All results (r1, r2, and the returned result) must be freed independently. - * - * @param r1 The base TOML result. - * @param r2 The TOML result containing overrides. - * @return A new toml_result_t representing the merged document. - * - * LOGIC: - * ret = copy of r1 - * for each item x in r2: - * if x is not in ret: - * set x in ret - * elif x in ret is NOT of the same type: - * override - * elif x in ret is an array of tables: - * append r2.x to ret.x - * elif x in ret is a table: - * merge r2.x to ret.x - * else: - * override - */ -TOML_EXTERN toml_result_t toml_merge(const toml_result_t *r1, - const toml_result_t *r2); - -/** - * @brief Compare two TOML results for equality. - * - * Tables compare as unordered maps (keys matched by name); array element - * order is significant. - * - * @param r1 The first TOML result. - * @param r2 The second TOML result. - * @return True if they are equivalent, false otherwise. - */ -TOML_EXTERN bool toml_equiv(const toml_result_t *r1, const toml_result_t *r2); - -/** - * @brief Global options for the TOML parser. - */ -typedef struct toml_option_t toml_option_t; -struct toml_option_t { - bool check_utf8; /**< If true, check if all characters are valid UTF-8. - Default: false. */ - void *(*mem_realloc)(void *ptr, - size_t size); /**< Custom realloc function (called with - size > 0). Default: realloc(). */ - void (*mem_free)(void *ptr); /**< Custom free function. Default: free(). */ -}; - -/** - * @brief Get the default parser options. - * - * Use this to obtain and initialize a toml_option_t structure before - * customizing it. - * - * @return A toml_option_t with default values. - */ -TOML_EXTERN toml_option_t toml_default_option(void); - -/** - * @brief Set the global parser options. - * - * Call this only if you need to override the default behavior. - * - * @param opt The options to set. - */ -TOML_EXTERN void toml_set_option(toml_option_t opt); - -#endif // TOMLC17_H diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/video-inhibition.md b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/video-inhibition.md deleted file mode 100644 index 393c1c52cf09..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/video-inhibition.md +++ /dev/null @@ -1,66 +0,0 @@ -# Application idle inhibition - -This integration is included in Ambient Background. See the configuration guide for current file paths and the WCM controls for continuous live wallpapers; helper and socket identifiers retain their compatible names. - -See [Configuration](configuration.md) for the full option reference and reload rules. - -Enable the Wayfire `digitalrain-idle` plugin alongside `ipc`, -`ipc-rules`, and `digitalrain-cursor` in Wayfire's `core/plugins` setting. - -`INHIBIT_VIDEO = true` in the `[global]` section of ambient-background.toml enables this feature -(default). Set it to false to disable it. The manager watches configuration saves. - -Applications that create Wayland idle inhibitors, including tested mpv playback, -keep their window's output awake. Moving the window moves the protection. When -the application releases its inhibitor (normally on pause or close), the normal -idle countdown resumes. Minimized -windows, disabled scene nodes, and windows off the current workspace are ignored. -An already active saver on the playback output is dismissed. Explicit activation -and lock requests still take precedence until user input resumes. - -Browser requests through `org.freedesktop.portal.Inhibit` are also supported -with digitalrain r3 and the Wayfire bridge in xdg-desktop-portal-gtk 1.15.3-r1. -The GTK backend keeps its usual portal routing and non-Wayfire fallback. It -forwards idle requests through a held Wayfire IPC connection rather than a -global ScreenSaver cookie. The compositor resolves the native Wayland parent -handle and verifies its owner against the original portal caller's bus PID. -Digitalrain r4 also accepts Firefox/LibreWolf's bare program-name placeholder -(for example `librewolf`) as a missing parent. The placeholder is not trusted -as an application or window identity: selection still uses the original bus -caller's PID. For a missing parent, it selects that caller's focused root or sole -mapped root; an ambiguous unfocused multi-window request is rejected. Invalid -or foreign `wayland:` handles never fall back to this selection, and other -prefixed handles (including `x11:`) remain unsupported. App IDs, -the portal broker's PID, MPRIS playback state, and fullscreen alone do not -create inhibition. - -Each request binds one window lifetime. Closing it, losing the original bus -caller or broker, backend exit, window unmap, or plugin unload releases it. -Minimizing or moving off-workspace temporarily stops its protection; restoring -the same mapped window resumes it while the request is held. Multiple requests -remain independent. New requests are needed after a plugin/backend restart; -already-failed portal calls are not silently replayed. Restarting the desktop -once activates both installed components, or relaunch the backend and renew -the browser request yourself. No user configuration rewrite is required. - -This follows application keep-awake requests, not video frame analysis. Players -that use neither supported Wayland nor portal inhibition are not detected. Applications -may also use inhibitors for non-video tasks. For ordinary outputs the window's primary output is used. With stitched outputs, -each physical member intersected by the playback window is protected. - -The manager uses ext-idle-notify v2's input-only notification to avoid one video -inhibiting idle detection across all monitors. With protocol v1 it falls back to -the old notification and logs a warning about the focused output also staying awake. - -IPC diagnostic: `digitalrain/idle_inhibitors` returns -`{"outputs":["DP-2"],"portal-requests":1}` for one portal request on DP-2. -The output list combines native and portal requests; the count includes held -portal leases even when their window is temporarily minimized or off-workspace. -Manager logs contain per-output `video inhibition active/released` transitions. - -Validation: build with `make all`; the C regression test is -`tests/test-inhibition.c` (link with the same protocol sources and libraries as -`files/digitalrain.c`). This also verifies that repeated seat-wide idle/resume -events refresh only the focused output, preserving other outputs' idle deadlines. -Live mpv tests covered playback on DP-1, moving to DP-3, -pause, resume, and preserving an existing DP-2 inhibitor throughout. diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/wlr-layer-shell-unstable-v1-client-protocol.h b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/wlr-layer-shell-unstable-v1-client-protocol.h deleted file mode 100644 index 660337bfad11..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/wlr-layer-shell-unstable-v1-client-protocol.h +++ /dev/null @@ -1,599 +0,0 @@ -/* Generated by wayland-scanner 1.26.0 */ - -#ifndef WLR_LAYER_SHELL_UNSTABLE_V1_CLIENT_PROTOCOL_H -#define WLR_LAYER_SHELL_UNSTABLE_V1_CLIENT_PROTOCOL_H - -#include <stdint.h> -#include <stddef.h> -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_wlr_layer_shell_unstable_v1 The wlr_layer_shell_unstable_v1 protocol - * @section page_ifaces_wlr_layer_shell_unstable_v1 Interfaces - * - @subpage page_iface_zwlr_layer_shell_v1 - create surfaces that are layers of the desktop - * - @subpage page_iface_zwlr_layer_surface_v1 - layer metadata interface - * @section page_copyright_wlr_layer_shell_unstable_v1 Copyright - * <pre> - * - * 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. - * </pre> - */ -struct wl_output; -struct wl_surface; -struct xdg_popup; -struct zwlr_layer_shell_v1; -struct zwlr_layer_surface_v1; - -#ifndef ZWLR_LAYER_SHELL_V1_INTERFACE -#define ZWLR_LAYER_SHELL_V1_INTERFACE -/** - * @page page_iface_zwlr_layer_shell_v1 zwlr_layer_shell_v1 - * @section page_iface_zwlr_layer_shell_v1_desc Description - * - * 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. - * @section page_iface_zwlr_layer_shell_v1_api API - * See @ref iface_zwlr_layer_shell_v1. - */ -/** - * @defgroup iface_zwlr_layer_shell_v1 The zwlr_layer_shell_v1 interface - * - * 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. - */ -extern const struct wl_interface zwlr_layer_shell_v1_interface; -#endif -#ifndef ZWLR_LAYER_SURFACE_V1_INTERFACE -#define ZWLR_LAYER_SURFACE_V1_INTERFACE -/** - * @page page_iface_zwlr_layer_surface_v1 zwlr_layer_surface_v1 - * @section page_iface_zwlr_layer_surface_v1_desc Description - * - * 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. - * @section page_iface_zwlr_layer_surface_v1_api API - * See @ref iface_zwlr_layer_surface_v1. - */ -/** - * @defgroup iface_zwlr_layer_surface_v1 The zwlr_layer_surface_v1 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. - */ -extern const struct wl_interface zwlr_layer_surface_v1_interface; -#endif - -#ifndef ZWLR_LAYER_SHELL_V1_ERROR_ENUM -#define ZWLR_LAYER_SHELL_V1_ERROR_ENUM -enum zwlr_layer_shell_v1_error { - /** - * wl_surface has another role - */ - ZWLR_LAYER_SHELL_V1_ERROR_ROLE = 0, - /** - * layer value is invalid - */ - ZWLR_LAYER_SHELL_V1_ERROR_INVALID_LAYER = 1, - /** - * wl_surface has a buffer attached or committed - */ - ZWLR_LAYER_SHELL_V1_ERROR_ALREADY_CONSTRUCTED = 2, -}; -#endif /* ZWLR_LAYER_SHELL_V1_ERROR_ENUM */ - -#ifndef ZWLR_LAYER_SHELL_V1_LAYER_ENUM -#define ZWLR_LAYER_SHELL_V1_LAYER_ENUM -/** - * @ingroup iface_zwlr_layer_shell_v1 - * 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. - */ -enum zwlr_layer_shell_v1_layer { - ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND = 0, - ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM = 1, - ZWLR_LAYER_SHELL_V1_LAYER_TOP = 2, - ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY = 3, -}; -#endif /* ZWLR_LAYER_SHELL_V1_LAYER_ENUM */ - -#define ZWLR_LAYER_SHELL_V1_GET_LAYER_SURFACE 0 -#define ZWLR_LAYER_SHELL_V1_DESTROY 1 - - -/** - * @ingroup iface_zwlr_layer_shell_v1 - */ -#define ZWLR_LAYER_SHELL_V1_GET_LAYER_SURFACE_SINCE_VERSION 1 -/** - * @ingroup iface_zwlr_layer_shell_v1 - */ -#define ZWLR_LAYER_SHELL_V1_DESTROY_SINCE_VERSION 3 - -/** @ingroup iface_zwlr_layer_shell_v1 */ -static inline void -zwlr_layer_shell_v1_set_user_data(struct zwlr_layer_shell_v1 *zwlr_layer_shell_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) zwlr_layer_shell_v1, user_data); -} - -/** @ingroup iface_zwlr_layer_shell_v1 */ -static inline void * -zwlr_layer_shell_v1_get_user_data(struct zwlr_layer_shell_v1 *zwlr_layer_shell_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) zwlr_layer_shell_v1); -} - -static inline uint32_t -zwlr_layer_shell_v1_get_version(struct zwlr_layer_shell_v1 *zwlr_layer_shell_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) zwlr_layer_shell_v1); -} - -/** - * @ingroup iface_zwlr_layer_shell_v1 - * - * Create a layer surface for an existing surface. This assigns the role of - * layer_surface, or raises a protocol error if another role is already - * assigned. - * - * Creating a layer surface from a wl_surface which has a buffer attached - * or committed is a client error, and any attempts by a client to attach - * or manipulate a buffer prior to the first layer_surface.configure call - * must also be treated as errors. - * - * You may pass NULL for output to allow the compositor to decide which - * output to use. Generally this will be the one that the user most - * recently interacted with. - * - * Clients can specify a namespace that defines the purpose of the layer - * surface. - */ -static inline struct zwlr_layer_surface_v1 * -zwlr_layer_shell_v1_get_layer_surface(struct zwlr_layer_shell_v1 *zwlr_layer_shell_v1, struct wl_surface *surface, struct wl_output *output, uint32_t layer, const char *namespace) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) zwlr_layer_shell_v1, - ZWLR_LAYER_SHELL_V1_GET_LAYER_SURFACE, &zwlr_layer_surface_v1_interface, wl_proxy_get_version((struct wl_proxy *) zwlr_layer_shell_v1), 0, NULL, surface, output, layer, namespace); - - return (struct zwlr_layer_surface_v1 *) id; -} - -/** - * @ingroup iface_zwlr_layer_shell_v1 - * - * 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. - */ -static inline void -zwlr_layer_shell_v1_destroy(struct zwlr_layer_shell_v1 *zwlr_layer_shell_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwlr_layer_shell_v1, - ZWLR_LAYER_SHELL_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_layer_shell_v1), WL_MARSHAL_FLAG_DESTROY); -} - -#ifndef ZWLR_LAYER_SURFACE_V1_ERROR_ENUM -#define ZWLR_LAYER_SURFACE_V1_ERROR_ENUM -enum zwlr_layer_surface_v1_error { - /** - * provided surface state is invalid - */ - ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SURFACE_STATE = 0, - /** - * size is invalid - */ - ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_SIZE = 1, - /** - * anchor bitfield is invalid - */ - ZWLR_LAYER_SURFACE_V1_ERROR_INVALID_ANCHOR = 2, -}; -#endif /* ZWLR_LAYER_SURFACE_V1_ERROR_ENUM */ - -#ifndef ZWLR_LAYER_SURFACE_V1_ANCHOR_ENUM -#define ZWLR_LAYER_SURFACE_V1_ANCHOR_ENUM -enum zwlr_layer_surface_v1_anchor { - /** - * the top edge of the anchor rectangle - */ - ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP = 1, - /** - * the bottom edge of the anchor rectangle - */ - ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM = 2, - /** - * the left edge of the anchor rectangle - */ - ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT = 4, - /** - * the right edge of the anchor rectangle - */ - ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT = 8, -}; -#endif /* ZWLR_LAYER_SURFACE_V1_ANCHOR_ENUM */ - -/** - * @ingroup iface_zwlr_layer_surface_v1 - * @struct zwlr_layer_surface_v1_listener - */ -struct zwlr_layer_surface_v1_listener { - /** - * 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. - */ - void (*configure)(void *data, - struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, - uint32_t serial, - uint32_t width, - uint32_t height); - /** - * 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. - */ - void (*closed)(void *data, - struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1); -}; - -/** - * @ingroup iface_zwlr_layer_surface_v1 - */ -static inline int -zwlr_layer_surface_v1_add_listener(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, - const struct zwlr_layer_surface_v1_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) zwlr_layer_surface_v1, - (void (**)(void)) listener, data); -} - -#define ZWLR_LAYER_SURFACE_V1_SET_SIZE 0 -#define ZWLR_LAYER_SURFACE_V1_SET_ANCHOR 1 -#define ZWLR_LAYER_SURFACE_V1_SET_EXCLUSIVE_ZONE 2 -#define ZWLR_LAYER_SURFACE_V1_SET_MARGIN 3 -#define ZWLR_LAYER_SURFACE_V1_SET_KEYBOARD_INTERACTIVITY 4 -#define ZWLR_LAYER_SURFACE_V1_GET_POPUP 5 -#define ZWLR_LAYER_SURFACE_V1_ACK_CONFIGURE 6 -#define ZWLR_LAYER_SURFACE_V1_DESTROY 7 -#define ZWLR_LAYER_SURFACE_V1_SET_LAYER 8 - -/** - * @ingroup iface_zwlr_layer_surface_v1 - */ -#define ZWLR_LAYER_SURFACE_V1_CONFIGURE_SINCE_VERSION 1 -/** - * @ingroup iface_zwlr_layer_surface_v1 - */ -#define ZWLR_LAYER_SURFACE_V1_CLOSED_SINCE_VERSION 1 - -/** - * @ingroup iface_zwlr_layer_surface_v1 - */ -#define ZWLR_LAYER_SURFACE_V1_SET_SIZE_SINCE_VERSION 1 -/** - * @ingroup iface_zwlr_layer_surface_v1 - */ -#define ZWLR_LAYER_SURFACE_V1_SET_ANCHOR_SINCE_VERSION 1 -/** - * @ingroup iface_zwlr_layer_surface_v1 - */ -#define ZWLR_LAYER_SURFACE_V1_SET_EXCLUSIVE_ZONE_SINCE_VERSION 1 -/** - * @ingroup iface_zwlr_layer_surface_v1 - */ -#define ZWLR_LAYER_SURFACE_V1_SET_MARGIN_SINCE_VERSION 1 -/** - * @ingroup iface_zwlr_layer_surface_v1 - */ -#define ZWLR_LAYER_SURFACE_V1_SET_KEYBOARD_INTERACTIVITY_SINCE_VERSION 1 -/** - * @ingroup iface_zwlr_layer_surface_v1 - */ -#define ZWLR_LAYER_SURFACE_V1_GET_POPUP_SINCE_VERSION 1 -/** - * @ingroup iface_zwlr_layer_surface_v1 - */ -#define ZWLR_LAYER_SURFACE_V1_ACK_CONFIGURE_SINCE_VERSION 1 -/** - * @ingroup iface_zwlr_layer_surface_v1 - */ -#define ZWLR_LAYER_SURFACE_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_zwlr_layer_surface_v1 - */ -#define ZWLR_LAYER_SURFACE_V1_SET_LAYER_SINCE_VERSION 2 - -/** @ingroup iface_zwlr_layer_surface_v1 */ -static inline void -zwlr_layer_surface_v1_set_user_data(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) zwlr_layer_surface_v1, user_data); -} - -/** @ingroup iface_zwlr_layer_surface_v1 */ -static inline void * -zwlr_layer_surface_v1_get_user_data(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) zwlr_layer_surface_v1); -} - -static inline uint32_t -zwlr_layer_surface_v1_get_version(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) zwlr_layer_surface_v1); -} - -/** - * @ingroup iface_zwlr_layer_surface_v1 - * - * 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. - */ -static inline void -zwlr_layer_surface_v1_set_size(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, uint32_t width, uint32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwlr_layer_surface_v1, - ZWLR_LAYER_SURFACE_V1_SET_SIZE, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_layer_surface_v1), 0, width, height); -} - -/** - * @ingroup iface_zwlr_layer_surface_v1 - * - * 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. - */ -static inline void -zwlr_layer_surface_v1_set_anchor(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, uint32_t anchor) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwlr_layer_surface_v1, - ZWLR_LAYER_SURFACE_V1_SET_ANCHOR, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_layer_surface_v1), 0, anchor); -} - -/** - * @ingroup iface_zwlr_layer_surface_v1 - * - * 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. - */ -static inline void -zwlr_layer_surface_v1_set_exclusive_zone(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, int32_t zone) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwlr_layer_surface_v1, - ZWLR_LAYER_SURFACE_V1_SET_EXCLUSIVE_ZONE, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_layer_surface_v1), 0, zone); -} - -/** - * @ingroup iface_zwlr_layer_surface_v1 - * - * 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. - */ -static inline void -zwlr_layer_surface_v1_set_margin(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, int32_t top, int32_t right, int32_t bottom, int32_t left) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwlr_layer_surface_v1, - ZWLR_LAYER_SURFACE_V1_SET_MARGIN, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_layer_surface_v1), 0, top, right, bottom, left); -} - -/** - * @ingroup iface_zwlr_layer_surface_v1 - * - * Set to 1 to request that the seat send keyboard events to this layer - * surface. For layers below the shell surface layer, the seat will use - * normal focus semantics. For layers above the shell surface layers, the - * seat will always give exclusive keyboard focus to the top-most layer - * which has keyboard interactivity set to true. - * - * Layer surfaces receive pointer, touch, and tablet events normally. If - * you do not want to receive them, set the input region on your surface - * to an empty region. - * - * Events is double-buffered, see wl_surface.commit. - */ -static inline void -zwlr_layer_surface_v1_set_keyboard_interactivity(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, uint32_t keyboard_interactivity) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwlr_layer_surface_v1, - ZWLR_LAYER_SURFACE_V1_SET_KEYBOARD_INTERACTIVITY, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_layer_surface_v1), 0, keyboard_interactivity); -} - -/** - * @ingroup iface_zwlr_layer_surface_v1 - * - * 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. - */ -static inline void -zwlr_layer_surface_v1_get_popup(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, struct xdg_popup *popup) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwlr_layer_surface_v1, - ZWLR_LAYER_SURFACE_V1_GET_POPUP, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_layer_surface_v1), 0, popup); -} - -/** - * @ingroup iface_zwlr_layer_surface_v1 - * - * 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. - */ -static inline void -zwlr_layer_surface_v1_ack_configure(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwlr_layer_surface_v1, - ZWLR_LAYER_SURFACE_V1_ACK_CONFIGURE, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_layer_surface_v1), 0, serial); -} - -/** - * @ingroup iface_zwlr_layer_surface_v1 - * - * This request destroys the layer surface. - */ -static inline void -zwlr_layer_surface_v1_destroy(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwlr_layer_surface_v1, - ZWLR_LAYER_SURFACE_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_layer_surface_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_zwlr_layer_surface_v1 - * - * Change the layer that the surface is rendered on. - * - * Layer is double-buffered, see wl_surface.commit. - */ -static inline void -zwlr_layer_surface_v1_set_layer(struct zwlr_layer_surface_v1 *zwlr_layer_surface_v1, uint32_t layer) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zwlr_layer_surface_v1, - ZWLR_LAYER_SURFACE_V1_SET_LAYER, NULL, wl_proxy_get_version((struct wl_proxy *) zwlr_layer_surface_v1), 0, layer); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/wlr-layer-shell-unstable-v1-protocol.c b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/wlr-layer-shell-unstable-v1-protocol.c deleted file mode 100644 index e9da255d87c6..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/wlr-layer-shell-unstable-v1-protocol.c +++ /dev/null @@ -1,94 +0,0 @@ -/* Generated by wayland-scanner 1.26.0 */ - -/* - * 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. - */ - -#include <stdbool.h> -#include <stdlib.h> -#include <stdint.h> -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface wl_output_interface; -extern const struct wl_interface wl_surface_interface; -extern const struct wl_interface xdg_popup_interface; -extern const struct wl_interface zwlr_layer_surface_v1_interface; - -static const struct wl_interface *wlr_layer_shell_unstable_v1_types[] = { - NULL, - NULL, - NULL, - NULL, - &zwlr_layer_surface_v1_interface, - &wl_surface_interface, - &wl_output_interface, - NULL, - NULL, - &xdg_popup_interface, -}; - -static const struct wl_message zwlr_layer_shell_v1_requests[] = { - { "get_layer_surface", "no?ous", wlr_layer_shell_unstable_v1_types + 4 }, - { "destroy", "3", wlr_layer_shell_unstable_v1_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface zwlr_layer_shell_v1_interface = { - "zwlr_layer_shell_v1", 3, - 2, zwlr_layer_shell_v1_requests, - 0, NULL, -}; - -static const struct wl_message zwlr_layer_surface_v1_requests[] = { - { "set_size", "uu", wlr_layer_shell_unstable_v1_types + 0 }, - { "set_anchor", "u", wlr_layer_shell_unstable_v1_types + 0 }, - { "set_exclusive_zone", "i", wlr_layer_shell_unstable_v1_types + 0 }, - { "set_margin", "iiii", wlr_layer_shell_unstable_v1_types + 0 }, - { "set_keyboard_interactivity", "u", wlr_layer_shell_unstable_v1_types + 0 }, - { "get_popup", "o", wlr_layer_shell_unstable_v1_types + 9 }, - { "ack_configure", "u", wlr_layer_shell_unstable_v1_types + 0 }, - { "destroy", "", wlr_layer_shell_unstable_v1_types + 0 }, - { "set_layer", "2u", wlr_layer_shell_unstable_v1_types + 0 }, -}; - -static const struct wl_message zwlr_layer_surface_v1_events[] = { - { "configure", "uuu", wlr_layer_shell_unstable_v1_types + 0 }, - { "closed", "", wlr_layer_shell_unstable_v1_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface zwlr_layer_surface_v1_interface = { - "zwlr_layer_surface_v1", 3, - 9, zwlr_layer_surface_v1_requests, - 2, zwlr_layer_surface_v1_events, -}; - diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/wlr-layer-shell-unstable-v1.xml b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/wlr-layer-shell-unstable-v1.xml deleted file mode 100644 index fa67001dfeb1..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/wlr-layer-shell-unstable-v1.xml +++ /dev/null @@ -1,311 +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="3"> - <description summary="create surfaces that are layers of the desktop"> - Clients can use this interface to assign the surface_layer role to - wl_surfaces. Such surfaces are assigned to a "layer" of the output and - rendered with a defined z-depth respective to each other. They may also be - anchored to the edges and corners of a screen and specify input handling - semantics. This interface should be suitable for the implementation of - many desktop shell components, and a broad number of other applications - that interact with the desktop. - </description> - - <request name="get_layer_surface"> - <description summary="create a layer_surface from a surface"> - Create a layer surface for an existing surface. This assigns the role of - layer_surface, or raises a protocol error if another role is already - assigned. - - Creating a layer surface from a wl_surface which has a buffer attached - or committed is a client error, and any attempts by a client to attach - or manipulate a buffer prior to the first layer_surface.configure call - must also be treated as errors. - - You may pass NULL for output to allow the compositor to decide which - output to use. Generally this will be the one that the user most - recently interacted with. - - Clients can specify a namespace that defines the purpose of the layer - surface. - </description> - <arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/> - <arg name="surface" type="object" interface="wl_surface"/> - <arg name="output" type="object" interface="wl_output" allow-null="true"/> - <arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/> - <arg name="namespace" type="string" summary="namespace for the layer surface"/> - </request> - - <enum name="error"> - <entry name="role" value="0" summary="wl_surface has another role"/> - <entry name="invalid_layer" value="1" summary="layer value is invalid"/> - <entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/> - </enum> - - <enum name="layer"> - <description summary="available layers for surfaces"> - These values indicate which layers a surface can be rendered in. They - are ordered by z depth, bottom-most first. Traditional shell surfaces - will typically be rendered between the bottom and top layers. - Fullscreen shell surfaces are typically rendered at the top layer. - Multiple surfaces can share a single layer, and ordering within a - single layer is undefined. - </description> - - <entry name="background" value="0"/> - <entry name="bottom" value="1"/> - <entry name="top" value="2"/> - <entry name="overlay" value="3"/> - </enum> - - <!-- 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="3"> - <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. - </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> - - <request name="set_keyboard_interactivity"> - <description summary="requests keyboard events"> - Set to 1 to request that the seat send keyboard events to this layer - surface. For layers below the shell surface layer, the seat will use - normal focus semantics. For layers above the shell surface layers, the - seat will always give exclusive keyboard focus to the top-most layer - which has keyboard interactivity set to true. - - Layer surfaces receive pointer, touch, and tablet events normally. If - you do not want to receive them, set the input region on your surface - to an empty region. - - Events is double-buffered, see wl_surface.commit. - </description> - <arg name="keyboard_interactivity" type="uint"/> - </request> - - <request name="get_popup"> - <description summary="assign this layer_surface as an xdg_popup parent"> - This assigns an xdg_popup's parent to this layer_surface. This popup - should have been created via xdg_surface::get_popup with the parent set - to NULL, and this request must be invoked before committing the popup's - initial state. - - See the documentation of xdg_popup for more details about what an - xdg_popup is and how it is used. - </description> - <arg name="popup" type="object" interface="xdg_popup"/> - </request> - - <request name="ack_configure"> - <description summary="ack a configure event"> - When a configure event is received, if a client commits the - surface in response to the configure event, then the client - must make an ack_configure request sometime before the commit - request, passing along the serial of the configure event. - - If the client receives multiple configure events before it - can respond to one, it only has to ack the last configure event. - - A client is not required to commit immediately after sending - an ack_configure request - it may even ack_configure several times - before its next surface commit. - - A client may send multiple ack_configure requests before committing, but - only the last request sent before a commit indicates which configure - event the client really is responding to. - </description> - <arg name="serial" type="uint" summary="the serial from the configure event"/> - </request> - - <request name="destroy" type="destructor"> - <description summary="destroy the layer_surface"> - This request destroys the layer surface. - </description> - </request> - - <event name="configure"> - <description summary="suggest a surface change"> - The configure event asks the client to resize its surface. - - Clients should arrange their surface for the new states, and then send - an ack_configure request with the serial sent in this configure event at - some point before committing the new surface. - - The client is free to dismiss all but the last configure event it - received. - - The width and height arguments specify the size of the window in - surface-local coordinates. - - The size is a hint, in the sense that the client is free to ignore it if - it doesn't resize, pick a smaller size (to satisfy aspect ratio or - resize in steps of NxM pixels). If the client picks a smaller size and - is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the - surface will be centered on this axis. - - If the width or height arguments are zero, it means the client should - decide its own window dimension. - </description> - <arg name="serial" type="uint"/> - <arg name="width" type="uint"/> - <arg name="height" type="uint"/> - </event> - - <event name="closed"> - <description summary="surface should be closed"> - The closed event is sent by the compositor when the surface will no - longer be shown. The output may have been destroyed or the user may - have asked for it to be removed. Further changes to the surface will be - ignored. The client should destroy the resource after receiving this - event, and create a new surface if they so choose. - </description> - </event> - - <enum name="error"> - <entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/> - <entry name="invalid_size" value="1" summary="size is invalid"/> - <entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/> - </enum> - - <enum name="anchor" bitfield="true"> - <entry name="top" value="1" summary="the top edge of the anchor rectangle"/> - <entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/> - <entry name="left" value="4" summary="the left edge of the anchor rectangle"/> - <entry name="right" value="8" summary="the right edge of the anchor rectangle"/> - </enum> - - <!-- 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> - </interface> -</protocol> diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/xdg-output-unstable-v1-client-protocol.h b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/xdg-output-unstable-v1-client-protocol.h deleted file mode 100644 index 5dfc398fd2cd..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/xdg-output-unstable-v1-client-protocol.h +++ /dev/null @@ -1,415 +0,0 @@ -/* Generated by wayland-scanner 1.26.0 */ - -#ifndef XDG_OUTPUT_UNSTABLE_V1_CLIENT_PROTOCOL_H -#define XDG_OUTPUT_UNSTABLE_V1_CLIENT_PROTOCOL_H - -#include <stdint.h> -#include <stddef.h> -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_xdg_output_unstable_v1 The xdg_output_unstable_v1 protocol - * Protocol to describe output regions - * - * @section page_desc_xdg_output_unstable_v1 Description - * - * This protocol aims at describing outputs in a way which is more in line - * with the concept of an output on desktop oriented systems. - * - * Some information are more specific to the concept of an output for - * a desktop oriented system and may not make sense in other applications, - * such as IVI systems for example. - * - * Typically, the global compositor space on a desktop system is made of - * a contiguous or overlapping set of rectangular regions. - * - * The logical_position and logical_size events defined in this protocol - * might provide information identical to their counterparts already - * available from wl_output, in which case the information provided by this - * protocol should be preferred to their equivalent in wl_output. The goal is - * to move the desktop specific concepts (such as output location within the - * global compositor space, etc.) out of the core wl_output protocol. - * - * Warning! The protocol described in this file is experimental and - * backward incompatible changes may be made. Backward compatible - * changes may be added together with the corresponding interface - * version bump. - * Backward incompatible changes are done by bumping the version - * number in the protocol and interface names and resetting the - * interface version. Once the protocol is to be declared stable, - * the 'z' prefix and the version number in the protocol and - * interface names are removed and the interface version number is - * reset. - * - * @section page_ifaces_xdg_output_unstable_v1 Interfaces - * - @subpage page_iface_zxdg_output_manager_v1 - manage xdg_output objects - * - @subpage page_iface_zxdg_output_v1 - compositor logical output region - * @section page_copyright_xdg_output_unstable_v1 Copyright - * <pre> - * - * Copyright © 2017 Red Hat Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * </pre> - */ -struct wl_output; -struct zxdg_output_manager_v1; -struct zxdg_output_v1; - -#ifndef ZXDG_OUTPUT_MANAGER_V1_INTERFACE -#define ZXDG_OUTPUT_MANAGER_V1_INTERFACE -/** - * @page page_iface_zxdg_output_manager_v1 zxdg_output_manager_v1 - * @section page_iface_zxdg_output_manager_v1_desc Description - * - * A global factory interface for xdg_output objects. - * @section page_iface_zxdg_output_manager_v1_api API - * See @ref iface_zxdg_output_manager_v1. - */ -/** - * @defgroup iface_zxdg_output_manager_v1 The zxdg_output_manager_v1 interface - * - * A global factory interface for xdg_output objects. - */ -extern const struct wl_interface zxdg_output_manager_v1_interface; -#endif -#ifndef ZXDG_OUTPUT_V1_INTERFACE -#define ZXDG_OUTPUT_V1_INTERFACE -/** - * @page page_iface_zxdg_output_v1 zxdg_output_v1 - * @section page_iface_zxdg_output_v1_desc Description - * - * An xdg_output describes part of the compositor geometry. - * - * This typically corresponds to a monitor that displays part of the - * compositor space. - * - * For objects version 3 onwards, after all xdg_output properties have been - * sent (when the object is created and when properties are updated), a - * wl_output.done event is sent. This allows changes to the output - * properties to be seen as atomic, even if they happen via multiple events. - * @section page_iface_zxdg_output_v1_api API - * See @ref iface_zxdg_output_v1. - */ -/** - * @defgroup iface_zxdg_output_v1 The zxdg_output_v1 interface - * - * An xdg_output describes part of the compositor geometry. - * - * This typically corresponds to a monitor that displays part of the - * compositor space. - * - * For objects version 3 onwards, after all xdg_output properties have been - * sent (when the object is created and when properties are updated), a - * wl_output.done event is sent. This allows changes to the output - * properties to be seen as atomic, even if they happen via multiple events. - */ -extern const struct wl_interface zxdg_output_v1_interface; -#endif - -#define ZXDG_OUTPUT_MANAGER_V1_DESTROY 0 -#define ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT 1 - - -/** - * @ingroup iface_zxdg_output_manager_v1 - */ -#define ZXDG_OUTPUT_MANAGER_V1_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_zxdg_output_manager_v1 - */ -#define ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT_SINCE_VERSION 1 - -/** @ingroup iface_zxdg_output_manager_v1 */ -static inline void -zxdg_output_manager_v1_set_user_data(struct zxdg_output_manager_v1 *zxdg_output_manager_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) zxdg_output_manager_v1, user_data); -} - -/** @ingroup iface_zxdg_output_manager_v1 */ -static inline void * -zxdg_output_manager_v1_get_user_data(struct zxdg_output_manager_v1 *zxdg_output_manager_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) zxdg_output_manager_v1); -} - -static inline uint32_t -zxdg_output_manager_v1_get_version(struct zxdg_output_manager_v1 *zxdg_output_manager_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) zxdg_output_manager_v1); -} - -/** - * @ingroup iface_zxdg_output_manager_v1 - * - * Using this request a client can tell the server that it is not - * going to use the xdg_output_manager object anymore. - * - * Any objects already created through this instance are not affected. - */ -static inline void -zxdg_output_manager_v1_destroy(struct zxdg_output_manager_v1 *zxdg_output_manager_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zxdg_output_manager_v1, - ZXDG_OUTPUT_MANAGER_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_output_manager_v1), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_zxdg_output_manager_v1 - * - * This creates a new xdg_output object for the given wl_output. - */ -static inline struct zxdg_output_v1 * -zxdg_output_manager_v1_get_xdg_output(struct zxdg_output_manager_v1 *zxdg_output_manager_v1, struct wl_output *output) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) zxdg_output_manager_v1, - ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT, &zxdg_output_v1_interface, wl_proxy_get_version((struct wl_proxy *) zxdg_output_manager_v1), 0, NULL, output); - - return (struct zxdg_output_v1 *) id; -} - -/** - * @ingroup iface_zxdg_output_v1 - * @struct zxdg_output_v1_listener - */ -struct zxdg_output_v1_listener { - /** - * position of the output within the global compositor space - * - * The position event describes the location of the wl_output - * within the global compositor space. - * - * The logical_position event is sent after creating an xdg_output - * (see xdg_output_manager.get_xdg_output) and whenever the - * location of the output changes within the global compositor - * space. - * @param x x position within the global compositor space - * @param y y position within the global compositor space - */ - void (*logical_position)(void *data, - struct zxdg_output_v1 *zxdg_output_v1, - int32_t x, - int32_t y); - /** - * size of the output in the global compositor space - * - * The logical_size event describes the size of the output in the - * global compositor space. - * - * Most regular Wayland clients should not pay attention to the - * logical size and would rather rely on xdg_shell interfaces. - * - * Some clients such as Xwayland, however, need this to configure - * their surfaces in the global compositor space as the compositor - * may apply a different scale from what is advertised by the - * output scaling property (to achieve fractional scaling, for - * example). - * - * For example, for a wl_output mode 3840×2160 and a scale factor - * 2: - * - * - A compositor not scaling the monitor viewport in its - * compositing space will advertise a logical size of 3840×2160, - * - * - A compositor scaling the monitor viewport with scale factor 2 - * will advertise a logical size of 1920×1080, - * - * - A compositor scaling the monitor viewport using a fractional - * scale of 1.5 will advertise a logical size of 2560×1440. - * - * For example, for a wl_output mode 1920×1080 and a 90 degree - * rotation, the compositor will advertise a logical size of - * 1080x1920. - * - * The logical_size event is sent after creating an xdg_output (see - * xdg_output_manager.get_xdg_output) and whenever the logical size - * of the output changes, either as a result of a change in the - * applied scale or because of a change in the corresponding output - * mode(see wl_output.mode) or transform (see wl_output.transform). - * @param width width in global compositor space - * @param height height in global compositor space - */ - void (*logical_size)(void *data, - struct zxdg_output_v1 *zxdg_output_v1, - int32_t width, - int32_t height); - /** - * all information about the output have been sent - * - * This event is sent after all other properties of an xdg_output - * have been sent. - * - * This allows changes to the xdg_output properties to be seen as - * atomic, even if they happen via multiple events. - * - * For objects version 3 onwards, this event is deprecated. - * Compositors are not required to send it anymore and must send - * wl_output.done instead. - * @deprecated Deprecated since version 3 - */ - void (*done)(void *data, - struct zxdg_output_v1 *zxdg_output_v1); - /** - * name of this output - * - * Many compositors will assign names to their outputs, show them - * to the user, allow them to be configured by name, etc. The - * client may wish to know this name as well to offer the user - * similar behaviors. - * - * The naming convention is compositor defined, but limited to - * alphanumeric characters and dashes (-). Each name is unique - * among all wl_output globals, but if a wl_output global is - * destroyed the same name may be reused later. The names will also - * remain consistent across sessions with the same hardware and - * software configuration. - * - * Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. - * However, do not assume that the name is a reflection of an - * underlying DRM connector, X11 connection, etc. - * - * The name event is sent after creating an xdg_output (see - * xdg_output_manager.get_xdg_output). This event is only sent once - * per xdg_output, and the name does not change over the lifetime - * of the wl_output global. - * - * This event is deprecated, instead clients should use - * wl_output.name. Compositors must still support this event. - * @param name output name - * @since 2 - */ - void (*name)(void *data, - struct zxdg_output_v1 *zxdg_output_v1, - const char *name); - /** - * human-readable description of this output - * - * Many compositors can produce human-readable descriptions of - * their outputs. The client may wish to know this description as - * well, to communicate the user for various purposes. - * - * The description is a UTF-8 string with no convention defined for - * its contents. Examples might include 'Foocorp 11" Display' or - * 'Virtual X11 output via :1'. - * - * The description event is sent after creating an xdg_output (see - * xdg_output_manager.get_xdg_output) and whenever the description - * changes. The description is optional, and may not be sent at - * all. - * - * For objects of version 2 and lower, this event is only sent once - * per xdg_output, and the description does not change over the - * lifetime of the wl_output global. - * - * This event is deprecated, instead clients should use - * wl_output.description. Compositors must still support this - * event. - * @param description output description - * @since 2 - */ - void (*description)(void *data, - struct zxdg_output_v1 *zxdg_output_v1, - const char *description); -}; - -/** - * @ingroup iface_zxdg_output_v1 - */ -static inline int -zxdg_output_v1_add_listener(struct zxdg_output_v1 *zxdg_output_v1, - const struct zxdg_output_v1_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) zxdg_output_v1, - (void (**)(void)) listener, data); -} - -#define ZXDG_OUTPUT_V1_DESTROY 0 - -/** - * @ingroup iface_zxdg_output_v1 - */ -#define ZXDG_OUTPUT_V1_LOGICAL_POSITION_SINCE_VERSION 1 -/** - * @ingroup iface_zxdg_output_v1 - */ -#define ZXDG_OUTPUT_V1_LOGICAL_SIZE_SINCE_VERSION 1 -/** - * @ingroup iface_zxdg_output_v1 - */ -#define ZXDG_OUTPUT_V1_DONE_SINCE_VERSION 1 -/** - * @ingroup iface_zxdg_output_v1 - */ -#define ZXDG_OUTPUT_V1_NAME_SINCE_VERSION 2 -/** - * @ingroup iface_zxdg_output_v1 - */ -#define ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION 2 - -/** - * @ingroup iface_zxdg_output_v1 - */ -#define ZXDG_OUTPUT_V1_DESTROY_SINCE_VERSION 1 - -/** @ingroup iface_zxdg_output_v1 */ -static inline void -zxdg_output_v1_set_user_data(struct zxdg_output_v1 *zxdg_output_v1, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) zxdg_output_v1, user_data); -} - -/** @ingroup iface_zxdg_output_v1 */ -static inline void * -zxdg_output_v1_get_user_data(struct zxdg_output_v1 *zxdg_output_v1) -{ - return wl_proxy_get_user_data((struct wl_proxy *) zxdg_output_v1); -} - -static inline uint32_t -zxdg_output_v1_get_version(struct zxdg_output_v1 *zxdg_output_v1) -{ - return wl_proxy_get_version((struct wl_proxy *) zxdg_output_v1); -} - -/** - * @ingroup iface_zxdg_output_v1 - * - * Using this request a client can tell the server that it is not - * going to use the xdg_output object anymore. - */ -static inline void -zxdg_output_v1_destroy(struct zxdg_output_v1 *zxdg_output_v1) -{ - wl_proxy_marshal_flags((struct wl_proxy *) zxdg_output_v1, - ZXDG_OUTPUT_V1_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) zxdg_output_v1), WL_MARSHAL_FLAG_DESTROY); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/xdg-output-unstable-v1-protocol.c b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/xdg-output-unstable-v1-protocol.c deleted file mode 100644 index acb89285552e..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/xdg-output-unstable-v1-protocol.c +++ /dev/null @@ -1,79 +0,0 @@ -/* Generated by wayland-scanner 1.26.0 */ - -/* - * Copyright © 2017 Red Hat Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include <stdbool.h> -#include <stdlib.h> -#include <stdint.h> -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface wl_output_interface; -extern const struct wl_interface zxdg_output_v1_interface; - -static const struct wl_interface *xdg_output_unstable_v1_types[] = { - NULL, - NULL, - &zxdg_output_v1_interface, - &wl_output_interface, -}; - -static const struct wl_message zxdg_output_manager_v1_requests[] = { - { "destroy", "", xdg_output_unstable_v1_types + 0 }, - { "get_xdg_output", "no", xdg_output_unstable_v1_types + 2 }, -}; - -WL_PRIVATE const struct wl_interface zxdg_output_manager_v1_interface = { - "zxdg_output_manager_v1", 3, - 2, zxdg_output_manager_v1_requests, - 0, NULL, -}; - -static const struct wl_message zxdg_output_v1_requests[] = { - { "destroy", "", xdg_output_unstable_v1_types + 0 }, -}; - -static const struct wl_message zxdg_output_v1_events[] = { - { "logical_position", "ii", xdg_output_unstable_v1_types + 0 }, - { "logical_size", "ii", xdg_output_unstable_v1_types + 0 }, - { "done", "", xdg_output_unstable_v1_types + 0 }, - { "name", "2s", xdg_output_unstable_v1_types + 0 }, - { "description", "2s", xdg_output_unstable_v1_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface zxdg_output_v1_interface = { - "zxdg_output_v1", 3, - 1, zxdg_output_v1_requests, - 5, zxdg_output_v1_events, -}; - diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/xdg-shell-client-protocol.h b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/xdg-shell-client-protocol.h deleted file mode 100644 index 64470e4e9163..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/xdg-shell-client-protocol.h +++ /dev/null @@ -1,2392 +0,0 @@ -/* Generated by wayland-scanner 1.26.0 */ - -#ifndef XDG_SHELL_CLIENT_PROTOCOL_H -#define XDG_SHELL_CLIENT_PROTOCOL_H - -#include <stdint.h> -#include <stddef.h> -#include "wayland-client.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @page page_xdg_shell The xdg_shell protocol - * @section page_ifaces_xdg_shell Interfaces - * - @subpage page_iface_xdg_wm_base - create desktop-style surfaces - * - @subpage page_iface_xdg_positioner - child surface positioner - * - @subpage page_iface_xdg_surface - desktop user interface surface base interface - * - @subpage page_iface_xdg_toplevel - toplevel surface - * - @subpage page_iface_xdg_popup - short-lived, popup surfaces for menus - * @section page_copyright_xdg_shell Copyright - * <pre> - * - * Copyright © 2008-2013 Kristian Høgsberg - * Copyright © 2013 Rafael Antognolli - * Copyright © 2013 Jasper St. Pierre - * Copyright © 2010-2013 Intel Corporation - * Copyright © 2015-2017 Samsung Electronics Co., Ltd - * Copyright © 2015-2017 Red Hat Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * </pre> - */ -struct wl_output; -struct wl_seat; -struct wl_surface; -struct xdg_popup; -struct xdg_positioner; -struct xdg_surface; -struct xdg_toplevel; -struct xdg_wm_base; - -#ifndef XDG_WM_BASE_INTERFACE -#define XDG_WM_BASE_INTERFACE -/** - * @page page_iface_xdg_wm_base xdg_wm_base - * @section page_iface_xdg_wm_base_desc Description - * - * The xdg_wm_base interface is exposed as a global object enabling clients - * to turn their wl_surfaces into windows in a desktop environment. It - * defines the basic functionality needed for clients and the compositor to - * create windows that can be dragged, resized, maximized, etc, as well as - * creating transient windows such as popup menus. - * @section page_iface_xdg_wm_base_api API - * See @ref iface_xdg_wm_base. - */ -/** - * @defgroup iface_xdg_wm_base The xdg_wm_base interface - * - * The xdg_wm_base interface is exposed as a global object enabling clients - * to turn their wl_surfaces into windows in a desktop environment. It - * defines the basic functionality needed for clients and the compositor to - * create windows that can be dragged, resized, maximized, etc, as well as - * creating transient windows such as popup menus. - */ -extern const struct wl_interface xdg_wm_base_interface; -#endif -#ifndef XDG_POSITIONER_INTERFACE -#define XDG_POSITIONER_INTERFACE -/** - * @page page_iface_xdg_positioner xdg_positioner - * @section page_iface_xdg_positioner_desc Description - * - * The xdg_positioner provides a collection of rules for the placement of a - * child surface relative to a parent surface. Rules can be defined to ensure - * the child surface remains within the visible area's borders, and to - * specify how the child surface changes its position, such as sliding along - * an axis, or flipping around a rectangle. These positioner-created rules are - * constrained by the requirement that a child surface must intersect with or - * be at least partially adjacent to its parent surface. - * - * See the various requests for details about possible rules. - * - * At the time of the request, the compositor makes a copy of the rules - * specified by the xdg_positioner. Thus, after the request is complete the - * xdg_positioner object can be destroyed or reused; further changes to the - * object will have no effect on previous usages. - * - * For an xdg_positioner object to be considered complete, it must have a - * non-zero size set by set_size, and a non-zero anchor rectangle set by - * set_anchor_rect. Passing an incomplete xdg_positioner object when - * positioning a surface raises an invalid_positioner error. - * @section page_iface_xdg_positioner_api API - * See @ref iface_xdg_positioner. - */ -/** - * @defgroup iface_xdg_positioner The xdg_positioner interface - * - * The xdg_positioner provides a collection of rules for the placement of a - * child surface relative to a parent surface. Rules can be defined to ensure - * the child surface remains within the visible area's borders, and to - * specify how the child surface changes its position, such as sliding along - * an axis, or flipping around a rectangle. These positioner-created rules are - * constrained by the requirement that a child surface must intersect with or - * be at least partially adjacent to its parent surface. - * - * See the various requests for details about possible rules. - * - * At the time of the request, the compositor makes a copy of the rules - * specified by the xdg_positioner. Thus, after the request is complete the - * xdg_positioner object can be destroyed or reused; further changes to the - * object will have no effect on previous usages. - * - * For an xdg_positioner object to be considered complete, it must have a - * non-zero size set by set_size, and a non-zero anchor rectangle set by - * set_anchor_rect. Passing an incomplete xdg_positioner object when - * positioning a surface raises an invalid_positioner error. - */ -extern const struct wl_interface xdg_positioner_interface; -#endif -#ifndef XDG_SURFACE_INTERFACE -#define XDG_SURFACE_INTERFACE -/** - * @page page_iface_xdg_surface xdg_surface - * @section page_iface_xdg_surface_desc Description - * - * An interface that may be implemented by a wl_surface, for - * implementations that provide a desktop-style user interface. - * - * It provides a base set of functionality required to construct user - * interface elements requiring management by the compositor, such as - * toplevel windows, menus, etc. The types of functionality are split into - * xdg_surface roles. - * - * Creating an xdg_surface does not set the role for a wl_surface. In order - * to map an xdg_surface, the client must create a role-specific object - * using, e.g., get_toplevel, get_popup. The wl_surface for any given - * xdg_surface can have at most one role, and may not be assigned any role - * not based on xdg_surface. - * - * A role must be assigned before any other requests are made to the - * xdg_surface object. - * - * The client must call wl_surface.commit on the corresponding wl_surface - * for the xdg_surface state to take effect. - * - * Creating an xdg_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 xdg_surface.configure call must - * also be treated as errors. - * - * After creating a role-specific object and setting it up (e.g. by sending - * the title, app ID, size constraints, parent, etc), the client must - * perform an initial commit without any buffer attached. The compositor - * will reply with initial wl_surface state such as - * wl_surface.preferred_buffer_scale followed by an xdg_surface.configure - * event. The client must acknowledge it and is then allowed to attach a - * buffer to map the surface. - * - * Mapping an xdg_surface-based role surface is defined as making it - * possible for the surface to be shown by the compositor. Note that - * a mapped surface is not guaranteed to be visible once it is mapped. - * - * For an xdg_surface to be mapped by the compositor, the following - * conditions must be met: - * (1) the client has assigned an xdg_surface-based role to the surface - * (2) the client has set and committed the xdg_surface state and the - * role-dependent state to the surface - * (3) the client has committed a buffer to the surface - * - * A newly-unmapped surface is considered to have met condition (1) out - * of the 3 required conditions for mapping a surface if its role surface - * has not been destroyed, i.e. the client must perform the initial commit - * again before attaching a buffer. - * @section page_iface_xdg_surface_api API - * See @ref iface_xdg_surface. - */ -/** - * @defgroup iface_xdg_surface The xdg_surface interface - * - * An interface that may be implemented by a wl_surface, for - * implementations that provide a desktop-style user interface. - * - * It provides a base set of functionality required to construct user - * interface elements requiring management by the compositor, such as - * toplevel windows, menus, etc. The types of functionality are split into - * xdg_surface roles. - * - * Creating an xdg_surface does not set the role for a wl_surface. In order - * to map an xdg_surface, the client must create a role-specific object - * using, e.g., get_toplevel, get_popup. The wl_surface for any given - * xdg_surface can have at most one role, and may not be assigned any role - * not based on xdg_surface. - * - * A role must be assigned before any other requests are made to the - * xdg_surface object. - * - * The client must call wl_surface.commit on the corresponding wl_surface - * for the xdg_surface state to take effect. - * - * Creating an xdg_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 xdg_surface.configure call must - * also be treated as errors. - * - * After creating a role-specific object and setting it up (e.g. by sending - * the title, app ID, size constraints, parent, etc), the client must - * perform an initial commit without any buffer attached. The compositor - * will reply with initial wl_surface state such as - * wl_surface.preferred_buffer_scale followed by an xdg_surface.configure - * event. The client must acknowledge it and is then allowed to attach a - * buffer to map the surface. - * - * Mapping an xdg_surface-based role surface is defined as making it - * possible for the surface to be shown by the compositor. Note that - * a mapped surface is not guaranteed to be visible once it is mapped. - * - * For an xdg_surface to be mapped by the compositor, the following - * conditions must be met: - * (1) the client has assigned an xdg_surface-based role to the surface - * (2) the client has set and committed the xdg_surface state and the - * role-dependent state to the surface - * (3) the client has committed a buffer to the surface - * - * A newly-unmapped surface is considered to have met condition (1) out - * of the 3 required conditions for mapping a surface if its role surface - * has not been destroyed, i.e. the client must perform the initial commit - * again before attaching a buffer. - */ -extern const struct wl_interface xdg_surface_interface; -#endif -#ifndef XDG_TOPLEVEL_INTERFACE -#define XDG_TOPLEVEL_INTERFACE -/** - * @page page_iface_xdg_toplevel xdg_toplevel - * @section page_iface_xdg_toplevel_desc Description - * - * This interface defines an xdg_surface role which allows a surface to, - * among other things, set window-like properties such as maximize, - * fullscreen, and minimize, set application-specific metadata like title and - * id, and well as trigger user interactive operations such as interactive - * resize and move. - * - * An xdg_toplevel by default is responsible for providing the full intended - * visual representation of the toplevel, which depending on the window - * state, may mean things like a title bar, window controls and drop shadow. - * - * Unmapping an xdg_toplevel means that the surface cannot be shown - * by the compositor until it is explicitly mapped again. - * All active operations (e.g., move, resize) are canceled and all - * attributes (e.g. title, state, stacking, ...) are discarded for - * an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to - * the state it had right after xdg_surface.get_toplevel. The client - * can re-map the toplevel by performing a commit without any buffer - * attached, waiting for a configure event and handling it as usual (see - * xdg_surface description). - * - * Attaching a null buffer to a toplevel unmaps the surface. - * @section page_iface_xdg_toplevel_api API - * See @ref iface_xdg_toplevel. - */ -/** - * @defgroup iface_xdg_toplevel The xdg_toplevel interface - * - * This interface defines an xdg_surface role which allows a surface to, - * among other things, set window-like properties such as maximize, - * fullscreen, and minimize, set application-specific metadata like title and - * id, and well as trigger user interactive operations such as interactive - * resize and move. - * - * An xdg_toplevel by default is responsible for providing the full intended - * visual representation of the toplevel, which depending on the window - * state, may mean things like a title bar, window controls and drop shadow. - * - * Unmapping an xdg_toplevel means that the surface cannot be shown - * by the compositor until it is explicitly mapped again. - * All active operations (e.g., move, resize) are canceled and all - * attributes (e.g. title, state, stacking, ...) are discarded for - * an xdg_toplevel surface when it is unmapped. The xdg_toplevel returns to - * the state it had right after xdg_surface.get_toplevel. The client - * can re-map the toplevel by performing a commit without any buffer - * attached, waiting for a configure event and handling it as usual (see - * xdg_surface description). - * - * Attaching a null buffer to a toplevel unmaps the surface. - */ -extern const struct wl_interface xdg_toplevel_interface; -#endif -#ifndef XDG_POPUP_INTERFACE -#define XDG_POPUP_INTERFACE -/** - * @page page_iface_xdg_popup xdg_popup - * @section page_iface_xdg_popup_desc Description - * - * A popup surface is a short-lived, temporary surface. It can be used to - * implement for example menus, popovers, tooltips and other similar user - * interface concepts. - * - * A popup can be made to take an explicit grab. See xdg_popup.grab for - * details. - * - * When the popup is dismissed, a popup_done event will be sent out, and at - * the same time the surface will be unmapped. See the xdg_popup.popup_done - * event for details. - * - * Explicitly destroying the xdg_popup object will also dismiss the popup and - * unmap the surface. Clients that want to dismiss the popup when another - * surface of their own is clicked should dismiss the popup using the destroy - * request. - * - * A newly created xdg_popup will be stacked on top of all previously created - * xdg_popup surfaces associated with the same xdg_toplevel. - * - * The parent of an xdg_popup must be mapped (see the xdg_surface - * description) before the xdg_popup itself. - * - * The client must call wl_surface.commit on the corresponding wl_surface - * for the xdg_popup state to take effect. - * @section page_iface_xdg_popup_api API - * See @ref iface_xdg_popup. - */ -/** - * @defgroup iface_xdg_popup The xdg_popup interface - * - * A popup surface is a short-lived, temporary surface. It can be used to - * implement for example menus, popovers, tooltips and other similar user - * interface concepts. - * - * A popup can be made to take an explicit grab. See xdg_popup.grab for - * details. - * - * When the popup is dismissed, a popup_done event will be sent out, and at - * the same time the surface will be unmapped. See the xdg_popup.popup_done - * event for details. - * - * Explicitly destroying the xdg_popup object will also dismiss the popup and - * unmap the surface. Clients that want to dismiss the popup when another - * surface of their own is clicked should dismiss the popup using the destroy - * request. - * - * A newly created xdg_popup will be stacked on top of all previously created - * xdg_popup surfaces associated with the same xdg_toplevel. - * - * The parent of an xdg_popup must be mapped (see the xdg_surface - * description) before the xdg_popup itself. - * - * The client must call wl_surface.commit on the corresponding wl_surface - * for the xdg_popup state to take effect. - */ -extern const struct wl_interface xdg_popup_interface; -#endif - -#ifndef XDG_WM_BASE_ERROR_ENUM -#define XDG_WM_BASE_ERROR_ENUM -enum xdg_wm_base_error { - /** - * given wl_surface has another role - */ - XDG_WM_BASE_ERROR_ROLE = 0, - /** - * xdg_wm_base was destroyed before children - */ - XDG_WM_BASE_ERROR_DEFUNCT_SURFACES = 1, - /** - * the client tried to map or destroy a non-topmost popup - */ - XDG_WM_BASE_ERROR_NOT_THE_TOPMOST_POPUP = 2, - /** - * the client specified an invalid popup parent surface - */ - XDG_WM_BASE_ERROR_INVALID_POPUP_PARENT = 3, - /** - * the client provided an invalid surface state - */ - XDG_WM_BASE_ERROR_INVALID_SURFACE_STATE = 4, - /** - * the client provided an invalid positioner - */ - XDG_WM_BASE_ERROR_INVALID_POSITIONER = 5, - /** - * the client didn’t respond to a ping event in time - */ - XDG_WM_BASE_ERROR_UNRESPONSIVE = 6, -}; -#endif /* XDG_WM_BASE_ERROR_ENUM */ - -/** - * @ingroup iface_xdg_wm_base - * @struct xdg_wm_base_listener - */ -struct xdg_wm_base_listener { - /** - * check if the client is alive - * - * The ping event asks the client if it's still alive. Pass the - * serial specified in the event back to the compositor by sending - * a "pong" request back with the specified serial. See - * xdg_wm_base.pong. - * - * Compositors can use this to determine if the client is still - * alive. It's unspecified what will happen if the client doesn't - * respond to the ping request, or in what timeframe. Clients - * should try to respond in a reasonable amount of time. The - * “unresponsive” error is provided for compositors that wish - * to disconnect unresponsive clients. - * - * A compositor is free to ping in any way it wants, but a client - * must always respond to any xdg_wm_base object it created. - * @param serial pass this to the pong request - */ - void (*ping)(void *data, - struct xdg_wm_base *xdg_wm_base, - uint32_t serial); -}; - -/** - * @ingroup iface_xdg_wm_base - */ -static inline int -xdg_wm_base_add_listener(struct xdg_wm_base *xdg_wm_base, - const struct xdg_wm_base_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) xdg_wm_base, - (void (**)(void)) listener, data); -} - -#define XDG_WM_BASE_DESTROY 0 -#define XDG_WM_BASE_CREATE_POSITIONER 1 -#define XDG_WM_BASE_GET_XDG_SURFACE 2 -#define XDG_WM_BASE_PONG 3 - -/** - * @ingroup iface_xdg_wm_base - */ -#define XDG_WM_BASE_PING_SINCE_VERSION 1 - -/** - * @ingroup iface_xdg_wm_base - */ -#define XDG_WM_BASE_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_wm_base - */ -#define XDG_WM_BASE_CREATE_POSITIONER_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_wm_base - */ -#define XDG_WM_BASE_GET_XDG_SURFACE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_wm_base - */ -#define XDG_WM_BASE_PONG_SINCE_VERSION 1 - -/** @ingroup iface_xdg_wm_base */ -static inline void -xdg_wm_base_set_user_data(struct xdg_wm_base *xdg_wm_base, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) xdg_wm_base, user_data); -} - -/** @ingroup iface_xdg_wm_base */ -static inline void * -xdg_wm_base_get_user_data(struct xdg_wm_base *xdg_wm_base) -{ - return wl_proxy_get_user_data((struct wl_proxy *) xdg_wm_base); -} - -static inline uint32_t -xdg_wm_base_get_version(struct xdg_wm_base *xdg_wm_base) -{ - return wl_proxy_get_version((struct wl_proxy *) xdg_wm_base); -} - -/** - * @ingroup iface_xdg_wm_base - * - * Destroy this xdg_wm_base object. - * - * Destroying a bound xdg_wm_base object while there are surfaces - * still alive created by this xdg_wm_base object instance is illegal - * and will result in a defunct_surfaces error. - */ -static inline void -xdg_wm_base_destroy(struct xdg_wm_base *xdg_wm_base) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_wm_base, - XDG_WM_BASE_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_wm_base), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_xdg_wm_base - * - * Create a positioner object. A positioner object is used to position - * surfaces relative to some parent surface. See the interface description - * and xdg_surface.get_popup for details. - */ -static inline struct xdg_positioner * -xdg_wm_base_create_positioner(struct xdg_wm_base *xdg_wm_base) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_wm_base, - XDG_WM_BASE_CREATE_POSITIONER, &xdg_positioner_interface, wl_proxy_get_version((struct wl_proxy *) xdg_wm_base), 0, NULL); - - return (struct xdg_positioner *) id; -} - -/** - * @ingroup iface_xdg_wm_base - * - * This creates an xdg_surface for the given surface. An xdg_surface is - * used as basis to define a role to a given surface, such as xdg_toplevel - * or xdg_popup. It also manages functionality shared between xdg_surface - * based surface roles. - * - * While xdg_surface itself is not a role, the corresponding surface may - * only be assigned a role extending xdg_surface, such as xdg_toplevel or - * xdg_popup. It is illegal to create an xdg_surface for a wl_surface which - * already has anassigned role and this will result in a role error. - * - * See the documentation of xdg_surface for more details about what an - * xdg_surface is and how it is used. - */ -static inline struct xdg_surface * -xdg_wm_base_get_xdg_surface(struct xdg_wm_base *xdg_wm_base, struct wl_surface *surface) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_wm_base, - XDG_WM_BASE_GET_XDG_SURFACE, &xdg_surface_interface, wl_proxy_get_version((struct wl_proxy *) xdg_wm_base), 0, NULL, surface); - - return (struct xdg_surface *) id; -} - -/** - * @ingroup iface_xdg_wm_base - * - * A client must respond to a ping event with a pong request or - * the client may be deemed unresponsive. See xdg_wm_base.ping - * and xdg_wm_base.error.unresponsive. - */ -static inline void -xdg_wm_base_pong(struct xdg_wm_base *xdg_wm_base, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_wm_base, - XDG_WM_BASE_PONG, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_wm_base), 0, serial); -} - -#ifndef XDG_POSITIONER_ERROR_ENUM -#define XDG_POSITIONER_ERROR_ENUM -enum xdg_positioner_error { - /** - * invalid input provided - */ - XDG_POSITIONER_ERROR_INVALID_INPUT = 0, -}; -#endif /* XDG_POSITIONER_ERROR_ENUM */ - -#ifndef XDG_POSITIONER_ANCHOR_ENUM -#define XDG_POSITIONER_ANCHOR_ENUM -enum xdg_positioner_anchor { - XDG_POSITIONER_ANCHOR_NONE = 0, - XDG_POSITIONER_ANCHOR_TOP = 1, - XDG_POSITIONER_ANCHOR_BOTTOM = 2, - XDG_POSITIONER_ANCHOR_LEFT = 3, - XDG_POSITIONER_ANCHOR_RIGHT = 4, - XDG_POSITIONER_ANCHOR_TOP_LEFT = 5, - XDG_POSITIONER_ANCHOR_BOTTOM_LEFT = 6, - XDG_POSITIONER_ANCHOR_TOP_RIGHT = 7, - XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT = 8, -}; -#endif /* XDG_POSITIONER_ANCHOR_ENUM */ - -#ifndef XDG_POSITIONER_GRAVITY_ENUM -#define XDG_POSITIONER_GRAVITY_ENUM -enum xdg_positioner_gravity { - XDG_POSITIONER_GRAVITY_NONE = 0, - XDG_POSITIONER_GRAVITY_TOP = 1, - XDG_POSITIONER_GRAVITY_BOTTOM = 2, - XDG_POSITIONER_GRAVITY_LEFT = 3, - XDG_POSITIONER_GRAVITY_RIGHT = 4, - XDG_POSITIONER_GRAVITY_TOP_LEFT = 5, - XDG_POSITIONER_GRAVITY_BOTTOM_LEFT = 6, - XDG_POSITIONER_GRAVITY_TOP_RIGHT = 7, - XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT = 8, -}; -#endif /* XDG_POSITIONER_GRAVITY_ENUM */ - -#ifndef XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM -#define XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM -/** - * @ingroup iface_xdg_positioner - * constraint adjustments - * - * The constraint adjustment value define ways the compositor will adjust - * the position of the surface, if the unadjusted position would result - * in the surface being partly constrained. - * - * Whether a surface is considered 'constrained' is left to the compositor - * to determine. For example, the surface may be partly outside the - * compositor's defined 'work area', thus necessitating the child surface's - * position be adjusted until it is entirely inside the work area. - * - * The adjustments can be combined, according to a defined precedence: 1) - * Flip, 2) Slide, 3) Resize. - */ -enum xdg_positioner_constraint_adjustment { - /** - * don't move the child surface when constrained - * - * Don't alter the surface position even if it is constrained on - * some axis, for example partially outside the edge of an output. - */ - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE = 0, - /** - * move along the x axis until unconstrained - * - * Slide the surface along the x axis until it is no longer - * constrained. - * - * First try to slide towards the direction of the gravity on the x - * axis until either the edge in the opposite direction of the - * gravity is unconstrained or the edge in the direction of the - * gravity is constrained. - * - * Then try to slide towards the opposite direction of the gravity - * on the x axis until either the edge in the direction of the - * gravity is unconstrained or the edge in the opposite direction - * of the gravity is constrained. - */ - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X = 1, - /** - * move along the y axis until unconstrained - * - * Slide the surface along the y axis until it is no longer - * constrained. - * - * First try to slide towards the direction of the gravity on the y - * axis until either the edge in the opposite direction of the - * gravity is unconstrained or the edge in the direction of the - * gravity is constrained. - * - * Then try to slide towards the opposite direction of the gravity - * on the y axis until either the edge in the direction of the - * gravity is unconstrained or the edge in the opposite direction - * of the gravity is constrained. - */ - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y = 2, - /** - * invert the anchor and gravity on the x axis - * - * Invert the anchor and gravity on the x axis if the surface is - * constrained on the x axis. For example, if the left edge of the - * surface is constrained, the gravity is 'left' and the anchor is - * 'left', change the gravity to 'right' and the anchor to 'right'. - * - * If the adjusted position also ends up being constrained, the - * resulting position of the flip_x adjustment will be the one - * before the adjustment. - */ - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X = 4, - /** - * invert the anchor and gravity on the y axis - * - * Invert the anchor and gravity on the y axis if the surface is - * constrained on the y axis. For example, if the bottom edge of - * the surface is constrained, the gravity is 'bottom' and the - * anchor is 'bottom', change the gravity to 'top' and the anchor - * to 'top'. - * - * The adjusted position is calculated given the original anchor - * rectangle and offset, but with the new flipped anchor and - * gravity values. - * - * If the adjusted position also ends up being constrained, the - * resulting position of the flip_y adjustment will be the one - * before the adjustment. - */ - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y = 8, - /** - * horizontally resize the surface - * - * Resize the surface horizontally so that it is completely - * unconstrained. - */ - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X = 16, - /** - * vertically resize the surface - * - * Resize the surface vertically so that it is completely - * unconstrained. - */ - XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y = 32, -}; -#endif /* XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_ENUM */ - -#define XDG_POSITIONER_DESTROY 0 -#define XDG_POSITIONER_SET_SIZE 1 -#define XDG_POSITIONER_SET_ANCHOR_RECT 2 -#define XDG_POSITIONER_SET_ANCHOR 3 -#define XDG_POSITIONER_SET_GRAVITY 4 -#define XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT 5 -#define XDG_POSITIONER_SET_OFFSET 6 -#define XDG_POSITIONER_SET_REACTIVE 7 -#define XDG_POSITIONER_SET_PARENT_SIZE 8 -#define XDG_POSITIONER_SET_PARENT_CONFIGURE 9 - - -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_SIZE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_ANCHOR_RECT_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_ANCHOR_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_GRAVITY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_OFFSET_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_REACTIVE_SINCE_VERSION 3 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_PARENT_SIZE_SINCE_VERSION 3 -/** - * @ingroup iface_xdg_positioner - */ -#define XDG_POSITIONER_SET_PARENT_CONFIGURE_SINCE_VERSION 3 - -/** @ingroup iface_xdg_positioner */ -static inline void -xdg_positioner_set_user_data(struct xdg_positioner *xdg_positioner, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) xdg_positioner, user_data); -} - -/** @ingroup iface_xdg_positioner */ -static inline void * -xdg_positioner_get_user_data(struct xdg_positioner *xdg_positioner) -{ - return wl_proxy_get_user_data((struct wl_proxy *) xdg_positioner); -} - -static inline uint32_t -xdg_positioner_get_version(struct xdg_positioner *xdg_positioner) -{ - return wl_proxy_get_version((struct wl_proxy *) xdg_positioner); -} - -/** - * @ingroup iface_xdg_positioner - * - * Notify the compositor that the xdg_positioner will no longer be used. - */ -static inline void -xdg_positioner_destroy(struct xdg_positioner *xdg_positioner) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_xdg_positioner - * - * Set the size of the surface that is to be positioned with the positioner - * object. The size is in surface-local coordinates and corresponds to the - * window geometry. See xdg_surface.set_window_geometry. - * - * If a zero or negative size is set the invalid_input error is raised. - */ -static inline void -xdg_positioner_set_size(struct xdg_positioner *xdg_positioner, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_SIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, width, height); -} - -/** - * @ingroup iface_xdg_positioner - * - * Specify the anchor rectangle within the parent surface that the child - * surface will be placed relative to. The rectangle is relative to the - * window geometry as defined by xdg_surface.set_window_geometry of the - * parent surface. - * - * When the xdg_positioner object is used to position a child surface, the - * anchor rectangle may not extend outside the window geometry of the - * positioned child's parent surface. - * - * If a negative size is set the invalid_input error is raised. - */ -static inline void -xdg_positioner_set_anchor_rect(struct xdg_positioner *xdg_positioner, int32_t x, int32_t y, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_ANCHOR_RECT, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, x, y, width, height); -} - -/** - * @ingroup iface_xdg_positioner - * - * Defines the anchor point for the anchor rectangle. The specified anchor - * is used to derive an anchor point that the child surface will be - * positioned relative to. If a corner anchor is set (e.g. 'top_left' or - * 'bottom_right'), the anchor point will be at the specified corner; - * otherwise, the derived anchor point will be centered on the specified - * edge, or in the center of the anchor rectangle if no edge is specified. - */ -static inline void -xdg_positioner_set_anchor(struct xdg_positioner *xdg_positioner, uint32_t anchor) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_ANCHOR, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, anchor); -} - -/** - * @ingroup iface_xdg_positioner - * - * Defines in what direction a surface should be positioned, relative to - * the anchor point of the parent surface. If a corner gravity is - * specified (e.g. 'bottom_right' or 'top_left'), then the child surface - * will be placed towards the specified gravity; otherwise, the child - * surface will be centered over the anchor point on any axis that had no - * gravity specified. If the gravity is not in the ‘gravity’ enum, an - * invalid_input error is raised. - */ -static inline void -xdg_positioner_set_gravity(struct xdg_positioner *xdg_positioner, uint32_t gravity) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_GRAVITY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, gravity); -} - -/** - * @ingroup iface_xdg_positioner - * - * Specify how the window should be positioned if the originally intended - * position caused the surface to be constrained, meaning at least - * partially outside positioning boundaries set by the compositor. The - * adjustment is set by constructing a bitmask describing the adjustment to - * be made when the surface is constrained on that axis. - * - * If no bit for one axis is set, the compositor will assume that the child - * surface should not change its position on that axis when constrained. - * - * If more than one bit for one axis is set, the order of how adjustments - * are applied is specified in the corresponding adjustment descriptions. - * - * The default adjustment is none. - */ -static inline void -xdg_positioner_set_constraint_adjustment(struct xdg_positioner *xdg_positioner, uint32_t constraint_adjustment) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, constraint_adjustment); -} - -/** - * @ingroup iface_xdg_positioner - * - * Specify the surface position offset relative to the position of the - * anchor on the anchor rectangle and the anchor on the surface. For - * example if the anchor of the anchor rectangle is at (x, y), the surface - * has the gravity bottom|right, and the offset is (ox, oy), the calculated - * surface position will be (x + ox, y + oy). The offset position of the - * surface is the one used for constraint testing. See - * set_constraint_adjustment. - * - * An example use case is placing a popup menu on top of a user interface - * element, while aligning the user interface element of the parent surface - * with some user interface element placed somewhere in the popup surface. - */ -static inline void -xdg_positioner_set_offset(struct xdg_positioner *xdg_positioner, int32_t x, int32_t y) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_OFFSET, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, x, y); -} - -/** - * @ingroup iface_xdg_positioner - * - * When set reactive, the surface is reconstrained if the conditions used - * for constraining changed, e.g. the parent window moved. - * - * If the conditions changed and the popup was reconstrained, an - * xdg_popup.configure event is sent with updated geometry, followed by an - * xdg_surface.configure event. - */ -static inline void -xdg_positioner_set_reactive(struct xdg_positioner *xdg_positioner) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_REACTIVE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0); -} - -/** - * @ingroup iface_xdg_positioner - * - * Set the parent window geometry the compositor should use when - * positioning the popup. The compositor may use this information to - * determine the future state the popup should be constrained using. If - * this doesn't match the dimension of the parent the popup is eventually - * positioned against, the behavior is undefined. - * - * The arguments are given in the surface-local coordinate space. - */ -static inline void -xdg_positioner_set_parent_size(struct xdg_positioner *xdg_positioner, int32_t parent_width, int32_t parent_height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_PARENT_SIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, parent_width, parent_height); -} - -/** - * @ingroup iface_xdg_positioner - * - * Set the serial of an xdg_surface.configure event this positioner will be - * used in response to. The compositor may use this information together - * with set_parent_size to determine what future state the popup should be - * constrained using. - */ -static inline void -xdg_positioner_set_parent_configure(struct xdg_positioner *xdg_positioner, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_positioner, - XDG_POSITIONER_SET_PARENT_CONFIGURE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_positioner), 0, serial); -} - -#ifndef XDG_SURFACE_ERROR_ENUM -#define XDG_SURFACE_ERROR_ENUM -enum xdg_surface_error { - /** - * Surface was not fully constructed - */ - XDG_SURFACE_ERROR_NOT_CONSTRUCTED = 1, - /** - * Surface was already constructed - */ - XDG_SURFACE_ERROR_ALREADY_CONSTRUCTED = 2, - /** - * Attaching a buffer to an unconfigured surface - */ - XDG_SURFACE_ERROR_UNCONFIGURED_BUFFER = 3, - /** - * Invalid serial number when acking a configure event - */ - XDG_SURFACE_ERROR_INVALID_SERIAL = 4, - /** - * Width or height was zero or negative - */ - XDG_SURFACE_ERROR_INVALID_SIZE = 5, - /** - * Surface was destroyed before its role object - */ - XDG_SURFACE_ERROR_DEFUNCT_ROLE_OBJECT = 6, -}; -#endif /* XDG_SURFACE_ERROR_ENUM */ - -/** - * @ingroup iface_xdg_surface - * @struct xdg_surface_listener - */ -struct xdg_surface_listener { - /** - * suggest a surface change - * - * The configure event marks the end of a configure sequence. A - * configure sequence is a set of one or more events configuring - * the state of the xdg_surface, including the final - * xdg_surface.configure event. - * - * Where applicable, xdg_surface surface roles will during a - * configure sequence extend this event as a latched state sent as - * events before the xdg_surface.configure event. Such events - * should be considered to make up a set of atomically applied - * configuration states, where the xdg_surface.configure commits - * the accumulated state. - * - * 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. - * - * If the client receives multiple configure events before it can - * respond to one, it is free to discard all but the last event it - * received. - * @param serial serial of the configure event - */ - void (*configure)(void *data, - struct xdg_surface *xdg_surface, - uint32_t serial); -}; - -/** - * @ingroup iface_xdg_surface - */ -static inline int -xdg_surface_add_listener(struct xdg_surface *xdg_surface, - const struct xdg_surface_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) xdg_surface, - (void (**)(void)) listener, data); -} - -#define XDG_SURFACE_DESTROY 0 -#define XDG_SURFACE_GET_TOPLEVEL 1 -#define XDG_SURFACE_GET_POPUP 2 -#define XDG_SURFACE_SET_WINDOW_GEOMETRY 3 -#define XDG_SURFACE_ACK_CONFIGURE 4 - -/** - * @ingroup iface_xdg_surface - */ -#define XDG_SURFACE_CONFIGURE_SINCE_VERSION 1 - -/** - * @ingroup iface_xdg_surface - */ -#define XDG_SURFACE_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_surface - */ -#define XDG_SURFACE_GET_TOPLEVEL_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_surface - */ -#define XDG_SURFACE_GET_POPUP_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_surface - */ -#define XDG_SURFACE_SET_WINDOW_GEOMETRY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_surface - */ -#define XDG_SURFACE_ACK_CONFIGURE_SINCE_VERSION 1 - -/** @ingroup iface_xdg_surface */ -static inline void -xdg_surface_set_user_data(struct xdg_surface *xdg_surface, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) xdg_surface, user_data); -} - -/** @ingroup iface_xdg_surface */ -static inline void * -xdg_surface_get_user_data(struct xdg_surface *xdg_surface) -{ - return wl_proxy_get_user_data((struct wl_proxy *) xdg_surface); -} - -static inline uint32_t -xdg_surface_get_version(struct xdg_surface *xdg_surface) -{ - return wl_proxy_get_version((struct wl_proxy *) xdg_surface); -} - -/** - * @ingroup iface_xdg_surface - * - * Destroy the xdg_surface object. An xdg_surface must only be destroyed - * after its role object has been destroyed, otherwise - * a defunct_role_object error is raised. - */ -static inline void -xdg_surface_destroy(struct xdg_surface *xdg_surface) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, - XDG_SURFACE_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_surface), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_xdg_surface - * - * This creates an xdg_toplevel object for the given xdg_surface and gives - * the associated wl_surface the xdg_toplevel role. - * - * See the documentation of xdg_toplevel for more details about what an - * xdg_toplevel is and how it is used. - */ -static inline struct xdg_toplevel * -xdg_surface_get_toplevel(struct xdg_surface *xdg_surface) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, - XDG_SURFACE_GET_TOPLEVEL, &xdg_toplevel_interface, wl_proxy_get_version((struct wl_proxy *) xdg_surface), 0, NULL); - - return (struct xdg_toplevel *) id; -} - -/** - * @ingroup iface_xdg_surface - * - * This creates an xdg_popup object for the given xdg_surface and gives - * the associated wl_surface the xdg_popup role. - * - * If null is passed as a parent, a parent surface must be specified using - * some other protocol, before committing the initial state. - * - * See the documentation of xdg_popup for more details about what an - * xdg_popup is and how it is used. - */ -static inline struct xdg_popup * -xdg_surface_get_popup(struct xdg_surface *xdg_surface, struct xdg_surface *parent, struct xdg_positioner *positioner) -{ - struct wl_proxy *id; - - id = wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, - XDG_SURFACE_GET_POPUP, &xdg_popup_interface, wl_proxy_get_version((struct wl_proxy *) xdg_surface), 0, NULL, parent, positioner); - - return (struct xdg_popup *) id; -} - -/** - * @ingroup iface_xdg_surface - * - * The window geometry of a surface is its "visible bounds" from the - * user's perspective. Client-side decorations often have invisible - * portions like drop-shadows which should be ignored for the - * purposes of aligning, placing and constraining windows. Note that - * in some situations, compositors may clip rendering to the window - * geometry, so the client should avoid putting functional elements - * outside of it. - * - * The window geometry is double-buffered state, see wl_surface.commit. - * - * When maintaining a position, the compositor should treat the (x, y) - * coordinate of the window geometry as the top left corner of the window. - * A client changing the (x, y) window geometry coordinate should in - * general not alter the position of the window. - * - * Once the window geometry of the surface is set, it is not possible to - * unset it, and it will remain the same until set_window_geometry is - * called again, even if a new subsurface or buffer is attached. - * - * If never set, the value is the full bounds of the surface, - * including any subsurfaces. This updates dynamically on every - * commit. This unset is meant for extremely simple clients. - * - * The arguments are given in the surface-local coordinate space of - * the wl_surface associated with this xdg_surface, and may extend outside - * of the wl_surface itself to mark parts of the subsurface tree as part of - * the window geometry. - * - * When applied, the effective window geometry will be the set window - * geometry clamped to the bounding rectangle of the combined - * geometry of the surface of the xdg_surface and the associated - * subsurfaces. - * - * The effective geometry will not be recalculated unless a new call to - * set_window_geometry is done and the new pending surface state is - * subsequently applied. - * - * The width and height of the effective window geometry must be - * greater than zero. Setting an invalid size will raise an - * invalid_size error. - */ -static inline void -xdg_surface_set_window_geometry(struct xdg_surface *xdg_surface, int32_t x, int32_t y, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, - XDG_SURFACE_SET_WINDOW_GEOMETRY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_surface), 0, x, y, width, height); -} - -/** - * @ingroup iface_xdg_surface - * - * 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. - * - * For instance, for toplevel surfaces the compositor might use this - * information to move a surface to the top left only when the client has - * drawn itself for the maximized or fullscreen state. - * - * If the client receives multiple configure events before it - * can respond to one, it only has to ack the last configure event. - * Acking a configure event that was never sent raises an invalid_serial - * error. - * - * 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. - * - * Sending an ack_configure request consumes the serial number sent with - * the request, as well as serial numbers sent by all configure events - * sent on this xdg_surface prior to the configure event referenced by - * the committed serial. - * - * It is an error to issue multiple ack_configure requests referencing a - * serial from the same configure event, or to issue an ack_configure - * request referencing a serial from a configure event issued before the - * event identified by the last ack_configure request for the same - * xdg_surface. Doing so will raise an invalid_serial error. - */ -static inline void -xdg_surface_ack_configure(struct xdg_surface *xdg_surface, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_surface, - XDG_SURFACE_ACK_CONFIGURE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_surface), 0, serial); -} - -#ifndef XDG_TOPLEVEL_ERROR_ENUM -#define XDG_TOPLEVEL_ERROR_ENUM -enum xdg_toplevel_error { - /** - * provided value is not a valid variant of the resize_edge enum - */ - XDG_TOPLEVEL_ERROR_INVALID_RESIZE_EDGE = 0, - /** - * invalid parent toplevel - */ - XDG_TOPLEVEL_ERROR_INVALID_PARENT = 1, - /** - * client provided an invalid min or max size - */ - XDG_TOPLEVEL_ERROR_INVALID_SIZE = 2, -}; -#endif /* XDG_TOPLEVEL_ERROR_ENUM */ - -#ifndef XDG_TOPLEVEL_RESIZE_EDGE_ENUM -#define XDG_TOPLEVEL_RESIZE_EDGE_ENUM -/** - * @ingroup iface_xdg_toplevel - * edge values for resizing - * - * These values are used to indicate which edge of a surface - * is being dragged in a resize operation. - */ -enum xdg_toplevel_resize_edge { - XDG_TOPLEVEL_RESIZE_EDGE_NONE = 0, - XDG_TOPLEVEL_RESIZE_EDGE_TOP = 1, - XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM = 2, - XDG_TOPLEVEL_RESIZE_EDGE_LEFT = 4, - XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT = 5, - XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT = 6, - XDG_TOPLEVEL_RESIZE_EDGE_RIGHT = 8, - XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT = 9, - XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT = 10, -}; -#endif /* XDG_TOPLEVEL_RESIZE_EDGE_ENUM */ - -#ifndef XDG_TOPLEVEL_STATE_ENUM -#define XDG_TOPLEVEL_STATE_ENUM -/** - * @ingroup iface_xdg_toplevel - * types of state on the surface - * - * The different state values used on the surface. This is designed for - * state values like maximized, fullscreen. It is paired with the - * configure event to ensure that both the client and the compositor - * setting the state can be synchronized. - * - * States set in this way are double-buffered, see wl_surface.commit. - */ -enum xdg_toplevel_state { - /** - * the surface is maximized - * the surface is maximized - * - * The surface is maximized. The window geometry specified in the - * configure event must be obeyed by the client, or the - * xdg_wm_base.invalid_surface_state error is raised. - * - * The client should draw without shadow or other decoration - * outside of the window geometry. - */ - XDG_TOPLEVEL_STATE_MAXIMIZED = 1, - /** - * the surface is fullscreen - * the surface is fullscreen - * - * The surface is fullscreen. The window geometry specified in - * the configure event is a maximum; the client cannot resize - * beyond it. For a surface to cover the whole fullscreened area, - * the geometry dimensions must be obeyed by the client. For more - * details, see xdg_toplevel.set_fullscreen. - */ - XDG_TOPLEVEL_STATE_FULLSCREEN = 2, - /** - * the surface is being resized - * the surface is being resized - * - * The surface is being resized. The window geometry specified in - * the configure event is a maximum; the client cannot resize - * beyond it. Clients that have aspect ratio or cell sizing - * configuration can use a smaller size, however. - */ - XDG_TOPLEVEL_STATE_RESIZING = 3, - /** - * the surface is now activated - * the surface is now activated - * - * Client window decorations should be painted as if the window - * is active. Do not assume this means that the window actually has - * keyboard or pointer focus. - */ - XDG_TOPLEVEL_STATE_ACTIVATED = 4, - /** - * the surface’s left edge is tiled - * - * The window is currently in a tiled layout and the left edge is - * considered to be adjacent to another part of the tiling grid. - * - * The client should draw without shadow or other decoration - * outside of the window geometry on the left edge. - * @since 2 - */ - XDG_TOPLEVEL_STATE_TILED_LEFT = 5, - /** - * the surface’s right edge is tiled - * - * The window is currently in a tiled layout and the right edge - * is considered to be adjacent to another part of the tiling grid. - * - * The client should draw without shadow or other decoration - * outside of the window geometry on the right edge. - * @since 2 - */ - XDG_TOPLEVEL_STATE_TILED_RIGHT = 6, - /** - * the surface’s top edge is tiled - * - * The window is currently in a tiled layout and the top edge is - * considered to be adjacent to another part of the tiling grid. - * - * The client should draw without shadow or other decoration - * outside of the window geometry on the top edge. - * @since 2 - */ - XDG_TOPLEVEL_STATE_TILED_TOP = 7, - /** - * the surface’s bottom edge is tiled - * - * The window is currently in a tiled layout and the bottom edge - * is considered to be adjacent to another part of the tiling grid. - * - * The client should draw without shadow or other decoration - * outside of the window geometry on the bottom edge. - * @since 2 - */ - XDG_TOPLEVEL_STATE_TILED_BOTTOM = 8, - /** - * surface repaint is suspended - * - * The surface is currently not ordinarily being repainted; for - * example because its content is occluded by another window, or - * its outputs are switched off due to screen locking. - * @since 6 - */ - XDG_TOPLEVEL_STATE_SUSPENDED = 9, - /** - * the surface’s left edge is constrained - * - * The left edge of the window is currently constrained, meaning - * it shouldn't attempt to resize from that edge. It can for - * example mean it's tiled next to a monitor edge on the - * constrained side of the window. - * @since 7 - */ - XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT = 10, - /** - * the surface’s right edge is constrained - * - * The right edge of the window is currently constrained, meaning - * it shouldn't attempt to resize from that edge. It can for - * example mean it's tiled next to a monitor edge on the - * constrained side of the window. - * @since 7 - */ - XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT = 11, - /** - * the surface’s top edge is constrained - * - * The top edge of the window is currently constrained, meaning - * it shouldn't attempt to resize from that edge. It can for - * example mean it's tiled next to a monitor edge on the - * constrained side of the window. - * @since 7 - */ - XDG_TOPLEVEL_STATE_CONSTRAINED_TOP = 12, - /** - * the surface’s bottom edge is constrained - * - * The bottom edge of the window is currently constrained, - * meaning it shouldn't attempt to resize from that edge. It can - * for example mean it's tiled next to a monitor edge on the - * constrained side of the window. - * @since 7 - */ - XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM = 13, -}; -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_STATE_TILED_LEFT_SINCE_VERSION 2 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_STATE_TILED_RIGHT_SINCE_VERSION 2 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_STATE_TILED_TOP_SINCE_VERSION 2 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_STATE_TILED_BOTTOM_SINCE_VERSION 2 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_STATE_SUSPENDED_SINCE_VERSION 6 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_STATE_CONSTRAINED_LEFT_SINCE_VERSION 7 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_STATE_CONSTRAINED_RIGHT_SINCE_VERSION 7 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_STATE_CONSTRAINED_TOP_SINCE_VERSION 7 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_STATE_CONSTRAINED_BOTTOM_SINCE_VERSION 7 -#endif /* XDG_TOPLEVEL_STATE_ENUM */ - -#ifndef XDG_TOPLEVEL_WM_CAPABILITIES_ENUM -#define XDG_TOPLEVEL_WM_CAPABILITIES_ENUM -enum xdg_toplevel_wm_capabilities { - /** - * show_window_menu is available - */ - XDG_TOPLEVEL_WM_CAPABILITIES_WINDOW_MENU = 1, - /** - * set_maximized and unset_maximized are available - */ - XDG_TOPLEVEL_WM_CAPABILITIES_MAXIMIZE = 2, - /** - * set_fullscreen and unset_fullscreen are available - */ - XDG_TOPLEVEL_WM_CAPABILITIES_FULLSCREEN = 3, - /** - * set_minimized is available - */ - XDG_TOPLEVEL_WM_CAPABILITIES_MINIMIZE = 4, -}; -#endif /* XDG_TOPLEVEL_WM_CAPABILITIES_ENUM */ - -/** - * @ingroup iface_xdg_toplevel - * @struct xdg_toplevel_listener - */ -struct xdg_toplevel_listener { - /** - * suggest a surface change - * - * This configure event asks the client to resize its toplevel - * surface or to change its state. The configured state should not - * be applied immediately. See xdg_surface.configure for details. - * - * The width and height arguments specify a hint to the window - * about how its surface should be resized in window geometry - * coordinates. See set_window_geometry. - * - * If the width or height arguments are zero, it means the client - * should decide its own window dimension. This may happen when the - * compositor needs to configure the state of the surface but - * doesn't have any information about any previous or expected - * dimension. - * - * The states listed in the event specify how the width/height - * arguments should be interpreted, and possibly how it should be - * drawn. - * - * The states are sent as an array of 32-bit unsigned integers in - * native endianness. State values are defined in the state enum. - * - * Clients must send an ack_configure in response to this event. - * See xdg_surface.configure and xdg_surface.ack_configure for - * details. - * @param width suggested width of window - * @param height suggested height of window - * @param states suggested states of the window - */ - void (*configure)(void *data, - struct xdg_toplevel *xdg_toplevel, - int32_t width, - int32_t height, - struct wl_array *states); - /** - * surface wants to be closed - * - * The close event is sent by the compositor when the user wants - * the surface to be closed. This should be equivalent to the user - * clicking the close button in client-side decorations, if your - * application has any. - * - * This is only a request that the user intends to close the - * window. The client may choose to ignore this request, or show a - * dialog to ask the user to save their data, etc. - */ - void (*close)(void *data, - struct xdg_toplevel *xdg_toplevel); - /** - * recommended window geometry bounds - * - * The configure_bounds event may be sent prior to a - * xdg_toplevel.configure event to communicate the bounds a window - * geometry size is recommended to constrain to. - * - * The passed width and height are in surface coordinate space. If - * width and height are 0, it means bounds is unknown and - * equivalent to as if no configure_bounds event was ever sent for - * this surface. - * - * The bounds can for example correspond to the size of a monitor - * excluding any panels or other shell components, so that a - * surface isn't created in a way that it cannot fit. - * - * The bounds may change at any point, and in such a case, a new - * xdg_toplevel.configure_bounds will be sent, followed by - * xdg_toplevel.configure and xdg_surface.configure. - * @param width suggested maximum width of surface - * @param height suggested maximum height of surface - * @since 4 - */ - void (*configure_bounds)(void *data, - struct xdg_toplevel *xdg_toplevel, - int32_t width, - int32_t height); - /** - * compositor capabilities - * - * This event advertises the capabilities supported by the - * compositor. If a capability isn't supported, clients should hide - * or disable the UI elements that expose this functionality. For - * instance, if the compositor doesn't advertise support for - * minimized toplevels, a button triggering the set_minimized - * request should not be displayed. - * - * The compositor will ignore requests it doesn't support. For - * instance, a compositor which doesn't advertise support for - * minimized will ignore set_minimized requests. - * - * Compositors must send this event once before the first - * xdg_surface.configure event. When the capabilities change, - * compositors must send this event again and then send an - * xdg_surface.configure event. - * - * The configured state should not be applied immediately. See - * xdg_surface.configure for details. - * - * The capabilities are sent as an array of 32-bit unsigned - * integers in native endianness. Capability values are defined in - * the wm_capabilities enum. - * @param capabilities array of 32-bit capabilities - * @since 5 - */ - void (*wm_capabilities)(void *data, - struct xdg_toplevel *xdg_toplevel, - struct wl_array *capabilities); -}; - -/** - * @ingroup iface_xdg_toplevel - */ -static inline int -xdg_toplevel_add_listener(struct xdg_toplevel *xdg_toplevel, - const struct xdg_toplevel_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) xdg_toplevel, - (void (**)(void)) listener, data); -} - -#define XDG_TOPLEVEL_DESTROY 0 -#define XDG_TOPLEVEL_SET_PARENT 1 -#define XDG_TOPLEVEL_SET_TITLE 2 -#define XDG_TOPLEVEL_SET_APP_ID 3 -#define XDG_TOPLEVEL_SHOW_WINDOW_MENU 4 -#define XDG_TOPLEVEL_MOVE 5 -#define XDG_TOPLEVEL_RESIZE 6 -#define XDG_TOPLEVEL_SET_MAX_SIZE 7 -#define XDG_TOPLEVEL_SET_MIN_SIZE 8 -#define XDG_TOPLEVEL_SET_MAXIMIZED 9 -#define XDG_TOPLEVEL_UNSET_MAXIMIZED 10 -#define XDG_TOPLEVEL_SET_FULLSCREEN 11 -#define XDG_TOPLEVEL_UNSET_FULLSCREEN 12 -#define XDG_TOPLEVEL_SET_MINIMIZED 13 - -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_CONFIGURE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_CLOSE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_CONFIGURE_BOUNDS_SINCE_VERSION 4 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION 5 - -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_PARENT_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_TITLE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_APP_ID_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SHOW_WINDOW_MENU_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_MOVE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_RESIZE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_MAX_SIZE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_MIN_SIZE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_MAXIMIZED_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_UNSET_MAXIMIZED_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_FULLSCREEN_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_UNSET_FULLSCREEN_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_toplevel - */ -#define XDG_TOPLEVEL_SET_MINIMIZED_SINCE_VERSION 1 - -/** @ingroup iface_xdg_toplevel */ -static inline void -xdg_toplevel_set_user_data(struct xdg_toplevel *xdg_toplevel, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) xdg_toplevel, user_data); -} - -/** @ingroup iface_xdg_toplevel */ -static inline void * -xdg_toplevel_get_user_data(struct xdg_toplevel *xdg_toplevel) -{ - return wl_proxy_get_user_data((struct wl_proxy *) xdg_toplevel); -} - -static inline uint32_t -xdg_toplevel_get_version(struct xdg_toplevel *xdg_toplevel) -{ - return wl_proxy_get_version((struct wl_proxy *) xdg_toplevel); -} - -/** - * @ingroup iface_xdg_toplevel - * - * This request destroys the role surface and unmaps the surface; - * see "Unmapping" behavior in interface section for details. - */ -static inline void -xdg_toplevel_destroy(struct xdg_toplevel *xdg_toplevel) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Set the "parent" of this surface. This surface should be stacked - * above the parent surface and all other ancestor surfaces. - * - * Parent surfaces should be set on dialogs, toolboxes, or other - * "auxiliary" surfaces, so that the parent is raised when the dialog - * is raised. - * - * Setting a null parent for a child surface unsets its parent. Setting - * a null parent for a surface which currently has no parent is a no-op. - * - * Only mapped surfaces can have child surfaces. Setting a parent which - * is not mapped is equivalent to setting a null parent. If a surface - * becomes unmapped, its children's parent is set to the parent of - * the now-unmapped surface. If the now-unmapped surface has no parent, - * its children's parent is unset. If the now-unmapped surface becomes - * mapped again, its parent-child relationship is not restored. - * - * The parent toplevel must not be one of the child toplevel's - * descendants, and the parent must be different from the child toplevel, - * otherwise the invalid_parent protocol error is raised. - */ -static inline void -xdg_toplevel_set_parent(struct xdg_toplevel *xdg_toplevel, struct xdg_toplevel *parent) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_PARENT, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, parent); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Set a short title for the surface. - * - * This string may be used to identify the surface in a task bar, - * window list, or other user interface elements provided by the - * compositor. - * - * The string must be encoded in UTF-8. - */ -static inline void -xdg_toplevel_set_title(struct xdg_toplevel *xdg_toplevel, const char *title) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_TITLE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, title); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Set an application identifier for the surface. - * - * The app ID identifies the general class of applications to which - * the surface belongs. The compositor can use this to group multiple - * surfaces together, or to determine how to launch a new application. - * - * For D-Bus activatable applications, the app ID is used as the D-Bus - * service name. - * - * The compositor shell will try to group application surfaces together - * by their app ID. As a best practice, it is suggested to select app - * ID's that match the basename of the application's .desktop file. - * For example, "org.freedesktop.FooViewer" where the .desktop file is - * "org.freedesktop.FooViewer.desktop". - * - * Like other properties, a set_app_id request can be sent after the - * xdg_toplevel has been mapped to update the property. - * - * See the desktop-entry specification [0] for more details on - * application identifiers and how they relate to well-known D-Bus - * names and .desktop files. - * - * [0] https://standards.freedesktop.org/desktop-entry-spec/ - */ -static inline void -xdg_toplevel_set_app_id(struct xdg_toplevel *xdg_toplevel, const char *app_id) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_APP_ID, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, app_id); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Clients implementing client-side decorations might want to show - * a context menu when right-clicking on the decorations, giving the - * user a menu that they can use to maximize or minimize the window. - * - * This request asks the compositor to pop up such a window menu at - * the given position, relative to the local surface coordinates of - * the parent surface. There are no guarantees as to what menu items - * the window menu contains, or even if a window menu will be drawn - * at all. - * - * This request must be used in response to some sort of user action - * like a button press, key press, or touch down event. - */ -static inline void -xdg_toplevel_show_window_menu(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial, int32_t x, int32_t y) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SHOW_WINDOW_MENU, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, seat, serial, x, y); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Start an interactive, user-driven move of the surface. - * - * This request must be used in response to some sort of user action - * like a button press, key press, or touch down event. The passed - * serial is used to determine the type of interactive move (touch, - * pointer, etc). - * - * The server may ignore move requests depending on the state of - * the surface (e.g. fullscreen or maximized), or if the passed serial - * is no longer valid. - * - * If triggered, the surface will lose the focus of the device - * (wl_pointer, wl_touch, etc) used for the move. It is up to the - * compositor to visually indicate that the move is taking place, such as - * updating a pointer cursor, during the move. There is no guarantee - * that the device focus will return when the move is completed. - */ -static inline void -xdg_toplevel_move(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_MOVE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, seat, serial); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Start a user-driven, interactive resize of the surface. - * - * This request must be used in response to some sort of user action - * like a button press, key press, or touch down event. The passed - * serial is used to determine the type of interactive resize (touch, - * pointer, etc). - * - * The server may ignore resize requests depending on the state of - * the surface (e.g. fullscreen or maximized). - * - * If triggered, the client will receive configure events with the - * "resize" state enum value and the expected sizes. See the "resize" - * enum value for more details about what is required. The client - * must also acknowledge configure events using "ack_configure". After - * the resize is completed, the client will receive another "configure" - * event without the resize state. - * - * If triggered, the surface also will lose the focus of the device - * (wl_pointer, wl_touch, etc) used for the resize. It is up to the - * compositor to visually indicate that the resize is taking place, - * such as updating a pointer cursor, during the resize. There is no - * guarantee that the device focus will return when the resize is - * completed. - * - * The edges parameter specifies how the surface should be resized, and - * is one of the values of the resize_edge enum. Values not matching - * a variant of the enum will cause the invalid_resize_edge protocol error. - * The compositor may use this information to update the surface position - * for example when dragging the top left corner. The compositor may also - * use this information to adapt its behavior, e.g. choose an appropriate - * cursor image. - */ -static inline void -xdg_toplevel_resize(struct xdg_toplevel *xdg_toplevel, struct wl_seat *seat, uint32_t serial, uint32_t edges) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_RESIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, seat, serial, edges); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Set a maximum size for the window. - * - * The client can specify a maximum size so that the compositor does - * not try to configure the window beyond this size. - * - * The width and height arguments are in window geometry coordinates. - * See xdg_surface.set_window_geometry. - * - * Values set in this way are double-buffered, see wl_surface.commit. - * - * The compositor can use this information to allow or disallow - * different states like maximize or fullscreen and draw accurate - * animations. - * - * Similarly, a tiling window manager may use this information to - * place and resize client windows in a more effective way. - * - * The client should not rely on the compositor to obey the maximum - * size. The compositor may decide to ignore the values set by the - * client and request a larger size. - * - * If never set, or a value of zero in the request, means that the - * client has no expected maximum size in the given dimension. - * As a result, a client wishing to reset the maximum size - * to an unspecified state can use zero for width and height in the - * request. - * - * Requesting a maximum size to be smaller than the minimum size of - * a surface is illegal and will result in an invalid_size error. - * - * The width and height must be greater than or equal to zero. Using - * strictly negative values for width or height will result in an - * invalid_size error. - */ -static inline void -xdg_toplevel_set_max_size(struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_MAX_SIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, width, height); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Set a minimum size for the window. - * - * The client can specify a minimum size so that the compositor does - * not try to configure the window below this size. - * - * The width and height arguments are in window geometry coordinates. - * See xdg_surface.set_window_geometry. - * - * Values set in this way are double-buffered, see wl_surface.commit. - * - * The compositor can use this information to allow or disallow - * different states like maximize or fullscreen and draw accurate - * animations. - * - * Similarly, a tiling window manager may use this information to - * place and resize client windows in a more effective way. - * - * The client should not rely on the compositor to obey the minimum - * size. The compositor may decide to ignore the values set by the - * client and request a smaller size. - * - * If never set, or a value of zero in the request, means that the - * client has no expected minimum size in the given dimension. - * As a result, a client wishing to reset the minimum size - * to an unspecified state can use zero for width and height in the - * request. - * - * Requesting a minimum size to be larger than the maximum size of - * a surface is illegal and will result in an invalid_size error. - * - * The width and height must be greater than or equal to zero. Using - * strictly negative values for width and height will result in an - * invalid_size error. - */ -static inline void -xdg_toplevel_set_min_size(struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_MIN_SIZE, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, width, height); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Maximize the surface. - * - * After requesting that the surface should be maximized, the compositor - * will respond by emitting a configure event. Whether this configure - * actually sets the window maximized is subject to compositor policies. - * The client must then update its content, drawing in the configured - * state. The client must also acknowledge the configure when committing - * the new content (see ack_configure). - * - * It is up to the compositor to decide how and where to maximize the - * surface, for example which output and what region of the screen should - * be used. - * - * If the surface was already maximized, the compositor will still emit - * a configure event with the "maximized" state. - * - * If the surface is in a fullscreen state, this request has no direct - * effect. It may alter the state the surface is returned to when - * unmaximized unless overridden by the compositor. - */ -static inline void -xdg_toplevel_set_maximized(struct xdg_toplevel *xdg_toplevel) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_MAXIMIZED, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Unmaximize the surface. - * - * After requesting that the surface should be unmaximized, the compositor - * will respond by emitting a configure event. Whether this actually - * un-maximizes the window is subject to compositor policies. - * If available and applicable, the compositor will include the window - * geometry dimensions the window had prior to being maximized in the - * configure event. The client must then update its content, drawing it in - * the configured state. The client must also acknowledge the configure - * when committing the new content (see ack_configure). - * - * It is up to the compositor to position the surface after it was - * unmaximized; usually the position the surface had before maximizing, if - * applicable. - * - * If the surface was already not maximized, the compositor will still - * emit a configure event without the "maximized" state. - * - * If the surface is in a fullscreen state, this request has no direct - * effect. It may alter the state the surface is returned to when - * unmaximized unless overridden by the compositor. - */ -static inline void -xdg_toplevel_unset_maximized(struct xdg_toplevel *xdg_toplevel) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_UNSET_MAXIMIZED, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Make the surface fullscreen. - * - * After requesting that the surface should be fullscreened, the - * compositor will respond by emitting a configure event. Whether the - * client is actually put into a fullscreen state is subject to compositor - * policies. The client must also acknowledge the configure when - * committing the new content (see ack_configure). - * - * The output passed by the request indicates the client's preference as - * to which display it should be set fullscreen on. If this value is NULL, - * it's up to the compositor to choose which display will be used to map - * this surface. - * - * If the surface doesn't cover the whole output, the compositor will - * position the surface in the center of the output and compensate with - * border fill covering the rest of the output. The content of the - * border fill is undefined, but should be assumed to be in some way that - * attempts to blend into the surrounding area (e.g. solid black). - * - * If the fullscreened surface is not opaque, the compositor must make - * sure that other screen content not part of the same surface tree (made - * up of subsurfaces, popups or similarly coupled surfaces) are not - * visible below the fullscreened surface. - */ -static inline void -xdg_toplevel_set_fullscreen(struct xdg_toplevel *xdg_toplevel, struct wl_output *output) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_FULLSCREEN, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0, output); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Make the surface no longer fullscreen. - * - * After requesting that the surface should be unfullscreened, the - * compositor will respond by emitting a configure event. - * Whether this actually removes the fullscreen state of the client is - * subject to compositor policies. - * - * Making a surface unfullscreen sets states for the surface based on the following: - * * the state(s) it may have had before becoming fullscreen - * * any state(s) decided by the compositor - * * any state(s) requested by the client while the surface was fullscreen - * - * The compositor may include the previous window geometry dimensions in - * the configure event, if applicable. - * - * The client must also acknowledge the configure when committing the new - * content (see ack_configure). - */ -static inline void -xdg_toplevel_unset_fullscreen(struct xdg_toplevel *xdg_toplevel) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_UNSET_FULLSCREEN, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0); -} - -/** - * @ingroup iface_xdg_toplevel - * - * Request that the compositor minimize your surface. There is no - * way to know if the surface is currently minimized, nor is there - * any way to unset minimization on this surface. - * - * If you are looking to throttle redrawing when minimized, please - * instead use the wl_surface.frame event for this, as this will - * also work with live previews on windows in Alt-Tab, Expose or - * similar compositor features. - */ -static inline void -xdg_toplevel_set_minimized(struct xdg_toplevel *xdg_toplevel) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_toplevel, - XDG_TOPLEVEL_SET_MINIMIZED, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_toplevel), 0); -} - -#ifndef XDG_POPUP_ERROR_ENUM -#define XDG_POPUP_ERROR_ENUM -enum xdg_popup_error { - /** - * tried to grab after being mapped - */ - XDG_POPUP_ERROR_INVALID_GRAB = 0, -}; -#endif /* XDG_POPUP_ERROR_ENUM */ - -/** - * @ingroup iface_xdg_popup - * @struct xdg_popup_listener - */ -struct xdg_popup_listener { - /** - * configure the popup surface - * - * This event asks the popup surface to configure itself given - * the configuration. The configured state should not be applied - * immediately. See xdg_surface.configure for details. - * - * The x and y arguments represent the position the popup was - * placed at given the xdg_positioner rule, relative to the upper - * left corner of the window geometry of the parent surface. - * - * For version 2 or older, the configure event for an xdg_popup is - * only ever sent once for the initial configuration. Starting with - * version 3, it may be sent again if the popup is setup with an - * xdg_positioner with set_reactive requested, or in response to - * xdg_popup.reposition requests. - * @param x x position relative to parent surface window geometry - * @param y y position relative to parent surface window geometry - * @param width window geometry width - * @param height window geometry height - */ - void (*configure)(void *data, - struct xdg_popup *xdg_popup, - int32_t x, - int32_t y, - int32_t width, - int32_t height); - /** - * popup interaction is done - * - * The popup_done event is sent out when a popup is dismissed by - * the compositor. The client should destroy the xdg_popup object - * at this point. - */ - void (*popup_done)(void *data, - struct xdg_popup *xdg_popup); - /** - * signal the completion of a repositioned request - * - * The repositioned event is sent as part of a popup - * configuration sequence, together with xdg_popup.configure and - * lastly xdg_surface.configure to notify the completion of a - * reposition request. - * - * The repositioned event is to notify about the completion of a - * xdg_popup.reposition request. The token argument is the token - * passed in the xdg_popup.reposition request. - * - * Immediately after this event is emitted, xdg_popup.configure and - * xdg_surface.configure will be sent with the updated size and - * position, as well as a new configure serial. - * - * The client should optionally update the content of the popup, - * but must acknowledge the new popup configuration for the new - * position to take effect. See xdg_surface.ack_configure for - * details. - * @param token reposition request token - * @since 3 - */ - void (*repositioned)(void *data, - struct xdg_popup *xdg_popup, - uint32_t token); -}; - -/** - * @ingroup iface_xdg_popup - */ -static inline int -xdg_popup_add_listener(struct xdg_popup *xdg_popup, - const struct xdg_popup_listener *listener, void *data) -{ - return wl_proxy_add_listener((struct wl_proxy *) xdg_popup, - (void (**)(void)) listener, data); -} - -#define XDG_POPUP_DESTROY 0 -#define XDG_POPUP_GRAB 1 -#define XDG_POPUP_REPOSITION 2 - -/** - * @ingroup iface_xdg_popup - */ -#define XDG_POPUP_CONFIGURE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_popup - */ -#define XDG_POPUP_POPUP_DONE_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_popup - */ -#define XDG_POPUP_REPOSITIONED_SINCE_VERSION 3 - -/** - * @ingroup iface_xdg_popup - */ -#define XDG_POPUP_DESTROY_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_popup - */ -#define XDG_POPUP_GRAB_SINCE_VERSION 1 -/** - * @ingroup iface_xdg_popup - */ -#define XDG_POPUP_REPOSITION_SINCE_VERSION 3 - -/** @ingroup iface_xdg_popup */ -static inline void -xdg_popup_set_user_data(struct xdg_popup *xdg_popup, void *user_data) -{ - wl_proxy_set_user_data((struct wl_proxy *) xdg_popup, user_data); -} - -/** @ingroup iface_xdg_popup */ -static inline void * -xdg_popup_get_user_data(struct xdg_popup *xdg_popup) -{ - return wl_proxy_get_user_data((struct wl_proxy *) xdg_popup); -} - -static inline uint32_t -xdg_popup_get_version(struct xdg_popup *xdg_popup) -{ - return wl_proxy_get_version((struct wl_proxy *) xdg_popup); -} - -/** - * @ingroup iface_xdg_popup - * - * This destroys the popup. Explicitly destroying the xdg_popup - * object will also dismiss the popup, and unmap the surface. - * - * If this xdg_popup is not the "topmost" popup, the - * xdg_wm_base.not_the_topmost_popup protocol error will be sent. - */ -static inline void -xdg_popup_destroy(struct xdg_popup *xdg_popup) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_popup, - XDG_POPUP_DESTROY, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_popup), WL_MARSHAL_FLAG_DESTROY); -} - -/** - * @ingroup iface_xdg_popup - * - * This request makes the created popup take an explicit grab. An explicit - * grab will be dismissed when the user dismisses the popup, or when the - * client destroys the xdg_popup. This can be done by the user clicking - * outside the surface, using the keyboard, or even locking the screen - * through closing the lid or a timeout. - * - * If the compositor denies the grab, the popup will be immediately - * dismissed. - * - * This request must be used in response to some sort of user action like a - * button press, key press, or touch down event. The serial number of the - * event should be passed as 'serial'. - * - * The parent of a grabbing popup must either be an xdg_toplevel surface or - * another xdg_popup with an explicit grab. If the parent is another - * xdg_popup it means that the popups are nested, with this popup now being - * the topmost popup. - * - * Nested popups must be destroyed in the reverse order they were created - * in, e.g. the only popup you are allowed to destroy at all times is the - * topmost one. - * - * When compositors choose to dismiss a popup, they may dismiss every - * nested grabbing popup as well. When a compositor dismisses popups, it - * will follow the same dismissing order as required from the client. - * - * If the topmost grabbing popup is destroyed, the grab will be returned to - * the parent of the popup, if that parent previously had an explicit grab. - * - * If the parent is a grabbing popup which has already been dismissed, this - * popup will be immediately dismissed. If the parent is a popup that did - * not take an explicit grab, an error will be raised. - * - * During a popup grab, the client owning the grab will receive pointer - * and touch events for all their surfaces as normal (similar to an - * "owner-events" grab in X11 parlance), while the top most grabbing popup - * will always have keyboard focus. - */ -static inline void -xdg_popup_grab(struct xdg_popup *xdg_popup, struct wl_seat *seat, uint32_t serial) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_popup, - XDG_POPUP_GRAB, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_popup), 0, seat, serial); -} - -/** - * @ingroup iface_xdg_popup - * - * Reposition an already-mapped popup. The popup will be placed given the - * details in the passed xdg_positioner object, and a - * xdg_popup.repositioned followed by xdg_popup.configure and - * xdg_surface.configure will be emitted in response. Any parameters set - * by the previous positioner will be discarded. - * - * The passed token will be sent in the corresponding - * xdg_popup.repositioned event. The new popup position will not take - * effect until the corresponding configure event is acknowledged by the - * client. See xdg_popup.repositioned for details. The token itself is - * opaque, and has no other special meaning. - * - * If multiple reposition requests are sent, the compositor may skip all - * but the last one. - * - * If the popup is repositioned in response to a configure event for its - * parent, the client should send an xdg_positioner.set_parent_configure - * and possibly an xdg_positioner.set_parent_size request to allow the - * compositor to properly constrain the popup. - * - * If the popup is repositioned together with a parent that is being - * resized, but not in response to a configure event, the client should - * send an xdg_positioner.set_parent_size request. - */ -static inline void -xdg_popup_reposition(struct xdg_popup *xdg_popup, struct xdg_positioner *positioner, uint32_t token) -{ - wl_proxy_marshal_flags((struct wl_proxy *) xdg_popup, - XDG_POPUP_REPOSITION, NULL, wl_proxy_get_version((struct wl_proxy *) xdg_popup), 0, positioner, token); -} - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/xdg-shell-protocol.c b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/xdg-shell-protocol.c deleted file mode 100644 index 33881aab5e02..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/digital-rain/xdg-shell-protocol.c +++ /dev/null @@ -1,184 +0,0 @@ -/* Generated by wayland-scanner 1.26.0 */ - -/* - * Copyright © 2008-2013 Kristian Høgsberg - * Copyright © 2013 Rafael Antognolli - * Copyright © 2013 Jasper St. Pierre - * Copyright © 2010-2013 Intel Corporation - * Copyright © 2015-2017 Samsung Electronics Co., Ltd - * Copyright © 2015-2017 Red Hat Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice (including the next - * paragraph) shall be included in all copies or substantial portions of the - * Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -#include <stdbool.h> -#include <stdlib.h> -#include <stdint.h> -#include "wayland-util.h" - -#ifndef __has_attribute -# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */ -#endif - -#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4) -#define WL_PRIVATE __attribute__ ((visibility("hidden"))) -#else -#define WL_PRIVATE -#endif - -extern const struct wl_interface wl_output_interface; -extern const struct wl_interface wl_seat_interface; -extern const struct wl_interface wl_surface_interface; -extern const struct wl_interface xdg_popup_interface; -extern const struct wl_interface xdg_positioner_interface; -extern const struct wl_interface xdg_surface_interface; -extern const struct wl_interface xdg_toplevel_interface; - -static const struct wl_interface *xdg_shell_types[] = { - NULL, - NULL, - NULL, - NULL, - &xdg_positioner_interface, - &xdg_surface_interface, - &wl_surface_interface, - &xdg_toplevel_interface, - &xdg_popup_interface, - &xdg_surface_interface, - &xdg_positioner_interface, - &xdg_toplevel_interface, - &wl_seat_interface, - NULL, - NULL, - NULL, - &wl_seat_interface, - NULL, - &wl_seat_interface, - NULL, - NULL, - &wl_output_interface, - &wl_seat_interface, - NULL, - &xdg_positioner_interface, - NULL, -}; - -static const struct wl_message xdg_wm_base_requests[] = { - { "destroy", "", xdg_shell_types + 0 }, - { "create_positioner", "n", xdg_shell_types + 4 }, - { "get_xdg_surface", "no", xdg_shell_types + 5 }, - { "pong", "u", xdg_shell_types + 0 }, -}; - -static const struct wl_message xdg_wm_base_events[] = { - { "ping", "u", xdg_shell_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface xdg_wm_base_interface = { - "xdg_wm_base", 7, - 4, xdg_wm_base_requests, - 1, xdg_wm_base_events, -}; - -static const struct wl_message xdg_positioner_requests[] = { - { "destroy", "", xdg_shell_types + 0 }, - { "set_size", "ii", xdg_shell_types + 0 }, - { "set_anchor_rect", "iiii", xdg_shell_types + 0 }, - { "set_anchor", "u", xdg_shell_types + 0 }, - { "set_gravity", "u", xdg_shell_types + 0 }, - { "set_constraint_adjustment", "u", xdg_shell_types + 0 }, - { "set_offset", "ii", xdg_shell_types + 0 }, - { "set_reactive", "3", xdg_shell_types + 0 }, - { "set_parent_size", "3ii", xdg_shell_types + 0 }, - { "set_parent_configure", "3u", xdg_shell_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface xdg_positioner_interface = { - "xdg_positioner", 7, - 10, xdg_positioner_requests, - 0, NULL, -}; - -static const struct wl_message xdg_surface_requests[] = { - { "destroy", "", xdg_shell_types + 0 }, - { "get_toplevel", "n", xdg_shell_types + 7 }, - { "get_popup", "n?oo", xdg_shell_types + 8 }, - { "set_window_geometry", "iiii", xdg_shell_types + 0 }, - { "ack_configure", "u", xdg_shell_types + 0 }, -}; - -static const struct wl_message xdg_surface_events[] = { - { "configure", "u", xdg_shell_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface xdg_surface_interface = { - "xdg_surface", 7, - 5, xdg_surface_requests, - 1, xdg_surface_events, -}; - -static const struct wl_message xdg_toplevel_requests[] = { - { "destroy", "", xdg_shell_types + 0 }, - { "set_parent", "?o", xdg_shell_types + 11 }, - { "set_title", "s", xdg_shell_types + 0 }, - { "set_app_id", "s", xdg_shell_types + 0 }, - { "show_window_menu", "ouii", xdg_shell_types + 12 }, - { "move", "ou", xdg_shell_types + 16 }, - { "resize", "ouu", xdg_shell_types + 18 }, - { "set_max_size", "ii", xdg_shell_types + 0 }, - { "set_min_size", "ii", xdg_shell_types + 0 }, - { "set_maximized", "", xdg_shell_types + 0 }, - { "unset_maximized", "", xdg_shell_types + 0 }, - { "set_fullscreen", "?o", xdg_shell_types + 21 }, - { "unset_fullscreen", "", xdg_shell_types + 0 }, - { "set_minimized", "", xdg_shell_types + 0 }, -}; - -static const struct wl_message xdg_toplevel_events[] = { - { "configure", "iia", xdg_shell_types + 0 }, - { "close", "", xdg_shell_types + 0 }, - { "configure_bounds", "4ii", xdg_shell_types + 0 }, - { "wm_capabilities", "5a", xdg_shell_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface xdg_toplevel_interface = { - "xdg_toplevel", 7, - 14, xdg_toplevel_requests, - 4, xdg_toplevel_events, -}; - -static const struct wl_message xdg_popup_requests[] = { - { "destroy", "", xdg_shell_types + 0 }, - { "grab", "ou", xdg_shell_types + 22 }, - { "reposition", "3ou", xdg_shell_types + 24 }, -}; - -static const struct wl_message xdg_popup_events[] = { - { "configure", "iiii", xdg_shell_types + 0 }, - { "popup_done", "", xdg_shell_types + 0 }, - { "repositioned", "3u", xdg_shell_types + 0 }, -}; - -WL_PRIVATE const struct wl_interface xdg_popup_interface = { - "xdg_popup", 7, - 3, xdg_popup_requests, - 3, xdg_popup_events, -}; - diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-anomalies.tpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-anomalies.tpp deleted file mode 100644 index 41b085be8a3f..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-anomalies.tpp +++ /dev/null @@ -1,52 +0,0 @@ -// Optional distant encounters. Keep their rendering separate from the -// cloud volume and particle shaders so new kinds can be added independently. -static const char *hyperspace_anomaly_fragment = R"( -#version 100 -precision highp float; -uniform vec2 resolution, centre; -uniform float size, angle, shape, age, strength; -uniform int kind; -varying vec2 uv; -float noise2(vec2 p) -{ - vec2 cell=floor(p),f=fract(p);f=f*f*(3.0-2.0*f); - vec4 k=vec4(dot(cell,vec2(127.1,311.7)),dot(cell+vec2(1,0),vec2(127.1,311.7)), - dot(cell+vec2(0,1),vec2(127.1,311.7)),dot(cell+vec2(1,1),vec2(127.1,311.7))); - vec4 h=fract(sin(k+shape*83.0)*43758.5453); - return mix(mix(h.x,h.y,f.x),mix(h.z,h.w,f.x),f.y); -} -void main() -{ - vec2 pixel=(uv*resolution-centre)/resolution.y; - float c=cos(angle),s=sin(angle); - vec2 p=vec2(c*pixel.x+s*pixel.y,-s*pixel.x+c*pixel.y)/size; - vec3 color=vec3(0.0); - if (kind==0) - { - // A warm/cool binary pair with a faint shared tidal dust arc. - float orbit=age*(0.12+shape*0.10)+shape*6.28; - vec2 offset=vec2(cos(orbit),sin(orbit)*0.55)*0.24; - float a=dot(p-offset,p-offset),b=dot(p+offset*0.72,p+offset*0.72); - color=vec3(1.0,0.77,0.48)*(exp(-a*1800.0)*1.4+exp(-a*85.0)*0.045)+ - vec3(0.65,0.82,1.0)*(exp(-b*2400.0)*1.6+exp(-b*110.0)*0.04); - float ring=length(p*vec2(1.0,1.8)); - float arc=exp(-pow((ring-0.48)*19.0,2.0))*(0.25+0.75*smoothstep(-0.4,0.7,p.x)); - color+=vec3(0.36,0.43,0.53)*arc*0.018; - } else - { - // A distant, broken remnant shell with knots and dark missing arcs. - // Its structure is fixed; a very slow expansion suggests depth. - vec2 q=p/(1.0+age*0.0015); - float n=noise2(q*4.7),fine=noise2(q*15.0+23.0); - float radius=length(q*vec2(1.0,1.25)); - float rim=exp(-pow((radius-(0.58+(n-0.5)*0.24))*15.0,2.0)); - float holes=smoothstep(0.32,0.78,noise2(q*2.5+11.0)); - float knots=pow(fine,3.0); - vec3 cold=vec3(0.30,0.54,0.78),warm=vec3(0.77,0.34,0.22); - color=mix(cold,warm,shape*0.7)*rim*holes*(0.14+knots*0.45); - color+=vec3(0.77,0.87,1.0)*exp(-dot(q,q)*3500.0)*0.45; - } - color*=1.0-smoothstep(1.15,1.6,length(p)); - gl_FragColor=vec4(color*strength,0.0); -} -)"; diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-asteroid-shaders.tpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-asteroid-shaders.tpp deleted file mode 100644 index 4d5214ed358d..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-asteroid-shaders.tpp +++ /dev/null @@ -1,88 +0,0 @@ -// Solid geometry with an opaque silhouette: these bodies absorb background -// light rather than becoming another additive star or nebula sprite. -static const char *hyperspace_asteroid_vertex = R"( -#version 100 -precision highp float; -attribute vec3 position, normal; -uniform mat4 rotation; -uniform vec3 centre; -uniform vec2 resolution; -uniform float radius; -varying vec3 rock, rock_normal, eye; -void main() -{ - vec3 p=centre+(rotation*vec4(position*radius,0.0)).xyz; - rock=position;rock_normal=normal;eye=-p; - // Positive Z points along the journey. Match the starfield's perspective, - // with proper clip depth so front/back facets and overlapping rocks agree. - float near_plane=0.12,far_plane=160.0,focal=1.0; - gl_Position=vec4(p.x*focal/(resolution.x/resolution.y),-p.y*focal, - ((far_plane+near_plane)*p.z-2.0*near_plane*far_plane)/(far_plane-near_plane),p.z); -} -)"; -static const char *hyperspace_asteroid_fragment = R"( -#version 100 -precision highp float; -uniform vec3 albedo; -uniform float material_seed, brightness, visibility; -uniform mat4 rotation; -varying vec3 rock, rock_normal, eye; -float hash3(vec3 p) -{ - return fract(sin(dot(p,vec3(127.1,311.7,74.7))+material_seed)*43758.5453); -} -// Value and analytic gradient together: relief follows the same object-space -// minerals as the colour, without extra finite-difference texture samples. -vec4 grain(vec3 p) -{ - vec3 i=floor(p),t=fract(p); - vec3 f=t*t*t*(t*(t*6.0-15.0)+10.0); - vec3 df=30.0*t*t*(t-1.0)*(t-1.0); - float a=hash3(i),b=hash3(i+vec3(1,0,0)); - float c=hash3(i+vec3(0,1,0)),d=hash3(i+vec3(1,1,0)); - float e=hash3(i+vec3(0,0,1)),h=hash3(i+vec3(1,0,1)); - float j=hash3(i+vec3(0,1,1)),k=hash3(i+vec3(1,1,1)); - float ab=mix(a,b,f.x),cd=mix(c,d,f.x); - float eh=mix(e,h,f.x),jk=mix(j,k,f.x); - return vec4(mix(mix(ab,cd,f.y),mix(eh,jk,f.y),f.z), - df.x*mix(mix(b-a,d-c,f.y),mix(h-e,k-j,f.y),f.z), - df.y*mix(cd-ab,jk-eh,f.z),df.z*(mix(eh,jk,f.y)-mix(ab,cd,f.y))); -} -void main() -{ - vec3 local_normal=normalize(rock_normal); - vec4 strata=grain(rock*4.7+vec3(3.1,7.3,1.9)); - vec4 chips=grain(rock*14.3+vec3(17.0,2.0,9.0)); - vec4 grit=grain(rock*47.0+vec3(2.0,31.0,11.0)); - vec4 flecks=grain(rock*109.0+vec3(43.0,13.0,7.0)); - - // Interrupted fracture seams, coarse chipped relief and fine regolith. - // All scales remain attached to the body while it rotates. - float seam_distance=abs(strata.x-0.48); - float seam_width=0.025; - float seam_profile=exp(-seam_distance*seam_distance/(seam_width*seam_width)); - float seam_mask=smoothstep(0.24,0.56,chips.x); - float seam=seam_profile*seam_mask; - float seam_slope=-2.0*(strata.x-0.48)/(seam_width*seam_width)*seam_profile; - vec3 relief=strata.yzw*4.7*0.10+chips.yzw*14.3*0.018+ - grit.yzw*47.0*0.0028+flecks.yzw*109.0*0.00035- - strata.yzw*4.7*seam_slope*seam_mask*0.012; - relief-=local_normal*dot(local_normal,relief); - vec3 n=normalize((rotation*vec4(normalize(local_normal-relief),0.0)).xyz); - vec3 light=normalize(vec3(-0.45,-0.55,-0.80)); - - float mineral=0.58+strata.x*0.48+chips.x*0.32+grit.x*0.12-seam*0.30; - vec3 weathering=mix(vec3(0.82,0.85,0.90),vec3(1.07,1.04,0.97), - smoothstep(0.25,0.72,strata.x)); - float inclusions=smoothstep(0.69,0.88,flecks.x)*smoothstep(0.42,0.67,chips.x); - vec3 stone=albedo*mineral*weathering+vec3(0.12,0.115,0.10)*inclusions; - float key=max(0.0,dot(n,light)); - float bounce=max(0.0,dot(n,normalize(vec3(0.6,0.15,-0.2)))); - vec3 illumination=vec3(0.07)+vec3(0.96,0.88,0.78)*key*0.86+ - vec3(0.32,0.43,0.60)*bounce*0.10; - float sheen=pow(max(0.0,dot(n,normalize(light+normalize(eye)))),12.0)* - (0.012+inclusions*0.055)*key; - vec3 color=(stone*illumination+vec3(sheen))*brightness; - gl_FragColor=vec4(color*visibility,visibility); -} -)"; diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-asteroids.tpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-asteroids.tpp deleted file mode 100644 index 692441f4c33b..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-asteroids.tpp +++ /dev/null @@ -1,191 +0,0 @@ -#include "hyperspace-asteroid-shaders.tpp" - -// Optional solid encounters. Geometry, materials and scheduling live here, -// independent of stars/clouds; both compositor and lock host use this module. -// Wayfire loads plugins globally and older versions can remain resident after -// dlclose. Keep every mesh/material/trajectory method local to this version. -class __attribute__((visibility("hidden"))) ambient_asteroid_field -{ - struct vertex { glm::vec3 position, normal; }; - OpenGL::program_t program; - GLuint buffer=0,depth=0; - int mesh_level=-1,depth_width=0,depth_height=0,vertices_per_mesh=0; - uint32_t mesh_seed=0; - static constexpr int mesh_count=10; - // Short encounter fields separated by a longer stretch of open space. - // Both periods divide the bounded journey clock exactly. - static constexpr double period=384,world_period=4096*12; - - void meshes(uint32_t seed,int level) - { - if (buffer && mesh_seed==seed && mesh_level==level) return; - mesh_seed=seed;mesh_level=level; - std::vector<vertex> vertices; - const float t=(1+std::sqrt(5.f))/2; - const glm::vec3 points[]={{-1,t,0},{1,t,0},{-1,-t,0},{1,-t,0}, - {0,-1,t},{0,1,t},{0,-1,-t},{0,1,-t},{t,0,-1},{t,0,1},{-t,0,-1},{-t,0,1}}; - const int faces[][3]={{0,11,5},{0,5,1},{0,1,7},{0,7,10},{0,10,11}, - {1,5,9},{5,11,4},{11,10,2},{10,7,6},{7,1,8},{3,9,4},{3,4,2}, - {3,2,6},{3,6,8},{3,8,9},{4,9,5},{2,4,11},{6,2,10},{8,6,7},{9,8,1}}; - for (int mesh=0;mesh<mesh_count;mesh++) { - uint32_t base=seed^uint32_t(mesh*19139+0x7a231); - auto random=[base](unsigned i){return star_random(base+i*7183);}; - float phase=random(0)*6.2831853f; - glm::vec3 axes{.70f+random(1)*.55f,.65f+random(2)*.65f,.65f+random(3)*.65f}; - std::array<glm::vec3,6> craters; - for (unsigned i=0;i<craters.size();i++) - craters[i]=glm::normalize(glm::vec3{2*random(5+i*3)-1, - 2*random(6+i*3)-1,2*random(7+i*3)-1}); - std::array<glm::vec3,9> fractures; - for (unsigned i=0;i<fractures.size();i++) - fractures[i]=glm::normalize(glm::vec3{2*random(53+i*3)-1, - 2*random(54+i*3)-1,2*random(55+i*3)-1}); - auto point=[&](glm::vec3 n) { - n=glm::normalize(n); - float radial=.94f+.11f*std::sin(n.x*4.8f+phase)*std::cos(n.y*3.3f+phase*.7f)+ - .08f*std::sin(glm::dot(n,glm::vec3{5.3f,2.1f,-3.5f})+phase*3)+ - .025f*std::sin(glm::dot(n,glm::vec3{13.1f,-8.7f,9.3f})+phase); - for (unsigned i=0;i<craters.size();i++) { - float d=glm::length(n-craters[i])/(.18f+random(29+i)*.24f); - radial-=(.07f+random(41+i)*.07f)*std::exp(-d*d*3); - radial+=.02f*std::exp(-std::pow((d-.85f)*7,2.f)); - } - // Broken faces cut through the rounded parent body. A plane - // intersection gives genuine angular silhouettes and broad - // exposed facets, rather than only colouring a smooth lump. - for (unsigned i=0;i<fractures.size();i++) { - float facing=glm::dot(n,fractures[i]); - if (facing>0) - radial=std::min(radial,(.63f+random(83+i)*.27f)/facing); - } - return n*radial*axes; - }; - auto append=[&](glm::vec3 n) { - n=glm::normalize(n); - glm::vec3 a=glm::normalize(glm::cross(n,std::abs(n.y)<.9f ? - glm::vec3{0,1,0}:glm::vec3{1,0,0})),b=glm::cross(n,a); - glm::vec3 da=point(n+a*.012f)-point(n-a*.012f),db=point(n+b*.012f)-point(n-b*.012f); - vertices.push_back({point(n),glm::normalize(glm::cross(da,db))}); - }; - std::function<void(glm::vec3,glm::vec3,glm::vec3,int)> triangle; - triangle=[&](glm::vec3 a,glm::vec3 b,glm::vec3 c,int subdivisions) { - if (!subdivisions) { append(a);append(b);append(c);return; } - glm::vec3 ab=glm::normalize(a+b),bc=glm::normalize(b+c),ca=glm::normalize(c+a); - triangle(a,ab,ca,subdivisions-1);triangle(b,bc,ab,subdivisions-1); - triangle(c,ca,bc,subdivisions-1);triangle(ab,bc,ca,subdivisions-1); - }; - for (const auto& face:faces) - triangle(glm::normalize(points[face[0]]),glm::normalize(points[face[1]]), - glm::normalize(points[face[2]]),level==2 ? 3 : 2); - } - vertices_per_mesh=vertices.size()/mesh_count; - if (!buffer) GL_CALL(glGenBuffers(1,&buffer)); - GL_CALL(glBindBuffer(GL_ARRAY_BUFFER,buffer)); - GL_CALL(glBufferData(GL_ARRAY_BUFFER,vertices.size()*sizeof(vertex),vertices.data(),GL_STATIC_DRAW)); - GL_CALL(glBindBuffer(GL_ARRAY_BUFFER,0)); - } - - public: - ~ambient_asteroid_field() { - program.free_resources(); - if (buffer) GL_CALL(glDeleteBuffers(1,&buffer)); - if (depth) GL_CALL(glDeleteRenderbuffers(1,&depth)); - } - void render(const wf::render_target_t& target,const wf::geometry_t& box, - uint32_t seed,double clock,int level,double density,double brightness) - { - meshes(seed,level); - if (!program.get_program_id(wf::TEXTURE_TYPE_RGBA)) - program.set_simple(OpenGL::compile_program(hyperspace_asteroid_vertex,hyperspace_asteroid_fragment)); - // Only our cached backdrop owns this depth attachment. The destination - // output and Cube/lock buffers are never cleared or reconfigured here. - GLint viewport[4],old_renderbuffer,old_depth_func; - GLboolean old_depth_mask; - GL_CALL(glGetIntegerv(GL_VIEWPORT,viewport)); - GL_CALL(glGetIntegerv(GL_RENDERBUFFER_BINDING,&old_renderbuffer)); - GL_CALL(glGetIntegerv(GL_DEPTH_FUNC,&old_depth_func)); - GL_CALL(glGetBooleanv(GL_DEPTH_WRITEMASK,&old_depth_mask)); - if (!depth) GL_CALL(glGenRenderbuffers(1,&depth)); - GL_CALL(glBindRenderbuffer(GL_RENDERBUFFER,depth)); - if (depth_width!=viewport[2] || depth_height!=viewport[3]) { - depth_width=viewport[2];depth_height=viewport[3]; - GL_CALL(glRenderbufferStorage(GL_RENDERBUFFER,GL_DEPTH_COMPONENT16,depth_width,depth_height)); - } - GL_CALL(glFramebufferRenderbuffer(GL_FRAMEBUFFER,GL_DEPTH_ATTACHMENT,GL_RENDERBUFFER,depth)); - GL_CALL(glBindRenderbuffer(GL_RENDERBUFFER,old_renderbuffer)); - GL_CALL(glEnable(GL_DEPTH_TEST));GL_CALL(glDepthFunc(GL_LEQUAL));GL_CALL(glDepthMask(GL_TRUE)); - wf::gles::for_each_scissor_rect(target,box,[&]{GL_CALL(glClear(GL_DEPTH_BUFFER_BIT));}); - GL_CALL(glEnable(GL_BLEND));GL_CALL(glBlendFunc(GL_ONE,GL_ONE_MINUS_SRC_ALPHA)); - program.use(wf::TEXTURE_TYPE_RGBA); - program.uniform2f("resolution",box.width,box.height); - program.uniform1f("brightness",brightness); - GL_CALL(glBindBuffer(GL_ARRAY_BUFFER,buffer)); - program.attrib_pointer("position",3,sizeof(vertex),reinterpret_cast<void*>(offsetof(vertex,position))); - program.attrib_pointer("normal",3,sizeof(vertex),reinterpret_cast<void*>(offsetof(vertex,normal))); - const double aspect=box.width/box.height; - const int cycle=int(std::floor(clock/period)); - const int count=int(64*density); - for (int field=cycle;field<=cycle+1;field++) { - const unsigned wrapped=(field+unsigned(world_period/period))%unsigned(world_period/period); - uint32_t field_seed=seed^uint32_t(wrapped*0x9e3779b9u)^0x891723u; - for (int i=0;i<count;i++) { - uint32_t base=field_seed^uint32_t(i*15341+917); - auto random=[base](unsigned j){return star_random(base+j*19139);}; - // Keep encounter scheduling on the shared journey clock, but - // cross the visible depth range quickly. Multiplying the - // whole clock would also make fields arrive more frequently. - const double arrival=field*period+1+random(0)*90; - if ((arrival-clock)*64>140) continue; - // Independent, logarithmic size draws give fragments, rocks - // and boulders distinct scales. Two percent of encounters are - // huge bodies; field scheduling and density stay unchanged. - const float size_class=random(2); - float radius; - if (size_class<.50f) radius=.04f*std::pow(7.5f,random(1)); - else if (size_class<.82f) radius=.30f*std::pow(2.3f/.30f,random(1)); - else if (size_class<.98f) radius=2.3f*std::pow(7.f/2.3f,random(1)); - else radius=10.f*std::pow(2.f,random(13)); - // A huge silhouette takes longer to clear the viewport at - // the same world velocity. Give larger bodies extra travel - // speed so they still make brief, fast passes at the edges. - const float z=(arrival-clock)*(64+radius*4); - if (z>140) continue; - // Approach along the central flight corridor, then let real - // perspective carry the body past the camera and out of view. - // World coordinates keep arrivals centred on a panorama too. - float angle=random(3)*2*M_PI; - // A radius-only miss distance cancels apparent size: tiny and - // huge rocks then fill roughly the same part of the screen. - // Independent clearance preserves the visible size spread, - // while the radius term keeps even a huge body off the camera. - float impact=radius*1.55f+2.f+std::pow(random(4),.75f)*8.f; - float x=std::cos(angle)*impact; - float y=std::sin(angle)*impact; - // Frustum planes test the entire bounding sphere, including - // its depth. Testing only the centre depth can drop a partly - // visible close body before its last facets leave the frame. - const float bound=radius*1.5f; - if (z+bound<=.12f || - std::abs(x)>z*aspect+bound*std::sqrt(1+aspect*aspect) || - std::abs(y)>z+bound*std::sqrt(2.f)) continue; - double phase=clock/world_period*2*M_PI; - glm::mat4 rotation{1}; - rotation=glm::rotate(rotation,float(random(6)*2*M_PI+phase*(128+int(random(7)*576))),glm::vec3{0,1,0}); - rotation=glm::rotate(rotation,float(random(8)*2*M_PI-phase*(96+int(random(9)*480))),glm::vec3{1,0,0}); - program.uniformMatrix4f("rotation",rotation); - program.uniform3f("centre",x,y,z);program.uniform1f("radius",radius); - program.uniform1f("material_seed",random(10)*100); - const glm::vec3 tones[]={{.40f,.42f,.45f},{.29f,.30f,.32f},{.46f,.32f,.23f},{.48f,.53f,.57f}}; - float material=random(11);auto tone=tones[material<.48f ? 0 : material<.86f ? 1 : material<.94f ? 2 : 3]; - program.uniform3f("albedo",tone.r,tone.g,tone.b); - float fade=std::clamp((140-z)/25.f,0.f,1.f);fade=fade*fade*(3-2*fade); - program.uniform1f("visibility",fade); - int mesh=std::min(mesh_count-1,int(random(12)*mesh_count)); - wf::gles::for_each_scissor_rect(target,box,[&]{GL_CALL(glDrawArrays(GL_TRIANGLES,mesh*vertices_per_mesh,vertices_per_mesh));}); - } - } - program.deactivate();GL_CALL(glBindBuffer(GL_ARRAY_BUFFER,0)); - GL_CALL(glDisable(GL_BLEND));GL_CALL(glDisable(GL_DEPTH_TEST)); - GL_CALL(glDepthFunc(old_depth_func));GL_CALL(glDepthMask(old_depth_mask)); - } -}; diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-background.cpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-background.cpp deleted file mode 100644 index 2e6edc36bb64..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-background.cpp +++ /dev/null @@ -1,400 +0,0 @@ -#include "hyperspace-background.hpp" -#include "hyperspace-shaders.tpp" -#include "../nebula/nebula-field.tpp" -#include <string> -#include "hyperspace-anomalies.tpp" -#include <algorithm> -#include <cmath> -#include <cstddef> -#include <cstdint> -#include <vector> -#include <random> -#include <glm/gtc/matrix_transform.hpp> - -namespace -{ -struct hyperspace_star_t -{ - float seed[4]; - float corner[2]; -}; - -float star_random(uint32_t value) -{ - value ^= value >> 16; - value *= 0x7feb352du; - value ^= value >> 15; - value *= 0x846ca68bu; - value ^= value >> 16; - return (value & 0x00ffffffu) / 16777216.0f; -} -} - -#include "hyperspace-asteroids.tpp" -#include "hyperspace-galaxies.tpp" - -ambient_hyperspace_background::ambient_hyperspace_background(std::function<void()> changed) -{ - auto update = [this, changed] { cloud_dirty = backdrop_dirty = true; changed(); }; - speed.set_callback(update); clouds.set_callback(update); turbulence.set_callback(update); - ribbons.set_callback(update); glow.set_callback(update); brightness.set_callback(update); - surge.set_callback(update); density.set_callback(update); coloured.set_callback(update); - distant_density.set_callback(update); distant_drift.set_callback(update); - galaxies.set_callback(update); fractal_detail.set_callback(update); - dust_density.set_callback(update); variation.set_callback(update); - cloud_colours.set_callback(update); - pulsars.set_callback(update); anomalies.set_callback(update); asteroids.set_callback(update); seed_option.set_callback(update); - tint.set_callback(update); quality.set_callback(update); fps.set_callback(update); - last_frame = std::chrono::steady_clock::now(); -} - -ambient_hyperspace_background::~ambient_hyperspace_background() -{ - wf::gles::run_in_context_if_gles([&] - { - cloud_program.free_resources(); star_program.free_resources(); pulsar_program.free_resources(); - anomaly_program.free_resources(); - asteroid_field.reset(); - galaxy_field.reset(); - cloud_buffer.free(); backdrop_buffer.free(); - if (star_buffer) { GL_CALL(glDeleteBuffers(1, &star_buffer)); } - }); -} - -void ambient_hyperspace_background::set_active(bool enabled) -{ - if (active != enabled) last_frame = std::chrono::steady_clock::now(); - active = enabled; -} - -void ambient_hyperspace_background::set_transition(bool moving) -{ - transition = moving; -} - -bool ambient_hyperspace_background::animated() const { return active && double(speed) > 0; } - -unsigned ambient_hyperspace_background::frame_interval() const -{ - return unsigned(std::ceil(1000.0 / std::clamp(int(fps), 10, 120))); -} - -std::array<double,24> ambient_hyperspace_background::scene() const -{ - return {double(scene_seed),double(travel_epoch),double(cloud_epoch),travel,cloud_phase,drift_phase, - double(event_number),pulsar_age,pulsar_duration,pulsar_x,pulsar_y,pulsar_angle,pulsar_rotation, - pulsar_size,pulsar_projection,double(anomaly_number),anomaly_age,anomaly_duration, - anomaly_x,anomaly_y,anomaly_size,anomaly_angle,anomaly_shape,double(anomaly_kind)}; -} - -bool ambient_hyperspace_background::restore_scene(const std::array<double,24>& s) -{ - for (double v:s) if (!std::isfinite(v)) return false; - if (s[0]<0 || s[0]>UINT32_MAX || s[1]<0 || s[1]>=4096 || s[2]<0 || s[2]>=4096 || - s[3]<0 || s[3]>=12 || s[4]<0 || s[4]>=64 || s[5]<0 || s[5]>2*M_PI || - s[6]<0 || s[6]>UINT32_MAX || s[15]<0 || s[15]>UINT32_MAX || - s[7]<-100 || s[7]>100 || s[16]<-100 || s[16]>100 || - s[8]<1 || s[8]>100 || s[17]<1 || s[17]>100 || - s[9]<0 || s[9]>1 || s[10]<0 || s[10]>1 || s[18]<0 || s[18]>1 || s[19]<0 || s[19]>1 || - s[13]<0 || s[13]>1 || s[14]<0 || s[14]>1 || s[20]<0 || s[20]>1 || - std::abs(s[11])>100 || std::abs(s[12])>10 || std::abs(s[21])>100 || - s[22]<0 || s[22]>1 || (s[23]!=0 && s[23]!=1)) return false; - configured_seed=int(seed_option);scene_seed=s[0];travel_epoch=s[1];cloud_epoch=s[2]; - travel=s[3];cloud_phase=s[4];drift_phase=s[5];event_number=s[6]; - pulsar_age=s[7];pulsar_duration=s[8];pulsar_x=s[9];pulsar_y=s[10]; - pulsar_angle=s[11];pulsar_rotation=s[12];pulsar_size=s[13];pulsar_projection=s[14]; - anomaly_number=s[15];anomaly_age=s[16];anomaly_duration=s[17];anomaly_x=s[18];anomaly_y=s[19]; - anomaly_size=s[20];anomaly_angle=s[21];anomaly_shape=s[22];anomaly_kind=s[23]; - near_count=far_count=dust_count=middle_count=-1;invalidate(); - last_frame=std::chrono::steady_clock::now();return true; -} - -void ambient_hyperspace_background::load_programs() -{ - if (!cloud_program.get_program_id(wf::TEXTURE_TYPE_RGBA)) - { - std::string fragment=hyperspace_cloud_fragment; - fragment.insert(fragment.find("uniform vec2"),ambient_nebula_field_glsl); - cloud_program.set_simple(OpenGL::compile_program(hyperspace_vertex,fragment.c_str())); - } - if (!star_program.get_program_id(wf::TEXTURE_TYPE_RGBA)) - star_program.set_simple(OpenGL::compile_program(hyperspace_star_vertex, hyperspace_star_fragment)); - if (!pulsar_program.get_program_id(wf::TEXTURE_TYPE_RGBA)) - pulsar_program.set_simple(OpenGL::compile_program(hyperspace_vertex, hyperspace_pulsar_fragment)); - if (!anomaly_program.get_program_id(wf::TEXTURE_TYPE_RGBA)) - anomaly_program.set_simple(OpenGL::compile_program(hyperspace_vertex, hyperspace_anomaly_fragment)); -} - -void ambient_hyperspace_background::prepare_pulsar() -{ - uint32_t base = scene_seed ^ (++event_number * 0x9e3779b9u); - pulsar_x = 0.12f + 0.76f * star_random(base); - pulsar_y = 0.15f + 0.70f * star_random(base + 1); - pulsar_angle = star_random(base + 2) * 2 * M_PI; - pulsar_rotation = (0.10f + 0.23f * star_random(base + 3)) * - (star_random(base + 4) < 0.5f ? -1 : 1); - pulsar_size = 0.065f + 0.095f * star_random(base + 5); - pulsar_projection = 0.45f + 0.45f * star_random(base + 6); - pulsar_duration = 10 + 10 * star_random(base + 7); -} - -void ambient_hyperspace_background::prepare_anomaly() -{ - uint32_t base = scene_seed ^ 0xa6174b29u ^ (++anomaly_number * 0x9e3779b9u); - anomaly_kind = star_random(base) < 0.5f ? 0 : 1; - anomaly_x = 0.08f + 0.84f * star_random(base + 1); - anomaly_y = 0.10f + 0.80f * star_random(base + 2); - anomaly_size = (anomaly_kind == 0 ? 0.032f : 0.065f) * (0.7f + star_random(base + 3)); - anomaly_angle = star_random(base + 4) * 2 * M_PI; - anomaly_shape = star_random(base + 5); - anomaly_duration = 14 + 12 * star_random(base + 6); -} - -void ambient_hyperspace_background::update_particles() -{ - bool reseed = configured_seed != int(seed_option); - if (reseed) - { - configured_seed = int(seed_option); - scene_seed = configured_seed > 0 ? uint32_t(configured_seed) : std::random_device{}(); - near_count = far_count = dust_count = middle_count = -1; - event_number = travel_epoch = cloud_epoch = 0; - cloud_phase = 0; - prepare_pulsar(); pulsar_age = -3; - anomaly_number = 0; - prepare_anomaly(); - anomaly_age = -(14 + 12 * star_random(scene_seed ^ 0x87a3191u)); - } - const int near_counts[] = {768, 2048, 4096}, far_counts[] = {1024, 2560, 5120}; - // Preserve fine-dust coverage after filling the full frustum. - const int dust_counts[] = {5400, 13500, 27000}; - const int middle_counts[] = {1536, 4096, 8192}; - int level = std::clamp(int(quality), 0, 2); - int near = int(near_counts[level] * std::clamp(double(density), 0.0, 3.0)); - int far = int(far_counts[level] * std::clamp(double(distant_density), 0.0, 3.0)); - int dust = int(dust_counts[level] * std::clamp(double(dust_density), 0.0, 3.0)); - int middle = int(middle_counts[level] * std::clamp(double(density), 0.0, 3.0)); - if (near == near_count && far == far_count && dust == dust_count && middle == middle_count) return; - near_count = near; far_count = far; dust_count = dust; middle_count = middle; - std::vector<hyperspace_star_t> vertices; - vertices.reserve((near + far + dust + middle) * 6); - const float corners[6][2] = {{0,-1},{1,-1},{1,1},{0,-1},{1,1},{0,1}}; - // Independent seeds retain both populations when either density changes. - // Buffer order: distant stars, dust, passing stars, middle-distance stars. - // The fourth population bridges the journey to the distant background. - // Changing any density - // retains the seeds of the other layers, including their colour balance. - const int counts[] = {far, dust, near, middle}; - const uint32_t salts[] = {419, 19139, 7183, 15341}; - for (int layer = 0; layer < 4; ++layer) - for (int i = 0; i < counts[layer]; ++i) - { - uint32_t base = (i * 4 + salts[layer]) ^ (scene_seed * 0x9e3779b9u); - float seed[] = {2 * star_random(base) - 1, 2 * star_random(base + 1) - 1, - star_random(base + 2), star_random(base + 3)}; - for (const auto& corner : corners) - vertices.push_back({{seed[0],seed[1],seed[2],seed[3]},{corner[0],corner[1]}}); - } - if (!star_buffer) { GL_CALL(glGenBuffers(1, &star_buffer)); } - GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, star_buffer)); - GL_CALL(glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(hyperspace_star_t), vertices.data(), GL_STATIC_DRAW)); - GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, 0)); -} - -void ambient_hyperspace_background::render(const wf::render_target_t& target, - const wf::regionf_t& damage, wf::dimensions_t resolution, float opacity) -{ - const auto now = std::chrono::steady_clock::now(); - const double dt = std::clamp(std::chrono::duration<double>(now - last_frame).count(), 0.0, 0.25); - update_particles(); - rendered_speed = std::clamp(double(speed), 0.0, 5.0) * (1 + boost * std::clamp(double(surge), 0.0, 2.0) * 3); - if (animated()) - { - boost += ((transition ? 1.0 : 0.0) - boost) * (1 - std::exp(-dt / (transition ? 0.18 : 0.55))); - rendered_speed = std::clamp(double(speed), 0.0, 5.0) * (1 + boost * std::clamp(double(surge), 0.0, 2.0) * 3); - const double advance = dt * rendered_speed * 1.8; - const double next_travel = travel + advance; - travel_epoch = (travel_epoch + unsigned(next_travel / 12.0)) % 4096; - travel = std::fmod(next_travel, 12.0); - // Cloud centres share the starfield's travel direction but occupy a - // much deeper volume. Bound both clocks for long-running desktops. - const double next_cloud = cloud_phase + advance * 0.65; - cloud_epoch = (cloud_epoch + unsigned(next_cloud / 64.0)) % 4096; - cloud_phase = std::fmod(next_cloud, 64.0); - // Distant stars never inherit the jump surge or foreground velocity. - drift_phase = std::fmod(drift_phase + dt * double(speed) * - std::clamp(double(distant_drift), 0.0, 1.0) * 0.04, 2 * M_PI); - // Event motion ignores workspace surges. Hidden/paused effects never - // consume their event clock, and speed zero freezes jets as well. - pulsar_age += dt * std::min(double(speed), 2.0); - if (pulsar_age > pulsar_duration) - { - const double remainder = pulsar_age - pulsar_duration; - prepare_pulsar(); - pulsar_age = remainder - (20 + 25 * star_random(scene_seed ^ event_number)); - } - // Independent encounters: rare, smooth and unhurried even during a - // workspace surge. Pausing or hiding the effect pauses this clock. - anomaly_age += dt * std::min(double(speed), 2.0); - if (anomaly_age > anomaly_duration) - { - const double remainder = anomaly_age - anomaly_duration; - prepare_anomaly(); - anomaly_age = remainder - (35 + 35 * star_random(scene_seed ^ anomaly_number ^ 0x913u)); - } - cloud_dirty = backdrop_dirty = true; - } - last_frame = now; - load_programs(); - const int level = std::clamp(int(quality), 0, 2); - const float scales[] = {0.20f, 0.25f, 1.0f / 3}; - const float cloud_scale = scales[level] * target.scale; - if (cloud_buffer.allocate(resolution, cloud_scale) == wf::buffer_reallocation_result_t::REALLOCATED) - cloud_dirty = backdrop_dirty = true; - if (backdrop_buffer.allocate(resolution, target.scale) == wf::buffer_reallocation_result_t::REALLOCATED) - backdrop_dirty = true; - wf::geometry_t box{0,0,double(resolution.width),double(resolution.height)}; - const float width = resolution.width, height = resolution.height; - const GLfloat quad[] = {0,0,width,0,width,height,0,0,width,height,0,height}; - const auto color = wf::color_t(tint); - GL_CALL(glDisable(GL_DEPTH_TEST)); GL_CALL(glDisable(GL_CULL_FACE)); - if (cloud_dirty) - { - wf::render_target_t cloud_target{cloud_buffer}; cloud_target.geometry = box; - cloud_target.scale = cloud_scale; - wf::gles::bind_render_buffer(cloud_target); - GL_CALL(glDisable(GL_BLEND)); - cloud_program.use(wf::TEXTURE_TYPE_RGBA); - cloud_program.uniformMatrix4f("mvp", wf::gles::render_target_orthographic_projection(cloud_target)); - cloud_program.uniform2f("resolution", width, height); - cloud_program.uniform1f("phase", cloud_phase); - cloud_program.uniform1f("epoch", cloud_epoch); - cloud_program.uniform1f("cloud_density", std::clamp(double(clouds), 0.0, 2.0)); - cloud_program.uniform1f("turbulence", std::clamp(double(turbulence), 0.0, 2.0)); - cloud_program.uniform1f("variation", std::clamp(double(variation), 0.0, 2.0)); - cloud_program.uniform1f("fractal_detail",std::clamp(double(fractal_detail),0.0,1.0)); - cloud_program.uniform1f("colour_variation", std::clamp(double(cloud_colours), 0.0, 1.0)); - cloud_program.uniform3f("scene_offset", 64 * star_random(scene_seed), - 64 * star_random(scene_seed + 1), 64 * star_random(scene_seed + 2)); - cloud_program.uniform1f("ribbons", std::clamp(double(ribbons), 0.0, 2.0)); - cloud_program.uniform1f("glow", std::clamp(double(glow), 0.0, 2.0)); - cloud_program.uniform1f("brightness", std::clamp(double(brightness), 0.0, 2.0)); - cloud_program.uniform1f("energy", 1 + boost * double(surge) * 0.20); - cloud_program.uniform3f("tint", color.r, color.g, color.b); - cloud_program.uniform1i("detail", level); - cloud_program.attrib_pointer("position", 2, 0, quad); - wf::gles::for_each_scissor_rect(cloud_target, box, [&] { GL_CALL(glDrawArrays(GL_TRIANGLES,0,6)); }); - cloud_program.deactivate(); - cloud_dirty = false; - } - wf::render_target_t backdrop_target{backdrop_buffer}; - backdrop_target.geometry = box; backdrop_target.scale = target.scale; - auto draw_stars = [&] (int layer) - { - const int count = layer == 0 ? far_count : layer == 1 ? dust_count : layer == 2 ? near_count : middle_count; - if (!count) return; - star_program.use(wf::TEXTURE_TYPE_RGBA); - star_program.uniformMatrix4f("mvp", wf::gles::render_target_orthographic_projection(backdrop_target)); - star_program.uniform2f("resolution", width, height); - // Derive the deeper journey from the same bounded world clock. Its - // 64-unit period divides the full 4096*12-unit clock, including wrap. - const double deep_clock = travel_epoch * 12.0 + travel; - star_program.uniform1f("travel", layer == 3 ? std::fmod(deep_clock, 64.0) : travel); - star_program.uniform1f("speed", rendered_speed); - star_program.uniform1f("epoch", layer == 3 ? std::floor(deep_clock / 64.0) : travel_epoch); - star_program.uniform1f("drift", drift_phase); - star_program.uniform1f("brightness", std::clamp(double(brightness), 0.0, 2.0)); - star_program.uniform1f("coloured_stars", std::clamp(double(coloured), 0.0, 1.0)); - star_program.uniform1f("glow", std::clamp(double(glow), 0.0, 2.0)); - star_program.uniform1f("backdrop_opacity", 1.0); - star_program.uniform1i("layer", layer); - GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, star_buffer)); - star_program.attrib_pointer("seed",4,sizeof(hyperspace_star_t),reinterpret_cast<void*>(offsetof(hyperspace_star_t,seed))); - star_program.attrib_pointer("corner",2,sizeof(hyperspace_star_t),reinterpret_cast<void*>(offsetof(hyperspace_star_t,corner))); - GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE,GL_ONE)); - wf::gles::for_each_scissor_rect(backdrop_target,box,[&] - { GL_CALL(glDrawArrays(GL_TRIANGLES,layer == 0 ? 0 : layer == 1 ? far_count * 6 : - (far_count + dust_count + (layer == 3 ? near_count : 0)) * 6,count * 6)); }); - GL_CALL(glDisable(GL_BLEND)); star_program.deactivate(); GL_CALL(glBindBuffer(GL_ARRAY_BUFFER,0)); - }; - if (backdrop_dirty) - { - wf::gles::bind_render_buffer(backdrop_target); - wf::gles::for_each_scissor_rect(backdrop_target,box,[&] { OpenGL::clear({0,0,0,0}); }); - draw_stars(0); - if (double(galaxies)>0) { - if (!galaxy_field) galaxy_field=std::make_unique<ambient_galaxy_field>(); - galaxy_field->render(backdrop_target,box,scene_seed,drift_phase, - level,std::clamp(double(galaxies),0.0,2.0), - std::clamp(double(brightness),0.0,2.0)); - } - draw_stars(3); - if (double(pulsars) > 0 && pulsar_age >= 0) - { - const float size = pulsar_size * height * 1.4f; - const float x = pulsar_x * width, y = pulsar_y * height; - const GLfloat vertices[] = {x-size,y-size,x+size,y-size,x+size,y+size, - x-size,y-size,x+size,y+size,x-size,y+size}; - pulsar_program.use(wf::TEXTURE_TYPE_RGBA); - pulsar_program.uniformMatrix4f("mvp", wf::gles::render_target_orthographic_projection(backdrop_target)); - pulsar_program.uniform2f("resolution", width, height); - pulsar_program.uniform2f("centre", x, y); - pulsar_program.uniform1f("size", pulsar_size); - pulsar_program.uniform1f("projection", pulsar_projection); - pulsar_program.uniform1f("angle", pulsar_angle + pulsar_age * pulsar_rotation); - // Smooth fades and a quiet breathing core, never a strobe. - const double fade = std::min({1.0, pulsar_age / 2.5, (pulsar_duration - pulsar_age) / 3.5}); - pulsar_program.uniform1f("strength", std::clamp(double(pulsars), 0.0, 2.0) * - std::clamp(double(brightness), 0.0, 2.0) * fade * fade); - pulsar_program.attrib_pointer("position", 2, 0, vertices); - GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE,GL_ONE)); - wf::gles::for_each_scissor_rect(backdrop_target,box,[&] { GL_CALL(glDrawArrays(GL_TRIANGLES,0,6)); }); - GL_CALL(glDisable(GL_BLEND)); pulsar_program.deactivate(); - } - if (double(anomalies) > 0 && anomaly_age >= 0) - { - const float size = anomaly_size * height * 1.7f; - const float x = anomaly_x * width, y = anomaly_y * height; - const GLfloat vertices[] = {x-size,y-size,x+size,y-size,x+size,y+size, - x-size,y-size,x+size,y+size,x-size,y+size}; - anomaly_program.use(wf::TEXTURE_TYPE_RGBA); - anomaly_program.uniformMatrix4f("mvp", wf::gles::render_target_orthographic_projection(backdrop_target)); - anomaly_program.uniform2f("resolution", width, height); - anomaly_program.uniform2f("centre", x, y); - anomaly_program.uniform1f("size", anomaly_size); - anomaly_program.uniform1f("angle", anomaly_angle); - anomaly_program.uniform1f("shape", anomaly_shape); - anomaly_program.uniform1f("age", anomaly_age); - anomaly_program.uniform1i("kind", anomaly_kind); - const double fade = std::min({1.0, anomaly_age / 4.0, (anomaly_duration - anomaly_age) / 5.0}); - const double envelope = std::clamp(fade, 0.0, 1.0); - anomaly_program.uniform1f("strength", std::clamp(double(anomalies), 0.0, 2.0) * - std::clamp(double(brightness), 0.0, 2.0) * envelope * envelope * (3 - 2 * envelope)); - anomaly_program.attrib_pointer("position", 2, 0, vertices); - GL_CALL(glEnable(GL_BLEND)); GL_CALL(glBlendFunc(GL_ONE,GL_ONE)); - wf::gles::for_each_scissor_rect(backdrop_target,box,[&] { GL_CALL(glDrawArrays(GL_TRIANGLES,0,6)); }); - GL_CALL(glDisable(GL_BLEND)); anomaly_program.deactivate(); - } - draw_stars(1); - auto clouds = wf::gles_texture_t::from_aux(cloud_buffer); - wf::gles::for_each_scissor_rect(backdrop_target,box,[&] - { OpenGL::render_texture(clouds,backdrop_target,box); }); - GL_CALL(glDisable(GL_BLEND)); - draw_stars(2); - if (double(asteroids)>0) { - if (!asteroid_field) asteroid_field=std::make_unique<ambient_asteroid_field>(); - asteroid_field->render(backdrop_target,box,scene_seed,travel_epoch*12.0+travel, - level,std::clamp(double(asteroids),0.0,2.0),std::clamp(double(brightness),0.0,2.0)); - } - backdrop_dirty = false; - } - // Composite once so 50% opacity really is half of the complete effect, - // including the stars behind the absorbing clouds. Reuse frozen frames. - wf::gles::bind_render_buffer(target); - auto texture = wf::gles_texture_t::from_aux(backdrop_buffer); - wf::gles::for_each_scissor_rect(target, damage, [&] - { - OpenGL::render_texture(texture,target,box,glm::vec4(1,1,1,opacity)); - }); - GL_CALL(glDisable(GL_BLEND)); -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-background.hpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-background.hpp deleted file mode 100644 index 3055dfbcb4ea..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-background.hpp +++ /dev/null @@ -1,84 +0,0 @@ -#pragma once - -#ifdef AMBIENT_LOCK_RENDERER -#include "hyperspace-standalone.hpp" -#else -#include <wayfire/option-wrapper.hpp> -#include <wayfire/opengl.hpp> -#endif -#include <chrono> -#include <cstdint> -#include <functional> -#include <array> -#include <memory> - -class __attribute__((visibility("hidden"))) ambient_asteroid_field; -class __attribute__((visibility("hidden"))) ambient_galaxy_field; - -// A self-contained procedural backdrop: layered nebulae, independent dust, -// distant stars and passing stars, with occasional distant pulsar jets. -class __attribute__((visibility("hidden"))) ambient_hyperspace_background -{ - wf::option_wrapper_t<double> speed{"ambient-background/hyperspace_speed"}; - wf::option_wrapper_t<double> clouds{"ambient-background/hyperspace_cloud_density"}; - wf::option_wrapper_t<double> turbulence{"ambient-background/hyperspace_turbulence"}; - wf::option_wrapper_t<double> ribbons{"ambient-background/hyperspace_ribbon_strength"}; - wf::option_wrapper_t<double> glow{"ambient-background/hyperspace_glow"}; - wf::option_wrapper_t<double> brightness{"ambient-background/hyperspace_brightness"}; - wf::option_wrapper_t<double> surge{"ambient-background/hyperspace_surge"}; - wf::option_wrapper_t<double> density{"ambient-background/hyperspace_star_density"}; - wf::option_wrapper_t<double> coloured{"ambient-background/hyperspace_coloured_stars"}; - wf::option_wrapper_t<double> distant_density{"ambient-background/hyperspace_distant_stars"}; - wf::option_wrapper_t<double> distant_drift{"ambient-background/hyperspace_distant_drift"}; - wf::option_wrapper_t<double> fractal_detail{"ambient-background/hyperspace_fractal_detail"}; - wf::option_wrapper_t<double> galaxies{"ambient-background/hyperspace_galaxies"}; - wf::option_wrapper_t<double> dust_density{"ambient-background/hyperspace_dust_density"}; - wf::option_wrapper_t<double> variation{"ambient-background/hyperspace_cloud_variation"}; - wf::option_wrapper_t<double> cloud_colours{"ambient-background/hyperspace_cloud_colours"}; - wf::option_wrapper_t<double> pulsars{"ambient-background/hyperspace_pulsars"}; - wf::option_wrapper_t<double> anomalies{"ambient-background/hyperspace_anomalies"}; - wf::option_wrapper_t<double> asteroids{"ambient-background/hyperspace_asteroids"}; - wf::option_wrapper_t<int> seed_option{"ambient-background/hyperspace_seed"}; - wf::option_wrapper_t<wf::color_t> tint{"ambient-background/hyperspace_color"}; - wf::option_wrapper_t<int> quality{"ambient-background/hyperspace_quality"}; - wf::option_wrapper_t<int> fps{"ambient-background/hyperspace_fps"}; - OpenGL::program_t cloud_program, star_program, pulsar_program, anomaly_program; - std::unique_ptr<ambient_asteroid_field> asteroid_field; - std::unique_ptr<ambient_galaxy_field> galaxy_field; - wf::auxilliary_buffer_t cloud_buffer, backdrop_buffer; - GLuint star_buffer = 0; - int near_count = -1, far_count = -1, dust_count = -1, middle_count = -1; - int configured_seed = -1; - uint32_t scene_seed = 1, event_number = 0, anomaly_number = 0; - unsigned travel_epoch = 0, cloud_epoch = 0; - bool active = false, transition = false, cloud_dirty = true, backdrop_dirty = true; - double travel = 0, cloud_phase = 0, drift_phase = 0, boost = 0; - double rendered_speed = 0; - double pulsar_age = -3, pulsar_duration = 15; - float pulsar_x = 0.5f, pulsar_y = 0.3f, pulsar_angle = 0, pulsar_rotation = 0.2f; - float pulsar_size = 0.13f, pulsar_projection = 0.65f; - double anomaly_age = -15, anomaly_duration = 18; - float anomaly_x = 0, anomaly_y = 0, anomaly_size = 0.08f; - float anomaly_angle = 0, anomaly_shape = 0; - int anomaly_kind = 0; - std::chrono::steady_clock::time_point last_frame; - - void load_programs(); - void update_particles(); - void prepare_pulsar(); - void prepare_anomaly(); - - public: - explicit ambient_hyperspace_background(std::function<void()> changed); - ~ambient_hyperspace_background(); - void set_active(bool enabled); - void set_transition(bool moving); - bool animated() const; - unsigned frame_interval() const; - void invalidate() { cloud_dirty = backdrop_dirty = true; } - bool has_scene() const { return configured_seed >= 0; } - std::array<double,24> scene() const; - bool restore_scene(const std::array<double,24>& state); - void render(const wf::render_target_t& target, const wf::regionf_t& damage, - wf::dimensions_t resolution, float opacity = 1.0f); -}; diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-galaxies.tpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-galaxies.tpp deleted file mode 100644 index 6c3ba1340fb6..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-galaxies.tpp +++ /dev/null @@ -1,54 +0,0 @@ -#include "hyperspace-galaxy-shaders.tpp" - -// Own the deep layer separately; no exported methods can bind to a resident -// earlier plugin during a reload. The lock client uses the same implementation. -class __attribute__((visibility("hidden"))) ambient_galaxy_field -{ - OpenGL::program_t program; - public: - ~ambient_galaxy_field() { program.free_resources(); } - void render(const wf::render_target_t& target,const wf::geometry_t& box, - uint32_t seed,double drift,int detail,double density,double brightness) - { - if (!program.get_program_id(wf::TEXTURE_TYPE_RGBA)) - program.set_simple(OpenGL::compile_program(hyperspace_galaxy_vertex,hyperspace_galaxy_fragment)); - const double aspect=box.width/box.height; - const int count=std::clamp(int(std::ceil(4*density*aspect)),0,128); - const GLfloat corners[]={-1,-1,1,-1,1,1,-1,-1,1,1,-1,1}; - program.use(wf::TEXTURE_TYPE_RGBA); - program.uniformMatrix4f("mvp",wf::gles::render_target_orthographic_projection(target)); - program.uniform1i("detail",detail); - GL_CALL(glBindBuffer(GL_ARRAY_BUFFER,0)); - program.attrib_pointer("corner",2,0,corners); - GL_CALL(glEnable(GL_BLEND));GL_CALL(glBlendFunc(GL_ONE,GL_ONE)); - for (int i=0;i<count;i++) - { - const uint32_t base=seed^uint32_t(i*31741+971)^0x731a29fu; - auto random=[base](unsigned j){return star_random(base+j*19139);}; - // Share the distant stars' slow, unboosted clock. Near/far fades - // hide recycling, and the 2*pi wrap preserves every visible body. - // Constant world centres and fixed textures give coherent radial - // parallax rather than a sliding image or rapidly changing fog. - const double z=96+std::fmod(random(0)*128-drift/(2*M_PI)*128+128,128); - const double world_x=(2*random(1)-1)*aspect*224; - const double world_y=(2*random(2)-1)*224; - const double world_radius=2.5+std::pow(random(3),2.f)*10.5; - const float radius=world_radius/z*box.height*.5; - const float x=box.width*.5+world_x/z*box.height*.5; - const float y=box.height*.5+world_y/z*box.height*.5; - if (x+radius<0 || x-radius>box.width || y+radius<0 || y-radius>box.height) continue; - const double fade=std::clamp((z-96)/18,0.0,1.0)*std::clamp((224-z)/18,0.0,1.0); - const int kind=random(4)<.52f ? 0 : random(4)<.75f ? 1 : 2; - const float projection=kind==1 ? .20f+random(5)*.13f : .50f+random(5)*.50f; - program.uniform2f("centre",x,y); - program.uniform1f("radius",radius); - program.uniform1f("angle",random(6)*2*M_PI); - program.uniform1f("projection",projection); - program.uniform1i("kind",kind); - program.uniform1f("shape_seed",random(7)*100); - program.uniform1f("strength",brightness*(.55+random(8)*.65)*fade*fade*(3-2*fade)); - wf::gles::for_each_scissor_rect(target,box,[&]{GL_CALL(glDrawArrays(GL_TRIANGLES,0,6));}); - } - GL_CALL(glDisable(GL_BLEND));program.deactivate(); - } -}; diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-galaxy-shaders.tpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-galaxy-shaders.tpp deleted file mode 100644 index 5b16f2712558..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-galaxy-shaders.tpp +++ /dev/null @@ -1,86 +0,0 @@ -static const char *hyperspace_galaxy_vertex=R"( -#version 100 -precision highp float; -attribute vec2 corner; -uniform mat4 mvp; -uniform vec2 centre; -uniform float radius,angle,projection; -varying vec2 local; -void main() -{ - local=corner; - vec2 p=corner*vec2(1.0,projection); - float c=cos(angle),s=sin(angle); - p=vec2(c*p.x-s*p.y,s*p.x+c*p.y); - gl_Position=mvp*vec4(centre+radius*p,0.0,1.0); -} -)"; - -static const char *hyperspace_galaxy_fragment=R"( -#version 100 -precision highp float; -varying vec2 local; -uniform float shape_seed,strength,radius; -uniform int kind,detail; -float hash(vec2 p) -{ - vec3 q=fract(vec3(p.xyx)*vec3(.1031,.1030,.0973)); - q+=dot(q,q.yzx+33.33); - return fract((q.x+q.y)*q.z); -} -float noise(vec2 p) -{ - vec2 i=floor(p),f=fract(p);f=f*f*(3.0-2.0*f); - return mix(mix(hash(i),hash(i+vec2(1,0)),f.x), - mix(hash(i+vec2(0,1)),hash(i+vec2(1)),f.x),f.y); -} -void main() -{ - vec2 p=local; - float r=length(p); - float edge=1.0-smoothstep(.68,1.0,r); - if (edge<=0.0) discard; - vec2 offset=vec2(shape_seed,shape_seed*.37); - float coarse=noise(p*9.0+offset); - float fine=noise(p*47.0+offset.yx); - float resolved=smoothstep(20.0,70.0,radius); - float patch=coarse*.80+mix(.5,fine,resolved)*.20; - float bulge=exp(-r*r*85.0); - float disk=exp(-r*4.6)*edge; - float stars=0.0,dust=0.0; - if (kind==0) - { - float arms=2.0+floor(mod(shape_seed,3.0)); - float theta=atan(p.y+.00001,p.x+.00001); - float phase=theta*arms-(5.5+mod(shape_seed,4.0))*log(r+.12) - +noise(p*4.0+offset)*1.6; - float spiral=pow(.5+.5*cos(phase),7.0); - stars=disk*(.18+spiral*(.6+patch*1.8))*smoothstep(.05,.18,r); - dust=pow(.5+.5*cos(phase+.7),12.0)*smoothstep(.12,.3,r)*.60; - } else if (kind==1) - { - // A thin tilted disc with a bulge and a dark, irregular dust lane. - stars=disk*(.38+patch*.70); - bulge=exp(-dot(p*vec2(5.0,2.3),p*vec2(5.0,2.3))); - float lane=p.y+.08*(coarse-.5); - dust=exp(-lane*lane*190.0)*(.55+patch*.30)*smoothstep(.03,.18,abs(p.x)); - } else - { - // Older stellar populations form warmer, smooth elliptical halos. - bulge=exp(-r*13.0); - stars=disk*(.26+patch*.08); - } - vec3 core=vec3(1.0,.84,.61); - vec3 arms_colour=mix(vec3(.58,.72,1.0),vec3(.83,.72,.89),mod(shape_seed*.73,1.0)); - if (kind==2) arms_colour=vec3(.88,.77,.64); - vec3 light=(core*bulge*.55+arms_colour*stars*.48)*(1.0-dust); - if (detail>0 && kind!=2) - { - // Sparse clusters stay attached to the arms, without temporal noise. - float clusters=pow(fine,12.0)*stars*resolved*(detail==2 ? .35 : .18); - light+=vec3(.75,.85,1.0)*clusters; - } - light*=edge*strength; - gl_FragColor=vec4(light,max(max(light.r,light.g),light.b)); -} -)"; diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-lock.cpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-lock.cpp deleted file mode 100644 index bca6ae82730f..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-lock.cpp +++ /dev/null @@ -1,116 +0,0 @@ -#include "hyperspace-background.hpp" -#include "../nebula/nebula-background.hpp" -#include "hyperspace-lock.h" -#include <memory> - -namespace ambient_lock_host { -thread_local json_object *parameters=nullptr; -thread_local OpenGL::program_t *texture_program=nullptr; -} -namespace { -struct compositor { - OpenGL::program_t texture;wf::auxilliary_buffer_t frame; - ~compositor() { texture.free_resources();frame.free(); } -}; -struct space { - compositor drawing; - json_object *parameters=nullptr; - std::unique_ptr<ambient_hyperspace_background> engine; - std::unique_ptr<ambient_nebula_background> nebula; - bool nebula_selected() const { - json_object *value=nullptr; - return json_object_object_get_ex(parameters,"_backend",&value) && - std::string(json_object_get_string(value))=="nebula"; - } - void choose() { - ambient_lock_host::parameters=parameters; - if (nebula_selected()) { - if (nebula) return; - engine.reset();nebula=std::make_unique<ambient_nebula_background>([]{}); - json_object *scene=nullptr; - if (json_object_object_get_ex(parameters,"_scene",&scene) && - json_object_is_type(scene,json_type_array) && json_object_array_length(scene)==2) - nebula->restore_scene({json_object_get_double(json_object_array_get_idx(scene,0)), - json_object_get_double(json_object_array_get_idx(scene,1))}); - return; - } - if (engine) return; - nebula.reset(); - engine=std::make_unique<ambient_hyperspace_background>([]{}); - json_object *scene=nullptr; - if (json_object_object_get_ex(parameters,"_scene",&scene) && - json_object_is_type(scene,json_type_array) && json_object_array_length(scene)==24) { - std::array<double,24> values; - for (unsigned i=0;i<values.size();++i) values[i]=json_object_get_double(json_object_array_get_idx(scene,i)); - engine->restore_scene(values); - } - } - explicit space(json_object *p):parameters(json_object_get(p)) { - choose(); - } - ~space() { engine.reset();nebula.reset();json_object_put(parameters); } -}; -wf::render_target_t target(int width,int height,GLuint framebuffer=0) { - wf::auxilliary_buffer_t buffer;buffer.width=width;buffer.height=height;buffer.framebuffer=framebuffer; - wf::render_target_t t(buffer);t.geometry={0,0,double(width),double(height)};return t; -} -template<class F> bool checked(F fn) { - try { fn();return true; } catch (const std::exception& e) { - fprintf(stderr,"%s\n",e.what());return false; - } -} -} -extern "C" void *ambient_space_create(json_object *settings) { - space *result=nullptr;checked([&]{result=new space(settings);});return result; -} -extern "C" void ambient_space_destroy(void *renderer) { delete static_cast<space*>(renderer); } -extern "C" bool ambient_space_update(void *renderer,json_object *settings) { - if (!renderer || !settings) return false; - auto s=static_cast<space*>(renderer); - // Mirror native option callbacks while retaining the running scene. - if (!json_object_equal(s->parameters,settings)) { - json_object_get(settings);json_object_put(s->parameters);s->parameters=settings; - s->choose(); - if (s->engine) s->engine->invalidate(); - if (s->nebula) s->nebula->invalidate(); - } - return true; -} -extern "C" bool ambient_space_draw(void *renderer,int width,int height,bool moving) { - if (!renderer) return false; - auto s=static_cast<space*>(renderer); - return checked([&]{ - ambient_lock_host::parameters=s->parameters;ambient_lock_host::texture_program=&s->drawing.texture; - GLint fb=0;glGetIntegerv(GL_FRAMEBUFFER_BINDING,&fb); - auto t=target(width,height,fb);glBindBuffer(GL_ARRAY_BUFFER,0); - if (s->nebula) { - s->nebula->set_active(moving);s->nebula->render(t,t.geometry,{width,height}); - } else { - s->engine->set_active(moving);s->engine->render(t,t.geometry,{width,height}); - } - }); -} -extern "C" void *ambient_lock_composite_create() { return new compositor; } -extern "C" void ambient_lock_composite_destroy(void *renderer) { delete static_cast<compositor*>(renderer); } -extern "C" bool ambient_lock_composite_begin(void *renderer,int width,int height) { - auto c=static_cast<compositor*>(renderer); - return checked([&]{c->frame.allocate({width,height},1);wf::gles::bind_render_buffer(target(width,height,c->frame.framebuffer));}); -} -extern "C" bool ambient_lock_composite_end(void *renderer,int width,int height,float brightness) { - auto c=static_cast<compositor*>(renderer); - return checked([&]{ - ambient_lock_host::texture_program=&c->texture;auto t=target(width,height); - wf::gles::bind_render_buffer(t);glDisable(GL_SCISSOR_TEST);OpenGL::clear({0,0,0,1}); - OpenGL::render_texture(wf::gles_texture_t::from_aux(c->frame),t,t.geometry,glm::vec4(brightness,brightness,brightness,1)); - glDisable(GL_BLEND); - }); -} -extern "C" bool ambient_lock_composite_overlay(void *renderer,int width,int height,float opacity) { - auto c=static_cast<compositor*>(renderer); - return checked([&]{ - ambient_lock_host::texture_program=&c->texture;auto t=target(width,height); - wf::gles::bind_render_buffer(t);glDisable(GL_SCISSOR_TEST); - OpenGL::render_texture(wf::gles_texture_t::from_aux(c->frame),t,t.geometry,glm::vec4(1,1,1,opacity)); - glDisable(GL_BLEND); - }); -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-lock.h b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-lock.h deleted file mode 100644 index 484ffccd3715..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-lock.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once -#include <json-c/json.h> -#include <stdbool.h> -#ifdef __cplusplus -extern "C" { -#endif -// Call with the output's EGL context current. All rendering stays inside the -// authenticated lock surface; no compositor objects cross this interface. -void *ambient_space_create(struct json_object *settings); -void ambient_space_destroy(void *renderer); -bool ambient_space_update(void *renderer, struct json_object *settings); -bool ambient_space_draw(void *renderer, int width, int height, bool moving); -void *ambient_lock_composite_create(void); -void ambient_lock_composite_destroy(void *renderer); -bool ambient_lock_composite_begin(void *renderer, int width, int height); -bool ambient_lock_composite_end(void *renderer, int width, int height, float brightness); -bool ambient_lock_composite_overlay(void *renderer, int width, int height, float opacity); -#ifdef __cplusplus -} -#endif diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-shaders.tpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-shaders.tpp deleted file mode 100644 index 87dd9a9a710e..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-shaders.tpp +++ /dev/null @@ -1,278 +0,0 @@ -static const char *hyperspace_vertex = R"( -#version 100 -attribute vec2 position; -uniform mat4 mvp; -uniform vec2 resolution; -varying vec2 uv; -void main() { uv = position / resolution; gl_Position = mvp * vec4(position,0.0,1.0); } -)"; - -static const char *hyperspace_cloud_fragment = R"( -#version 100 -precision highp float; -uniform vec2 resolution; -uniform vec3 tint; -uniform float phase, epoch, cloud_density, turbulence, variation, ribbons, glow, brightness, energy; -uniform float colour_variation, fractal_detail; -uniform vec3 scene_offset; -uniform int detail; -varying vec2 uv; - -float hash3(vec3 p) -{ - // Repeat every 64 cells, including octave-scaled coordinates. The CPU - // clock can wrap without a seam or lost precision after hours of travel. - p = mod(p,64.0); - p = fract(p * vec3(0.1031,0.1030,0.0973)); - p += dot(p,p.yxz + 33.33); - return fract((p.x + p.y) * p.z); -} -float noise3(vec3 p) -{ - vec3 c = floor(p), f = fract(p); f = f*f*(3.0-2.0*f); - return mix(mix(mix(hash3(c),hash3(c+vec3(1,0,0)),f.x), - mix(hash3(c+vec3(0,1,0)),hash3(c+vec3(1,1,0)),f.x),f.y), - mix(mix(hash3(c+vec3(0,0,1)),hash3(c+vec3(1,0,1)),f.x), - mix(hash3(c+vec3(0,1,1)),hash3(c+vec3(1,1,1)),f.x),f.y),f.z); -} -float fbm(vec3 p) -{ - float result=0.0, amplitude=0.55; - for (int i=0;i<5;i++) - { - if (i < detail+3) result += noise3(p)*amplitude; - p=p*2.0+vec3(7,13,19); amplitude*=0.5; - } - return result; -} -float bank_random(vec3 p) -{ - p=fract(p*vec3(0.1031,0.1030,0.0973)); - p+=dot(p,p.yxz+33.33); - return fract((p.x+p.y)*p.z); -} -vec2 rotate_xy(vec2 p, vec2 axis) -{ - return vec2(dot(p,axis),dot(p,vec2(-axis.y,axis.x))); -} -void main() -{ - float aspect=resolution.x/resolution.y; - vec3 ray=vec3((uv-0.5)*vec2(aspect,1.0)*2.0,1.0); - vec3 emission=vec3(0.0); - float extinction=0.0; - int banks=10+int(min(6.0,max(0.0,aspect-1.5)*2.0)); - // Discrete, seeded volumes occupy real world positions. Their centres - // approach the camera, so silhouettes expand and move outwards instead - // of boiling on camera-relative sheets. Recycling happens only while a - // bank is invisible at the near/far limits. - for (int i=0;i<16;i++) - { - if (i < banks && cloud_density > 0.0) - { - vec3 slot=scene_offset+vec3(float(i)*19.0,7.0,23.0); - float initial=mod((float(i)+bank_random(slot))*64.0/float(banks),64.0); - float cycle=mod(epoch+step(initial,phase),4096.0); - vec3 key=slot+vec3(cycle*1.17,cycle*0.73,cycle*0.31); - // Each bank retains its spectral character while it travels. - // Colours belong to the volume, never to screen-space regions. - float hue=bank_random(key+277.0); - vec3 palette=hue<0.22 ? vec3(0.25,0.58,1.0) : - hue<0.46 ? vec3(0.65,0.30,1.0) : - hue<0.68 ? vec3(0.20,0.78,0.70) : - hue<0.86 ? vec3(0.91,0.35,0.58) : vec3(1.0,0.63,0.26); - vec3 bank_tint=mix(tint,palette,colour_variation); - vec3 random=vec3(bank_random(key),bank_random(key+17.0),bank_random(key+43.0)); - vec3 centre=vec3((random.xy*2.0-1.0)*vec2(aspect*23.0,18.0), - mod(initial-phase,64.0)+4.0); - float visibility=smoothstep(4.0,12.0,centre.z)*(1.0-smoothstep(54.0,68.0,centre.z)); - vec3 radius=vec3(7.0+random.z*10.0,2.5+bank_random(key+71.0)*5.0, - 4.0+bank_random(key+97.0)*5.0); - float angle=bank_random(key+131.0)*6.2831853; - vec2 axis=vec2(cos(angle),sin(angle)); - vec3 origin=vec3(rotate_xy(-centre.xy,axis),-centre.z)/radius; - vec3 direction=vec3(rotate_xy(ray.xy,axis),ray.z)/radius; - float a=dot(direction,direction),b=dot(origin,direction); - float discriminant=b*b-a*(dot(origin,origin)-1.0); - if (discriminant > 0.0 && visibility > 0.0) - { - float root=sqrt(discriminant); - float entry=max(3.0,(-b-root)/a),exit=(-b+root)/a; - if (exit > entry) - { - float samples=float(3+detail); - float step_size=(exit-entry)/samples; - vec3 offset=vec3(bank_random(key+157.0),bank_random(key+193.0), - bank_random(key+211.0))*32.0; - // Local structure stays attached to the bank, including - // its torn edges and dark holes. All three coordinates - // use the same scale; time never animates the noise. - for (int j=0;j<5;j++) - { - if (j < 3+detail) - { - float distance=entry+(float(j)+0.5)*step_size; - vec3 local=origin+direction*distance; - vec3 position=local*radius; - float envelope=1.0-smoothstep(0.35,1.0,dot(local,local)); - float cover=noise3(local*1.8+offset); - float bend=noise3(position*0.16+offset.yzx); - vec3 fine=position*0.48+offset.zxy; - fine+=vec3(bend-0.5,cover-0.5,bend-cover)*turbulence*1.8; - float structure=fbm(fine); - if (fractal_detail>0.0) { - float folded=nebula_field(position*0.08+offset*0.015,7.0,vec3(-0.5,-0.8,-1.1)); - structure=mix(structure,clamp(folded*0.42,0.0,1.0),fractal_detail); - } - float gaps=smoothstep(0.34+variation*0.055,0.72,cover); - float wisps=smoothstep(0.30+variation*0.025,0.68,structure); - float density=envelope*gaps*wisps*cloud_density*visibility; - float optical_depth=density*step_size*0.22; - float filament=pow(max(0.0,1.0-abs(structure*2.0-1.0)),8.0); - float highlights=pow(max(0.0,structure-0.30)*1.8,3.0); - vec3 silver=mix(vec3(0.60,0.65,0.72),bank_tint,0.70); - vec3 lit=mix(vec3(0.90,0.94,1.0),bank_tint,0.60); - vec3 light=silver*0.18+lit*(highlights*0.90+ - filament*ribbons*0.26)*(0.55+glow*0.55); - extinction+=optical_depth; - emission+=light*optical_depth; - } - } - } - } - } - } - // Glow illuminates the dust itself; there is no camera-fixed central fog - // or halo. One premultiplied layer preserves wallpaper and fade opacity. - float alpha=1.0-exp(-extinction); - vec3 color=emission*(alpha/max(extinction,0.00001))*brightness*energy; - gl_FragColor=vec4(color,alpha); -} -)"; - -static const char *hyperspace_star_vertex = R"( -#version 100 -precision highp float; -attribute vec4 seed; -attribute vec2 corner; -uniform mat4 mvp; -uniform vec2 resolution; -uniform float travel, speed, drift, brightness, coloured_stars, epoch; -uniform int layer; -varying vec2 local; -varying float trail_length, radius, luminosity; -varying vec3 star_color; -float random2(vec2 p) -{ - vec3 q=fract(vec3(p.xyx)*vec3(0.1031,0.1030,0.0973)); - q+=dot(q,q.yzx+33.33); - return fract((q.x+q.y)*q.z); -} -void main() -{ - vec2 head,tail; - float pixel_scale=clamp(resolution.y/1080.0,0.65,2.0); - float spectral_seed=seed.w; - if (layer==0) - { - vec2 offset=vec2(sin(drift),cos(drift)-1.0)*resolution.y*0.07; - head=mod((seed.xy*0.5+0.5)*resolution+offset,resolution)-resolution*0.5; - tail=head; - radius=(0.55+1.15*seed.w*seed.w)*pixel_scale; - luminosity=brightness*(0.14+0.76*pow(seed.w,3.0))*(0.94+0.06*sin(drift*8.0+seed.z*50.0)); - } else - { - float span=layer==3 ? 64.0 : 12.0; - float near_limit=layer==3 ? 8.0 : 0.45; - float far_limit=near_limit+span; - float z=mod(seed.z*span-travel,span)+near_limit; - // Resample only when a particle returns beyond the horizon. The - // epoch clock wraps coherently for particles still on this pass. - float cycle=mod(epoch+step(seed.z*span,travel),layer==3 ? 768.0 : 4096.0); - vec2 key=seed.xy*37.0+vec2(cycle,cycle*0.37); - vec2 position=vec2(random2(key),random2(key+vec2(17,43)))*2.0-1.0; - float size_seed=random2(key+vec2(91,29)); - spectral_seed=random2(key+vec2(7,61)); - // Populate the full viewing frustum at the horizon. A narrow fixed - // cylinder concentrates far particles into a small central cone, - // especially on panoramic outputs, leaving an artificial empty gap. - vec2 world=position*vec2(resolution.x/resolution.y,1.0)*far_limit*1.02; - head=world/z*resolution.y*0.5; - float exposure=layer==1 ? 0.065 : layer==3 ? 0.12 : 0.42; - tail=world/(z+speed*exposure)*resolution.y*0.5; - float fade=smoothstep(near_limit,near_limit+(layer==3 ? 5.0 : 0.65),z)* - (1.0-smoothstep(far_limit-(layer==3 ? 10.0 : 2.45),far_limit,z)); - if (layer==3) - { - radius=(0.55+1.10*size_seed*size_seed)*pixel_scale*(0.7+5.0/(z+2.0)); - luminosity=brightness*fade*(0.11+0.85*size_seed*size_seed); - } else if (layer==1) - { - radius=(0.38+0.80*size_seed*size_seed)*pixel_scale*(0.75+0.7/(z+0.5)); - luminosity=brightness*fade*(0.04+0.32*pow(size_seed,3.0)); - } else - { - radius=(0.8+2.2*pow(size_seed,4.0))*pixel_scale*(0.7+1.0/(z+0.5)); - luminosity=brightness*fade*(0.22+1.45*pow(size_seed,4.0)); - } - } - vec2 direction=normalize(head-tail+vec2(0.00001)),normal=vec2(-direction.y,direction.x); - trail_length=min(length(head-tail),resolution.y*(layer==1 ? 0.065 : layer==3 ? 0.035 : 0.35)); - tail=head-direction*trail_length; - local=vec2(corner.x*(trail_length+2.0*radius)-radius,corner.y*radius); - vec2 pixel=resolution*0.5+tail+direction*local.x+normal*local.y; - gl_Position=mvp*vec4(pixel,0.0,1.0); - float chance=fract(spectral_seed*31.37),spectral=fract(spectral_seed*17.0); - star_color=layer==1 ? vec3(0.74,0.78,0.82) : vec3(1.0); - if (layer!=1 && chance<coloured_stars) - star_color=spectral<0.12 ? vec3(1.0,0.50,0.32) : - (spectral<0.40 ? vec3(1.0,0.86,0.62) : vec3(0.60,0.78,1.0)); -} -)"; - -static const char *hyperspace_star_fragment = R"( -#version 100 -precision highp float; -uniform float glow,backdrop_opacity; -varying vec2 local; -varying float trail_length,radius,luminosity; -varying vec3 star_color; -void main() -{ - vec2 head=vec2(local.x-trail_length,local.y)/radius; - float along=clamp(local.x/max(trail_length,0.01),0.0,1.0); - float end=max(max(-local.x,local.x-trail_length),0.0); - float d=(local.y*local.y+end*end)/(radius*radius); - float core=exp(-dot(head,head)*18.0); - float trail=exp(-d*15.0)*(0.10+0.65*along); - float halo=exp(-d*4.0)*0.16*glow; - float edge=1.0-smoothstep(0.75,1.0,d); - gl_FragColor=vec4(star_color*luminosity*(core+trail+halo)*edge*backdrop_opacity,0.0); -} -)"; - - -static const char *hyperspace_pulsar_fragment = R"( -#version 100 -precision highp float; -uniform vec2 resolution, centre; -uniform float size, projection, angle, strength; -varying vec2 uv; -void main() -{ - vec2 p=(uv*resolution-centre)/resolution.y; - float c=cos(angle),s=sin(angle); - vec2 axis=vec2(c,s),normal=vec2(-s,c); - float along=abs(dot(p,axis))/size; - float across=dot(p,normal)/size; - float width=0.014+0.11*pow(along,1.25)*projection; - float cone=exp(-across*across/(width*width))*exp(-along*2.8); - cone*=1.0-smoothstep(0.72,1.15,along); - float distance=length(p)/size; - float core=exp(-distance*distance*4200.0); - float bloom=exp(-distance*distance*220.0)*0.18; - vec3 color=vec3(0.45,0.70,1.0)*cone*0.48+ - vec3(0.88,0.95,1.0)*(core*1.8+bloom); - gl_FragColor=vec4(color*strength,0.0); -} -)"; diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-standalone.hpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-standalone.hpp deleted file mode 100644 index b542a01efbe2..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/hyperspace/hyperspace-standalone.hpp +++ /dev/null @@ -1,160 +0,0 @@ -#pragma once - -// The lock client hosts the same renderer and shaders in its own EGL context. -// This narrow adapter supplies the option, buffer and drawing operations used -// by that renderer; it never links the compositor or reads desktop surfaces. -#include <GLES2/gl2.h> -#include <glm/gtc/matrix_transform.hpp> -#include <glm/gtc/type_ptr.hpp> -#include <json-c/json.h> -#include <algorithm> -#include <cmath> -#include <cstdio> -#include <functional> -#include <stdexcept> -#include <string> -#include <vector> -#define GL_CALL(x) x - -namespace OpenGL { class program_t; } -namespace ambient_lock_host { -extern thread_local json_object *parameters; -extern thread_local OpenGL::program_t *texture_program; -} -namespace wf { -struct color_t { float r=0, g=0, b=0, a=1; }; -template<class T> class option_wrapper_t { - std::string name; - public: - explicit option_wrapper_t(const char *key) : name(std::string(key).substr(19)) {} - void set_callback(std::function<void()>) {} - operator T() const { - json_object *value=nullptr; - if (!ambient_lock_host::parameters || - !json_object_object_get_ex(ambient_lock_host::parameters,name.c_str(),&value)) return T{}; - return T(json_object_get_double(value)); - } -}; -template<> inline option_wrapper_t<color_t>::operator color_t() const { - json_object *value=nullptr; color_t result; - if (!ambient_lock_host::parameters || - !json_object_object_get_ex(ambient_lock_host::parameters,name.c_str(),&value)) return result; - const char *text=json_object_get_string(value); - if (text && text[0]=='#') { - unsigned r,g,b; - if (sscanf(text+1,"%2x%2x%2x",&r,&g,&b)==3) - result={r/255.f,g/255.f,b/255.f,1}; - } else if (text) sscanf(text,"%f %f %f %f",&result.r,&result.g,&result.b,&result.a); - return result; -} -struct dimensions_t { int width, height; }; -struct geometry_t { double x,y,width,height; }; -using regionf_t=geometry_t; -enum texture_type_t { TEXTURE_TYPE_RGBA }; -enum class buffer_reallocation_result_t { REALLOCATED, UNCHANGED }; -struct auxilliary_buffer_t { - GLuint texture=0, framebuffer=0; int width=0,height=0; - buffer_reallocation_result_t allocate(dimensions_t size,float scale) { - int w=std::max(1,int(std::ceil(size.width*scale))),h=std::max(1,int(std::ceil(size.height*scale))); - if (w==width && h==height && texture) return buffer_reallocation_result_t::UNCHANGED; - free(); width=w;height=h; - glGenTextures(1,&texture);glBindTexture(GL_TEXTURE_2D,texture); - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE); - glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,w,h,0,GL_RGBA,GL_UNSIGNED_BYTE,nullptr); - glGenFramebuffers(1,&framebuffer);glBindFramebuffer(GL_FRAMEBUFFER,framebuffer); - glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_2D,texture,0); - if (glCheckFramebufferStatus(GL_FRAMEBUFFER)!=GL_FRAMEBUFFER_COMPLETE) - throw std::runtime_error("Ambient lock framebuffer allocation failed"); - return buffer_reallocation_result_t::REALLOCATED; - } - void free() { - if (texture) glDeleteTextures(1,&texture); - if (framebuffer) glDeleteFramebuffers(1,&framebuffer); - texture=framebuffer=0; width=height=0; - } -}; -struct render_target_t { - GLuint framebuffer=0;int width=0,height=0;float scale=1; - geometry_t geometry{0,0,0,0}; - explicit render_target_t(const auxilliary_buffer_t& b):framebuffer(b.framebuffer),width(b.width),height(b.height) {} -}; -struct gles_texture_t { - GLuint tex_id; - static gles_texture_t from_aux(const auxilliary_buffer_t& b) { return {b.texture}; } -}; -namespace gles { -template<class F> void run_in_context_if_gles(F&& fn) { fn(); } -inline void bind_render_buffer(const render_target_t& t) { - glBindFramebuffer(GL_FRAMEBUFFER,t.framebuffer);glViewport(0,0,t.width,t.height); -} -inline glm::mat4 render_target_orthographic_projection(const render_target_t& t) { - return glm::ortho(float(t.geometry.x),float(t.geometry.x+t.geometry.width), - float(t.geometry.y+t.geometry.height),float(t.geometry.y),-1.f,1.f); -} -template<class F> void for_each_scissor_rect(const render_target_t&,const regionf_t&,F&& fn) { - glDisable(GL_SCISSOR_TEST);fn(); -} -} -} -namespace OpenGL { -inline GLuint compile_shader(GLenum type,const char *source) { - GLuint shader=glCreateShader(type);glShaderSource(shader,1,&source,nullptr);glCompileShader(shader); - GLint ok=0;glGetShaderiv(shader,GL_COMPILE_STATUS,&ok); - if (!ok) { - char log[2048];glGetShaderInfoLog(shader,sizeof(log),nullptr,log); - glDeleteShader(shader);throw std::runtime_error(std::string("Ambient lock shader: ")+log); - } - return shader; -} -inline GLuint compile_program(const char *vertex,const char *fragment) { - GLuint vs=compile_shader(GL_VERTEX_SHADER,vertex),fs=0,program=0; - try { - fs=compile_shader(GL_FRAGMENT_SHADER,fragment);program=glCreateProgram(); - glAttachShader(program,vs);glAttachShader(program,fs);glLinkProgram(program); - GLint ok=0;glGetProgramiv(program,GL_LINK_STATUS,&ok); - if (!ok) throw std::runtime_error("Ambient lock program link failed"); - } catch (...) { - glDeleteShader(vs);if (fs) glDeleteShader(fs);if (program) glDeleteProgram(program);throw; - } - glDeleteShader(vs);glDeleteShader(fs);return program; -} -class program_t { - GLuint id=0;std::vector<GLuint> attributes; - public: - GLuint get_program_id(wf::texture_type_t) const { return id; } - void set_simple(GLuint value) { id=value; } - void free_resources() { if (id) glDeleteProgram(id);id=0; } - void use(wf::texture_type_t) { glUseProgram(id); } - void uniform1i(const char *key,int a) { glUniform1i(glGetUniformLocation(id,key),a); } - void uniform1f(const char *key,float a) { glUniform1f(glGetUniformLocation(id,key),a); } - void uniform2f(const char *key,float a,float b) { glUniform2f(glGetUniformLocation(id,key),a,b); } - void uniform3f(const char *key,float a,float b,float c) { glUniform3f(glGetUniformLocation(id,key),a,b,c); } - void uniformMatrix4f(const char *key,const glm::mat4& m) { glUniformMatrix4fv(glGetUniformLocation(id,key),1,GL_FALSE,glm::value_ptr(m)); } - void attrib_pointer(const char *key,int n,int stride,const void *data) { - GLint a=glGetAttribLocation(id,key);if (a<0) return; - glEnableVertexAttribArray(a);glVertexAttribPointer(a,n,GL_FLOAT,GL_FALSE,stride,data);attributes.push_back(a); - } - void deactivate() { for (auto a:attributes) glDisableVertexAttribArray(a);attributes.clear();glUseProgram(0); } -}; -inline void clear(const glm::vec4& c) { glClearColor(c.r,c.g,c.b,c.a);glClear(GL_COLOR_BUFFER_BIT); } -inline void render_texture(wf::gles_texture_t texture,const wf::render_target_t& target, - const wf::geometry_t& box,const glm::vec4& color=glm::vec4(1)) { - auto& p=*ambient_lock_host::texture_program; - if (!p.get_program_id(wf::TEXTURE_TYPE_RGBA)) p.set_simple(compile_program( - "attribute vec2 position;attribute vec2 texcoord;uniform mat4 mvp;varying vec2 uv;void main(){uv=texcoord;gl_Position=mvp*vec4(position,0.,1.);}", - "precision mediump float;varying vec2 uv;uniform sampler2D image;uniform vec3 tint;uniform float alpha;void main(){vec4 c=texture2D(image,uv);gl_FragColor=c*vec4(tint*alpha,alpha);}" - )); - float x=box.x,y=box.y,w=box.width,h=box.height; - GLfloat xy[]={x,y,x+w,y,x+w,y+h,x,y,x+w,y+h,x,y+h}; - GLfloat uv[]={0,1,1,1,1,0,0,1,1,0,0,0}; - glBindBuffer(GL_ARRAY_BUFFER,0);p.use(wf::TEXTURE_TYPE_RGBA); - p.uniformMatrix4f("mvp",wf::gles::render_target_orthographic_projection(target)); - p.uniform1i("image",0);p.uniform1f("alpha",color.a);p.uniform3f("tint",color.r,color.g,color.b); - glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_2D,texture.tex_id); - p.attrib_pointer("position",2,0,xy);p.attrib_pointer("texcoord",2,0,uv); - glEnable(GL_BLEND);glBlendFunc(GL_ONE,GL_ONE_MINUS_SRC_ALPHA);glDrawArrays(GL_TRIANGLES,0,6);p.deactivate(); -} -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/nebula/LICENSE b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/nebula/LICENSE deleted file mode 100644 index 618286616836..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/nebula/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2018 Iliya Bozhinov -Copyright (c) 2025 Andrew Pliatsikas - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/nebula/UPSTREAM b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/nebula/UPSTREAM deleted file mode 100644 index cc250f83f05c..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/nebula/UPSTREAM +++ /dev/null @@ -1,5 +0,0 @@ -Adapted from okcomput3/wayfire-v-kube -Commit: 7a9a584994d8a045a45ba239b6fefc0529ed7867 -Source: https://github.com/okcomput3/wayfire-v-kube/blob/7a9a584994d8a045a45ba239b6fefc0529ed7867/wf-v-kube/src/shader-background.cpp -License: MIT; retained in LICENSE -Adaptations: seeded and bounded smooth motion, independent emission layers, controls, premultiplied composition, shared Hyperspace detail and native lock integration. diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/nebula/nebula-background.cpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/nebula/nebula-background.cpp deleted file mode 100644 index 4e3d9134fcfe..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/nebula/nebula-background.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "nebula-background.hpp" -#include "nebula-shaders.tpp" -#include "nebula-field.tpp" -#include <algorithm> -#include <cmath> -#include <random> -#include <string> -namespace { -float seeded(uint32_t value) { - value^=value>>16;value*=0x7feb352du;value^=value>>15;value*=0x846ca68bu;value^=value>>16; - return (value&0x00ffffffu)/16777216.0f; -} -} -ambient_nebula_background::ambient_nebula_background(std::function<void()> changed) { - auto update=[this,changed]{dirty=true;changed();}; - speed.set_callback(update);brightness.set_callback(update);clouds.set_callback(update); - stars.set_callback(update);colours.set_callback(update);scale.set_callback(update); - tint.set_callback(update);seed_option.set_callback(update);quality.set_callback(update); - last_frame=std::chrono::steady_clock::now(); -} -ambient_nebula_background::~ambient_nebula_background() { - wf::gles::run_in_context_if_gles([&]{program.free_resources();buffer.free();}); -} -void ambient_nebula_background::set_active(bool enabled) { - if (active!=enabled) last_frame=std::chrono::steady_clock::now(); - active=enabled; -} -bool ambient_nebula_background::restore_scene(const std::array<double,2>& state) { - if (!std::isfinite(state[0]) || !std::isfinite(state[1]) || state[0]<0 || - state[0]>UINT32_MAX || state[1]<0 || state[1]>2*M_PI) return false; - configured_seed=int(seed_option);scene_seed=state[0];phase=state[1];dirty=true; - last_frame=std::chrono::steady_clock::now();return true; -} -void ambient_nebula_background::render(const wf::render_target_t& target, - const wf::regionf_t& damage,wf::dimensions_t resolution,float opacity) { - if (resolution.width<=0 || resolution.height<=0) return; - if (configured_seed!=int(seed_option)) { - configured_seed=int(seed_option);scene_seed=configured_seed ? configured_seed : std::random_device{}(); - phase=0;dirty=true; - } - auto now=std::chrono::steady_clock::now(); - double dt=std::clamp(std::chrono::duration<double>(now-last_frame).count(),0.0,0.1); - last_frame=now; - if (active && double(speed)>0) { - phase=std::fmod(phase+dt*std::clamp(double(speed),0.0,5.0)*0.012,2*M_PI);dirty=true; - } - float render_scale=std::clamp(double(scale),0.25,1.0)*target.scale; - if (buffer.allocate(resolution,render_scale)==wf::buffer_reallocation_result_t::REALLOCATED) dirty=true; - wf::geometry_t box{0,0,double(resolution.width),double(resolution.height)}; - if (dirty) { - if (!program.get_program_id(wf::TEXTURE_TYPE_RGBA)) { - auto fragment=std::string(nebula_fragment_head)+ambient_nebula_field_glsl+nebula_fragment_body; - program.set_simple(OpenGL::compile_program(nebula_vertex,fragment.c_str())); - } - wf::render_target_t offscreen{buffer};offscreen.geometry=box;offscreen.scale=render_scale; - wf::gles::bind_render_buffer(offscreen); - GL_CALL(glDisable(GL_DEPTH_TEST));GL_CALL(glDisable(GL_CULL_FACE));GL_CALL(glDisable(GL_BLEND)); - GL_CALL(glBindBuffer(GL_ARRAY_BUFFER,0)); - program.use(wf::TEXTURE_TYPE_RGBA); - program.uniformMatrix4f("mvp",wf::gles::render_target_orthographic_projection(offscreen)); - program.uniform2f("resolution",resolution.width,resolution.height); - program.uniform1f("phase",phase); - program.uniform1f("cloud_density",std::clamp(double(clouds),0.0,2.0)); - program.uniform1f("star_density",std::clamp(double(stars),0.0,3.0)); - program.uniform1f("brightness",std::clamp(double(brightness),0.0,2.0)); - program.uniform1f("colour_variation",std::clamp(double(colours),0.0,1.0)); - auto colour=wf::color_t(tint);program.uniform3f("tint",colour.r,colour.g,colour.b); - program.uniform3f("scene_offset",seeded(scene_seed)*0.9,seeded(scene_seed+1)*0.9,seeded(scene_seed+2)*0.9); - program.uniform1i("detail",std::clamp(int(quality),0,2)); - const float width=resolution.width,height=resolution.height; - const GLfloat vertices[]={0,0,width,0,width,height,0,0,width,height,0,height}; - program.attrib_pointer("position",2,0,vertices); - wf::gles::for_each_scissor_rect(offscreen,box,[&]{GL_CALL(glDrawArrays(GL_TRIANGLES,0,6));}); - program.deactivate();dirty=false; - } - wf::gles::bind_render_buffer(target); - auto texture=wf::gles_texture_t::from_aux(buffer); - wf::gles::for_each_scissor_rect(target,damage,[&]{ - OpenGL::render_texture(texture,target,box,glm::vec4(1,1,1,std::clamp(opacity,0.0f,1.0f))); - }); - GL_CALL(glDisable(GL_BLEND)); -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/nebula/nebula-background.hpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/nebula/nebula-background.hpp deleted file mode 100644 index 51df5eb78eb7..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/nebula/nebula-background.hpp +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once -#ifdef AMBIENT_LOCK_RENDERER -#include "../hyperspace/hyperspace-standalone.hpp" -#else -#include <wayfire/option-wrapper.hpp> -#include <wayfire/opengl.hpp> -#endif -#include <array> -#include <chrono> -#include <functional> -#include <cstdint> - -class __attribute__((visibility("hidden"))) ambient_nebula_background { - wf::option_wrapper_t<double> speed{"ambient-background/nebula_speed"}; - wf::option_wrapper_t<double> brightness{"ambient-background/nebula_brightness"}; - wf::option_wrapper_t<double> clouds{"ambient-background/nebula_cloud_density"}; - wf::option_wrapper_t<double> stars{"ambient-background/nebula_star_density"}; - wf::option_wrapper_t<double> colours{"ambient-background/nebula_cloud_colours"}; - wf::option_wrapper_t<double> scale{"ambient-background/nebula_resolution_scale"}; - wf::option_wrapper_t<wf::color_t> tint{"ambient-background/nebula_color"}; - wf::option_wrapper_t<int> seed_option{"ambient-background/nebula_seed"}; - wf::option_wrapper_t<int> quality{"ambient-background/nebula_quality"}; - OpenGL::program_t program; - wf::auxilliary_buffer_t buffer; - bool active=false, dirty=true; - int configured_seed=-1; - uint32_t scene_seed=1; - double phase=0; - std::chrono::steady_clock::time_point last_frame; - public: - explicit ambient_nebula_background(std::function<void()> changed); - ~ambient_nebula_background(); - void set_active(bool enabled); - void invalidate() { dirty=true; } - bool has_scene() const { return configured_seed>=0; } - std::array<double,2> scene() const { return {double(scene_seed),phase}; } - bool restore_scene(const std::array<double,2>& state); - void render(const wf::render_target_t& target,const wf::regionf_t& damage, - wf::dimensions_t resolution,float opacity=1); -}; diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/nebula/nebula-field.tpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/nebula/nebula-field.tpp deleted file mode 100644 index f5be4e27675e..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/nebula/nebula-field.tpp +++ /dev/null @@ -1,16 +0,0 @@ -// Fractal fold adapted from wayfire-v-kube. See LICENSE and UPSTREAM. -// Object-space inputs stay fixed: the camera moves through the field. -static const char *ambient_nebula_field_glsl = R"( -float nebula_field(vec3 p, float strength, vec3 offset) { - float accum=0.0, previous=0.0, weight_sum=0.0; - for (int i=0;i<6;i++) { - float magnitude=max(dot(p,p),0.0001); - p=abs(p)/magnitude+offset; - float weight=exp(-float(i)/7.0); - accum+=weight*exp(-strength*pow(abs(magnitude-previous),2.3)); - weight_sum+=weight; - previous=magnitude; - } - return max(0.0,5.0*accum/weight_sum-0.7); -} -)"; diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/nebula/nebula-shaders.tpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/nebula/nebula-shaders.tpp deleted file mode 100644 index d2d84b84ea2e..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/effects/nebula/nebula-shaders.tpp +++ /dev/null @@ -1,61 +0,0 @@ -// Adapted from okcomput3/wayfire-v-kube (MIT), with stable seeded motion. -static const char *nebula_vertex = R"( -#version 100 -attribute vec2 position; -uniform mat4 mvp; -uniform vec2 resolution; -varying vec2 uv; -void main() { uv=position/resolution;gl_Position=mvp*vec4(position,0.0,1.0); } -)"; -static const char *nebula_fragment_head = R"( -#version 100 -precision highp float; -uniform vec2 resolution; -uniform float phase, cloud_density, star_density, brightness, colour_variation; -uniform vec3 scene_offset, tint; -uniform int detail; -varying vec2 uv; -)"; -static const char *nebula_fragment_body = R"( -void main() { - vec2 plane=(uv*2.0-1.0)*resolution/max(resolution.x,resolution.y); - float xy=0.9+phase; - mat2 rot_xy=mat2(cos(xy),sin(xy),-sin(xy),cos(xy)); - mat2 rot_xz=mat2(cos(0.9),sin(0.9),-sin(0.9),cos(0.9)); - mat2 rot_yz=mat2(cos(-0.6),sin(-0.6),-sin(-0.6),cos(-0.6)); - vec3 ray=vec3(plane*3.9,1.0); - vec3 origin=vec3(-2.5+1.1*cos(phase),-2.5+1.1*sin(phase),0.45*sin(phase)); - origin+=scene_offset; - ray.xy*=rot_xy;ray.xz*=rot_xz;ray.yz*=rot_yz; - origin.xy*=-rot_xy;origin.xz*=rot_xz;origin.yz*=rot_yz; - vec3 stars=vec3(0.0), clouds=vec3(0.0); - int steps=6+detail*3; - for (int r=0;r<12;r++) { - if (r<steps) { - float distance=0.24+float(r)*0.19; - vec3 p=origin+distance*ray; - vec3 q=(origin+(distance+0.095)*ray)*(1.9/3.9); - p=abs(vec3(0.45)-mod(p,vec3(0.9))); - q=abs(vec3(0.45)-mod(q,vec3(0.9))); - float previous=0.0, structure=0.0; - for (int i=0;i<4;i++) { - p=abs(p)/max(dot(p,p),0.0001)-0.89; - if (i==2) previous=length(p); - if (i==3) structure=abs(length(p)-previous); - } - structure=min(structure*structure*structure,5000.0); - float fade=pow(0.56,float(r)); - stars+=vec3(distance,distance*distance,pow(distance,4.0))*structure*0.002*fade; - float f=nebula_field(q,7.0,vec3(-0.5,-0.8,-1.1)); - clouds+=vec3(0.20*f*f*f,0.4*f*f,f*0.7)*fade*0.12; - } - } - stars=mix(vec3(length(stars)),stars,0.4)*star_density; - vec3 coloured=mix(tint,vec3(0.45,0.28,0.95),0.35+0.25*sin(phase)); - vec3 cloud_colour=mix(tint,vec3(0.32,0.56,1.0),colour_variation); - cloud_colour=mix(cloud_colour,coloured,colour_variation*0.4); - vec3 emission=(stars+clouds*cloud_density*cloud_colour*2.0)*brightness; - // Emit light over any selected backdrop; no baked-in colour or image. - gl_FragColor=vec4(1.0-exp(-max(emission,vec3(0.0))),0.0); -} -)"; diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/include/ambient-api.hpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/include/ambient-api.hpp deleted file mode 100644 index 17f912921c80..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/include/ambient-api.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once -#include <wayfire/signal-provider.hpp> -#include <wayfire/opengl.hpp> -namespace wf { -struct ambient_query_signal { wf::output_t *output; bool enabled = false; }; -struct ambient_render_signal { - wf::output_t *output; - const wf::render_target_t *target; - const wf::regionf_t *damage; - bool handled = false; -}; -struct ambient_transition_signal { wf::output_t *output; bool moving; }; -struct ambient_changed_signal {}; -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/include/effect-settings.inc b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/include/effect-settings.inc deleted file mode 100644 index df78556402c2..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/include/effect-settings.inc +++ /dev/null @@ -1,71 +0,0 @@ - register_setting<double>("background_black_delay", 600); - register_setting<double>("effect_black_delay", 600); - register_setting<double>("black_fade_duration", 0.667); - register_setting<std::string>("hyperspace_background", std::string("none")); - register_setting<double>("hyperspace_background_opacity", 1); - register_setting<double>("hyperspace_speed", 0.65); - register_setting<double>("hyperspace_cloud_density", 0.85); - register_setting<double>("hyperspace_turbulence", 0.65); - register_setting<double>("hyperspace_ribbon_strength", 0.75); - register_setting<double>("hyperspace_glow", 0.65); - register_setting<double>("hyperspace_brightness", 0.85); - register_setting<double>("hyperspace_surge", 0.7); - register_setting<double>("hyperspace_star_density", 0.65); - register_setting<double>("hyperspace_coloured_stars", 0.12); - register_setting<double>("hyperspace_distant_stars", 1); - register_setting<double>("hyperspace_distant_drift", 0.1); - register_setting<double>("hyperspace_galaxies", 0.7); - register_setting<double>("hyperspace_dust_density", 0.9); - register_setting<double>("hyperspace_cloud_variation", 1.15); - register_setting<double>("hyperspace_cloud_colours", 0.75); - register_setting<double>("hyperspace_pulsars", 0.65); - register_setting<double>("hyperspace_anomalies", 0.65); - register_setting<double>("hyperspace_asteroids", 0.65); - register_setting<int>("hyperspace_seed", 0); - register_setting<wf::color_t>("hyperspace_color", wf::option_type::from_string<wf::color_t>("0.20 0.52 1.0 1.0").value()); - register_setting<int>("hyperspace_quality", 1); - register_setting<int>("hyperspace_fps", 30); - register_setting<std::string>("matrix_background", std::string("none")); - register_setting<double>("matrix_background_opacity", 1); - register_setting<std::string>("matrix_color", std::string("green")); - register_setting<int>("matrix_font_size", 12); - register_setting<int>("matrix_depth_layers", 4); - register_setting<double>("matrix_min_speed", 0.4); - register_setting<double>("matrix_max_speed", 2); - register_setting<double>("matrix_avg_speed", 0.8); - register_setting<int>("matrix_min_trail", 4); - register_setting<int>("matrix_max_trail", 28); - register_setting<double>("matrix_min_density", 0.25); - register_setting<double>("matrix_max_density", 0.45); - register_setting<double>("matrix_layer_near_density", 0.15); - register_setting<double>("matrix_layer_far_density", 0.35); - register_setting<int>("matrix_head_hold_frames", 5); - register_setting<double>("matrix_head_mutation_chance", 0.15); - register_setting<double>("matrix_head_white_min", 0.75); - register_setting<double>("matrix_head_white_max", 1); - register_setting<bool>("matrix_monitor_colours", true); - register_setting<std::string>("lock_background", std::string("inherit")); - register_setting<double>("lock_speed", 1); - register_setting<double>("lock_brightness", .65); - register_setting<bool>("lock_colour_override", false); - register_setting<wf::color_t>("lock_colour", wf::option_type::from_string<wf::color_t>("0.20 0.52 1.0 1.0").value()); - register_setting<bool>("lock_show_clock", true); - register_setting<bool>("lock_show_date", true); - register_setting<std::string>("lock_output", std::string("all")); - register_setting<bool>("lock_ui_on_interaction", true); - register_setting<double>("lock_ui_hide_delay", 15); - register_setting<double>("lock_ui_fade_duration", .25); - register_setting<double>("lock_black_delay", 600); - register_setting<double>("lock_fade_duration", 2); - register_setting<std::string>("nebula_background", std::string("none")); - register_setting<double>("nebula_background_opacity", 1); - register_setting<double>("nebula_speed", 0.65); - register_setting<double>("nebula_brightness", 0.85); - register_setting<double>("nebula_cloud_density", 0.85); - register_setting<double>("nebula_star_density", 1); - register_setting<double>("nebula_cloud_colours", 0.75); - register_setting<double>("nebula_resolution_scale", 1.0); - register_setting<wf::color_t>("nebula_color", wf::option_type::from_string<wf::color_t>("0.30 0.45 1.0 1.0").value()); - register_setting<int>("nebula_seed", 0); - register_setting<int>("nebula_quality", 2); - register_setting<double>("hyperspace_fractal_detail", 0); diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/metadata/ambient-background.xml b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/metadata/ambient-background.xml deleted file mode 100644 index ae90be58e3da..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/metadata/ambient-background.xml +++ /dev/null @@ -1,636 +0,0 @@ -<?xml version='1.0' encoding='utf-8'?> -<wayfire> - <plugin name="ambient-background"> - <_short>Ambient Background</_short> - <_long>Live wallpapers with Hyperspace, Nebula and Digital Rain, independent monitor fading, and optional idle black transitions.</_long> - <category>Desktop</category> - <group> - <_short>Desktop and idle</_short> - <option name="enabled" type="bool"> - <_short>Enable ambient background</_short> - <default>false</default> - </option> - <option name="backend" type="string"> - <_short>Live wallpaper</_short> - <default>hyperspace</default> - <desc> - <value>hyperspace</value> - <_name>Hyperspace</_name> - </desc> - <desc> - <value>matrix</value> - <_name>Digital Rain</_name> - </desc> - <_long>Choose an animated wallpaper. Each effect has its own appearance and background controls.</_long> - <desc> - <value>nebula</value> - <_name>Nebula</_name> - </desc> - </option> - <option name="focused_delay" type="double"> - <_short>Focused monitor idle delay</_short> - <default>240</default> - <min>0</min> - <max>86400</max> - </option> - <option name="inactive_delay" type="double"> - <_short>Inactive monitor idle delay</_short> - <default>120</default> - <min>0</min> - <max>86400</max> - </option> - <option name="fade_duration" type="double"> - <_short>Desktop fade duration</_short> - <default>20</default> - <min>0.1</min> - <max>120</max> - </option> - <option name="restore_duration" type="double"> - <_short>Desktop restore duration</_short> - <default>.25</default> - <min>0.05</min> - <max>10</max> - </option> - <option name="minimum_opacity" type="double"> - <_short>Minimum desktop opacity</_short> - <default>0</default> - <min>0</min> - <max>1</max> - </option> - <option name="manual_duration" type="double"> - <_short>Ambient-only transition duration</_short> - <default>.7</default> - <min>0.1</min> - <max>10</max> - </option> - <option name="fade_to_black" type="bool"> - <_short>Fade to black when idle</_short> - <default>true</default> - <_long>Keep the background and animation black transitions during automatic idle. Manual ambient-only mode keeps the animation visible.</_long> - </option> - <option name="toggle" type="activator"> - <_short>Toggle ambient-only mode</_short> - <default>none</default> - <_long>Hide windows, panels, launchers and docks on all monitors. No default shortcut; choose an unused binding. Repeat the shortcut or press Escape to restore.</_long> - </option> - <option name="restore" type="activator"> - <_short>Restore desktop</_short> - <default>KEY_ESC</default> - </option> - <option name="background_black_delay" type="double"> - <_short>Background black delay</_short> - <_long>Seconds after idle activation before an optional wallpaper fades to black. Zero disables this stage.</_long> - <default>600</default> - <min>0</min> - <max>86400</max> - </option> - <option name="effect_black_delay" type="double"> - <_short>Animation black delay</_short> - <_long>Additional seconds after the background becomes black before the animation fades away. Zero keeps the animation visible.</_long> - <default>600</default> - <min>0</min> - <max>86400</max> - </option> - <option name="black_fade_duration" type="double"> - <_short>Black transition duration</_short> - <default>0.667</default> - <min>0.05</min> - <max>10</max> - </option> - </group> - <group> - <_short>Hyperspace</_short> - <option name="hyperspace_background" type="string"> - <_short>Hyperspace background</_short> - <_long>None shows only the animated effect. Desktop wallpaper adds your existing wallpaper underneath it.</_long> - <default>none</default> - <desc> - <value>none</value> - <_name>None</_name> - </desc> - <desc> - <value>wallpaper</value> - <_name>Desktop wallpaper</_name> - </desc> - </option> - <option name="hyperspace_background_opacity" type="double"> - <_short>Hyperspace wallpaper opacity</_short> - <default>1</default> - <min>0</min> - <max>1</max> - </option> - <option name="hyperspace_speed" type="double"> - <_short>Hyperspace speed</_short> - <_long>Continuous forward travel speed. Zero freezes clouds and both star layers.</_long> - <default>0.65</default> - <min>0</min> - <max>5</max> - <precision>0.05</precision> - </option> - <option name="hyperspace_cloud_density" type="double"> - <_short>Cloud density</_short> - <_long>Strength and absorption of irregular dust banks at different distances. Banks approach and pass the camera with perspective motion. Zero removes clouds.</_long> - <default>0.85</default> - <min>0</min> - <max>2</max> - <precision>0.05</precision> - </option> - <option name="hyperspace_turbulence" type="double"> - <_short>Cloud turbulence</_short> - <_long>Amount of structural distortion in the dust wisps and filaments. Their shapes remain attached to the cloud banks as you travel.</_long> - <default>0.65</default> - <min>0</min> - <max>2</max> - <precision>0.05</precision> - </option> - <option name="hyperspace_ribbon_strength" type="double"> - <_short>Light ribbons</_short> - <_long>Brightness of irregular filaments that follow the cloud field.</_long> - <default>0.75</default> - <min>0</min> - <max>2</max> - <precision>0.05</precision> - </option> - <option name="hyperspace_glow" type="double"> - <_short>Dust and star glow</_short> - <_long>Light scattered by dust filaments and halos around stars.</_long> - <default>0.65</default> - <min>0</min> - <max>2</max> - <precision>0.05</precision> - </option> - <option name="hyperspace_brightness" type="double"> - <_short>Hyperspace brightness</_short> - <_long>Brightness of the complete Hyperspace effect.</_long> - <default>0.85</default> - <min>0</min> - <max>2</max> - <precision>0.05</precision> - </option> - <option name="hyperspace_surge" type="double"> - <_short>Jump surge</_short> - <_long>Smooth acceleration during workspace transitions, easing back to continuous travel afterward. Zero keeps a steady speed.</_long> - <default>0.7</default> - <min>0</min> - <max>2</max> - <precision>0.05</precision> - </option> - <option name="hyperspace_star_density" type="double"> - <_short>Passing stars</_short> - <_long>Density of stars along the journey, from small middle-distance points to fast nearby trails. Stars fill the viewing frustum, including panoramic outputs, and bridge the foreground to the distant starfield.</_long> - <default>0.65</default> - <min>0</min> - <max>3</max> - <precision>0.05</precision> - </option> - <option name="hyperspace_coloured_stars" type="double"> - <_short>Coloured stars</_short> - <_long>Fraction of warm or blue stars in both layers. Most stars are white at the default 12%. Zero makes all stars white; one colours every star.</_long> - <default>0.12</default> - <min>0</min> - <max>1</max> - <precision>0.01</precision> - </option> - <option name="hyperspace_distant_stars" type="double"> - <_short>Distant stars</_short> - <_long>Density of the faint distant stars behind the clouds. They remain points and never inherit the foreground jump surge.</_long> - <default>1</default> - <min>0</min> - <max>3</max> - <precision>0.05</precision> - </option> - <option name="hyperspace_distant_drift" type="double"> - <_short>Distant star drift</_short> - <_long>Very slow drift of the distant starfield and perspective travel of distant galaxies. Zero keeps both stationary while nearby stars continue moving. Galaxies never inherit jump surges.</_long> - <default>0.1</default> - <min>0</min> - <max>1</max> - <precision>0.01</precision> - </option> - <option name="hyperspace_galaxies" type="double"> - <_short>Distant galaxy density</_short> - <_long>Faint spiral, inclined disc and elliptical galaxies near the distant starfield. Seeded shapes, colours and sizes move slowly outwards with depth, behind passing stars and nebulae. Zero disables galaxies without resetting the journey.</_long> - <default>0.7</default> - <min>0</min> - <max>2</max> - <precision>0.05</precision> - </option> - <option name="hyperspace_dust_density" type="double"> - <_short>Space dust</_short> - <_long>Density of faint particles at different depths. Nearby dust makes short streaks; distant dust stays fine. Zero removes dust.</_long> - <default>0.9</default> - <min>0</min> - <max>3</max> - <precision>0.05</precision> - </option> - <option name="hyperspace_cloud_variation" type="double"> - <_short>Cloud variation</_short> - <_long>Amount of broken cloud coverage, dark gaps and filament contrast in the seeded nebula field.</_long> - <default>1.15</default> - <min>0</min> - <max>2</max> - <precision>0.05</precision> - </option> - <option name="hyperspace_cloud_colours" type="double"> - <_short>Cloud colour variation</_short> - <_long>Blend the Hyperspace colour with seeded blue, violet, teal, rose and occasional amber dust banks. Each bank keeps its colour while it travels. Zero uses only the chosen Hyperspace colour; one uses the full varied palette. Star colours remain independent.</_long> - <default>0.75</default> - <min>0</min> - <max>1</max> - <precision>0.05</precision> - </option> - <option name="hyperspace_pulsars" type="double"> - <_short>Pulsar strength</_short> - <_long>Brightness of occasional distant blue-white cores and slowly rotating jets. Events fade smoothly with varied positions, sizes and timing. Zero removes pulsars.</_long> - <default>0.65</default> - <min>0</min> - <max>2</max> - <precision>0.05</precision> - </option> - <option name="hyperspace_anomalies" type="double"> - <_short>Rare anomalies</_short> - <_long>Intensity of occasional distant binary stars and broken glowing remnants. Encounters have varied positions, sizes, shapes and quiet intervals; they fade gradually. Independent of pulsars. Zero disables anomalies.</_long> - <default>0.65</default> - <min>0</min> - <max>2</max> - <precision>0.05</precision> - </option> - <option name="hyperspace_asteroids" type="double"> - <_short>Asteroid field density</_short> - <_long>Sparse encounters with solid, cratered rocks and fractured mineral surfaces. Sizes range from small fragments to rare large asteroids. They approach near the vanishing point and sweep quickly past the camera, with long gaps between fields. Zero disables asteroids.</_long> - <default>0.35</default> - <min>0</min> - <max>2</max> - <precision>0.05</precision> - </option> - <option name="hyperspace_seed" type="int"> - <_short>Journey seed</_short> - <_long>Choose repeatable clouds, particles, encounters and asteroid fields. Zero chooses a new random journey when this effect loads. Objects vary on each pass without changing abruptly in view.</_long> - <default>0</default> - <min>0</min> - <max>65535</max> - </option> - <option name="hyperspace_color" type="color"> - <_short>Hyperspace colour</_short> - <_long>Tint of the clouds and light ribbons. White stars retain their neutral colour.</_long> - <default>0.20 0.52 1.0 1.0</default> - </option> - <option name="hyperspace_quality" type="int"> - <_short>Hyperspace quality</_short> - <_long>Cloud resolution and volume detail, plus star and dust counts. Particles always render at full output resolution.</_long> - <default>1</default> - <min>0</min> - <max>2</max> - <desc> - <value>0</value> - <_name>Low</_name> - </desc> - <desc> - <value>1</value> - <_name>Balanced</_name> - </desc> - <desc> - <value>2</value> - <_name>Detailed</_name> - </desc> - </option> - <option name="hyperspace_fps" type="int"> - <_short>Hyperspace frame rate</_short> - <_long>Frame rate while the desktop is idle. Workspace transitions keep their normal refresh rate.</_long> - <default>30</default> - <min>10</min> - <max>120</max> - </option> - <option name="hyperspace_fractal_detail" type="double"> - <_short>Fractal cloud detail</_short> - <_long>Blend Nebula fractal filaments into the existing moving cloud banks. Structure stays attached to each bank. Zero retains the original clouds; this does not alter stars, galaxies or encounters.</_long> - <default>0</default> - <min>0</min> - <max>1</max> - <precision>0.05</precision> - </option> - </group> - <group> - <_short>Nebula</_short> - <option name="nebula_background" type="string"> - <_short>Nebula background</_short> - <_long>Optional static wallpaper behind the emitted nebula and stars. None removes the image.</_long> - <default>none</default> - <desc> - <value>none</value> - <_name>None</_name> - </desc> - <desc> - <value>wallpaper</value> - <_name>Desktop wallpaper</_name> - </desc> - </option> - <option name="nebula_background_opacity" type="double"> - <_short>Nebula background opacity</_short> - <default>1</default> - <min>0</min> - <max>1</max> - <precision>0.05</precision> - </option> - <option name="nebula_speed" type="double"> - <_short>Nebula speed</_short> - <_long>Continuous drift and rotation through the fractal field. Zero freezes it; Cube movement does not accelerate it.</_long> - <default>0.65</default> - <min>0</min> - <max>5</max> - <precision>0.05</precision> - </option> - <option name="nebula_brightness" type="double"> - <_short>Nebula brightness</_short> - <default>0.85</default> - <min>0</min> - <max>2</max> - <precision>0.05</precision> - </option> - <option name="nebula_cloud_density" type="double"> - <_short>Nebula clouds</_short> - <default>0.85</default> - <min>0</min> - <max>2</max> - <precision>0.05</precision> - </option> - <option name="nebula_star_density" type="double"> - <_short>Nebula stars</_short> - <default>1</default> - <min>0</min> - <max>3</max> - <precision>0.05</precision> - </option> - <option name="nebula_cloud_colours" type="double"> - <_short>Nebula colour variation</_short> - <default>0.75</default> - <min>0</min> - <max>1</max> - <precision>0.05</precision> - </option> - <option name="nebula_color" type="color"> - <_short>Nebula base colour</_short> - <default>0.30 0.45 1.0 1.0</default> - </option> - <option name="nebula_resolution_scale" type="double"> - <_short>Nebula render resolution</_short> - <_long>Fraction of output resolution for the procedural field, smoothly upscaled. Lower values reduce GPU work; other desktop elements retain full resolution.</_long> - <default>1</default> - <min>0.25</min> - <max>1</max> - <precision>0.05</precision> - </option> - <option name="nebula_seed" type="int"> - <_short>Nebula scene seed</_short> - <_long>Fixed seeds reproduce a scene. Zero generates a new scene when the renderer is created.</_long> - <default>0</default> - <min>0</min> - <max>2147483647</max> - </option> - <option name="nebula_quality" type="int"> - <_short>Nebula detail</_short> - <default>2</default> - <min>0</min> - <max>2</max> - <desc> - <value>0</value> - <_name>Low</_name> - </desc> - <desc> - <value>1</value> - <_name>Balanced</_name> - </desc> - <desc> - <value>2</value> - <_name>High</_name> - </desc> - </option> - </group> - <group> - <_short>Digital Rain</_short> - <option name="matrix_background" type="string"> - <_short>Digital Rain background</_short> - <_long>None shows only the animated effect. Desktop wallpaper adds your existing wallpaper underneath it.</_long> - <default>none</default> - <desc> - <value>none</value> - <_name>None</_name> - </desc> - <desc> - <value>wallpaper</value> - <_name>Desktop wallpaper</_name> - </desc> - </option> - <option name="matrix_background_opacity" type="double"> - <_short>Digital Rain wallpaper opacity</_short> - <default>1</default> - <min>0</min> - <max>1</max> - </option> - <option name="matrix_color" type="string"> - <_short>Rain colour</_short> - <_long>A named colour, #RRGGBB, random_trail_named, random_trail_hex, or an adaptive colour mode.</_long> - <default>green</default> - </option> - <option name="matrix_font_size" type="int"> - <_short>Glyph size</_short> - <default>12</default> - <min>6</min> - <max>72</max> - </option> - <option name="matrix_depth_layers" type="int"> - <_short>Depth layers</_short> - <default>4</default> - <min>1</min> - <max>8</max> - </option> - <option name="matrix_min_speed" type="double"> - <_short>Minimum fall speed</_short> - <default>0.4</default> - <min>0.001</min> - <max>100</max> - </option> - <option name="matrix_max_speed" type="double"> - <_short>Maximum fall speed</_short> - <default>2</default> - <min>0.001</min> - <max>100</max> - </option> - <option name="matrix_avg_speed" type="double"> - <_short>Fall speed bias</_short> - <default>0.8</default> - <min>0.001</min> - <max>100</max> - </option> - <option name="matrix_min_trail" type="int"> - <_short>Minimum trail lifetime</_short> - <default>4</default> - <min>1</min> - <max>10000</max> - </option> - <option name="matrix_max_trail" type="int"> - <_short>Maximum trail lifetime</_short> - <default>28</default> - <min>1</min> - <max>10000</max> - </option> - <option name="matrix_min_density" type="double"> - <_short>Minimum single-layer density</_short> - <default>0.25</default> - <min>0</min> - <max>1</max> - </option> - <option name="matrix_max_density" type="double"> - <_short>Maximum single-layer density</_short> - <default>0.45</default> - <min>0</min> - <max>1</max> - </option> - <option name="matrix_layer_near_density" type="double"> - <_short>Near-layer density</_short> - <default>0.15</default> - <min>0</min> - <max>2</max> - </option> - <option name="matrix_layer_far_density" type="double"> - <_short>Far-layer density</_short> - <default>0.35</default> - <min>0</min> - <max>2</max> - </option> - <option name="matrix_head_hold_frames" type="int"> - <_short>Head glyph hold frames</_short> - <default>5</default> - <min>0</min> - <max>120</max> - </option> - <option name="matrix_head_mutation_chance" type="double"> - <_short>Head mutation chance</_short> - <default>0.15</default> - <min>0</min> - <max>1</max> - </option> - <option name="matrix_head_white_min" type="double"> - <_short>Minimum head whiteness</_short> - <default>0.75</default> - <min>0</min> - <max>1</max> - </option> - <option name="matrix_head_white_max" type="double"> - <_short>Maximum head whiteness</_short> - <default>1</default> - <min>0</min> - <max>1</max> - </option> - <option name="matrix_monitor_colours" type="bool"> - <_short>Use monitor colour overrides</_short> - <_long>Use the per-monitor colours configured in ambient-background.toml.</_long> - <default>true</default> - </option> - </group> - <group> - <_short>Lock screen</_short> - <option name="lock_background" type="string"> - <_short>Lock-screen background</_short> - <_long>Choose the Ambient effect, Hyperspace, Nebula, Digital Rain, or a plain black lock screen.</_long> - <default>inherit</default> - <desc> - <value>inherit</value> - <_name>Same as Ambient</_name> - </desc> - <desc> - <value>hyperspace</value> - <_name>Hyperspace</_name> - </desc> - <desc> - <value>digitalrain</value> - <_name>Digital Rain</_name> - </desc> - <desc> - <value>black</value> - <_name>Black</_name> - </desc> - <desc> - <value>nebula</value> - <_name>Nebula</_name> - </desc> - </option> - <option name="lock_speed" type="double"> - <_short>Lock animation speed</_short> - <_long>Multiplier for the selected effect while locked. Zero freezes the animation.</_long> - <default>1</default> - <min>0</min> - <max>3</max> - </option> - <option name="lock_brightness" type="double"> - <_short>Lock animation brightness</_short> - <_long>Brightness multiplier for the locked background.</_long> - <default>.65</default> - <min>0</min> - <max>2</max> - </option> - <option name="lock_colour_override" type="bool"> - <_short>Override lock colours</_short> - <_long>Use the lock colour instead of the selected effect’s base colour.</_long> - <default>false</default> - </option> - <option name="lock_colour" type="color"> - <_short>Lock colour</_short> - <_long>Base colour for the lock background when the override is enabled.</_long> - <default>0.20 0.52 1.0 1.0</default> - </option> - <option name="lock_output" type="string"> - <_short>Lock controls monitor</_short> - <_long>Show the clock and unlock controls on one physical monitor or all monitors. If the selected monitor is disconnected, controls appear on an available monitor. The background and session protection cover every monitor.</_long> - <default>all</default> - <desc> - <value>all</value> - <_name>All monitors</_name> - </desc> - </option> - <option name="lock_ui_on_interaction" type="bool"> - <_short>Reveal lock controls on interaction</_short> - <_long>Keep the clock, date and unlock controls hidden until keyboard or mouse interaction.</_long> - <default>true</default> - </option> - <option name="lock_ui_hide_delay" type="double"> - <_short>Lock controls hide delay</_short> - <_long>Seconds without input before hiding the unlock controls and clearing the password. Zero keeps them visible after interaction. Authentication in progress remains visible.</_long> - <default>15</default> - <min>0</min> - <max>600</max> - </option> - <option name="lock_ui_fade_duration" type="double"> - <_short>Lock controls fade duration</_short> - <_long>Seconds for the clock and unlock controls to appear or disappear.</_long> - <default>0.25</default> - <min>0.05</min> - <max>3</max> - </option> - <option name="lock_show_clock" type="bool"> - <_short>Show lock clock</_short> - <_long>Show the time on the selected lock-controls monitor or monitors.</_long> - <default>true</default> - </option> - <option name="lock_show_date" type="bool"> - <_short>Show lock date</_short> - <_long>Show the date below the lock clock.</_long> - <default>true</default> - </option> - <option name="lock_black_delay" type="double"> - <_short>Lock fade-to-black delay</_short> - <_long>Seconds without lock-screen input before the animation fades to black. Zero keeps it visible.</_long> - <default>600</default> - <min>0</min> - <max>86400</max> - </option> - <option name="lock_fade_duration" type="double"> - <_short>Lock fade duration</_short> - <_long>Seconds for the lock background to fade to black and return on input.</_long> - <default>2</default> - <min>0.1</min> - <max>30</max> - </option> - </group> - </plugin> -</wayfire>
\ No newline at end of file diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/ambient-4k.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/ambient-4k.py deleted file mode 100755 index 0cbc6d9dfb1f..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/ambient-4k.py +++ /dev/null @@ -1,40 +0,0 @@ -from pathlib import Path -import sys,time -from PIL import Image,ImageChops -stage=Path(sys.argv[1]);sys.path.insert(0,str(stage/'tests')) -from private_session import PrivateSession -extra=''' -[core] -vwidth=4 -vheight=3 -[cube] -render_at_rest=true -mouse_overview=true -hyperspace_speed=1.5 -[ambient-background] -enabled=true -hyperspace_quality=2 -hyperspace_speed=1.5 -controller= -manual_duration=.1 -''' -for i in range(3):extra+=f'\n[output:HEADLESS-{i+1}]\nmode=3840x2160@60\nposition={i*3840},0\n' -extra+='\n[output:HEADLESS-4]\nposition=11520,0\n' -with PrivateSession(stage,hotplug=True,extra_plugins=' ambient-background cube render-test',config_extra=extra) as s: - print('SESSION',s.root,flush=True) - def call(m,d=None): - r=s.call(m,d);assert not isinstance(r,dict) or not r.get('error'),r - return r - def snap(name): - p=s.root/(name+'.png');s.run(['grim','-o','VIRTUAL-1',str(p)],timeout=25);return Image.open(p).convert('RGB') - s.wait(lambda:len(call('ambient/status')['outputs'])==4) - call('stipc/move_cursor',{'x':11530,'y':10});time.sleep(.4) - a=snap('4k-first');assert a.size==(11520,2160) - time.sleep(.3);assert ImageChops.difference(a,snap('4k-next')).getbbox() - call('test/stats');time.sleep(1);frames=call('test/stats')['passes'];assert 10<=frames<=90,frames - call('test/cube',{'output':'VIRTUAL-1','angle':.65,'zoom':.55});time.sleep(.3) - assert call('test/partial',{'output':'VIRTUAL-1'})['outside']==0 - call('test/cube',{'output':'VIRTUAL-1','stop':True});time.sleep(.3) - logs=(s.root/'wayfire.log').read_text(errors='replace') - assert 'GL_INVALID' not in logs and 'Shader compilation failed' not in logs - print('PASS 3x4K continuous Hyperspace, Cube delegation and partial damage; frame passes',frames,flush=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/ambient-autostart.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/ambient-autostart.py deleted file mode 100755 index 84ac12a00684..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/ambient-autostart.py +++ /dev/null @@ -1,48 +0,0 @@ -from pathlib import Path -import json,os,signal,socket,struct,sys,time -stage=Path(sys.argv[1]);sys.path.insert(0,str(stage/'tests')) -from private_session import PrivateSession -extra=f''' -[ambient-background] -enabled=true -backend=matrix -controller={stage}/ambient-background --daemon --renderer {stage}/digitalrain -focused_delay=86400 -inactive_delay=86400 -''' -s=PrivateSession(stage,hotplug=True,extra_plugins=' ambient-background digitalrain-cursor digitalrain-idle digitalrain-sampler',config_extra=extra) -config=s.root/'ambient-background.toml';config.write_text('''schema_version=1 -[global] -TIMEOUT=86400 -FOCUSED_TIMEOUT=86400 -LOCK_TIMEOUT=0 -LOG_FILE="stderr" -''') -s.env['AMBIENT_BACKGROUND_CONFIG']=str(config) -manager_pid=None;helper_pid=None -try: - with s: - print('SESSION',s.root,flush=True) - control=s.root/'digitalrain/control.sock';s.wait(control.exists) - with socket.socket(socket.AF_UNIX,socket.SOCK_SEQPACKET) as conn: - conn.connect(str(control));manager_pid,uid,_=struct.unpack('3i',conn.getsockopt(socket.SOL_SOCKET,socket.SO_PEERCRED,12));assert uid==os.geteuid() - conn.send(b'{"version":1,"method":"status"}');status=json.loads(conn.recv(1024*1024));assert status['config_file']==str(config) - s.wait(lambda:sum(v.get('app-id')=='ambient-matrix' for v in s.call('window-rules/list-views'))==4) - print('PASS enabled plugin starts its controller, idle manager and all Matrix renderers without separate autostart',flush=True) - for entry in Path('/proc').iterdir(): - if not entry.name.isdigit():continue - try: - command=entry.joinpath('cmdline').read_bytes() - env=entry.joinpath('environ').read_bytes() - if b'ambient-background\0--daemon' in command and ('XDG_RUNTIME_DIR='+str(s.root)).encode()+b'\0' in env: - helper_pid=int(entry.name) - except OSError:pass -finally: - for pid in [helper_pid,manager_pid]: - if pid is None:continue - try: - proc=Path('/proc')/str(pid) - assert proc.stat().st_uid==os.geteuid() - assert ('XDG_RUNTIME_DIR='+str(s.root)).encode()+b'\0' in proc.joinpath('environ').read_bytes() - os.kill(pid,signal.SIGTERM) - except FileNotFoundError:pass diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/ambient-controller.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/ambient-controller.py deleted file mode 100755 index ead91775ee85..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/ambient-controller.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env python3 -from pathlib import Path -import json,os,signal,socket,sys,time -from PIL import Image,ImageChops -stage=Path(sys.argv[1]).resolve();sys.path.insert(0,str(stage/'tests')) -from private_session import PrivateSession -extra=f''' -[core] -vwidth=4 -vheight=3 -[cube] -render_at_rest=true -hyperspace_speed=1.5 -hyperspace_seed=31415 -[ambient-background] -enabled=true -controller= -focused_delay=2 -inactive_delay=1 -fade_duration=0.3 -restore_duration=0.05 -manual_duration=0.1 -backend=matrix -background_black_delay=2 -effect_black_delay=2 -black_fade_duration=.2 -''' -with PrivateSession(stage,hotplug=True,extra_plugins=' ambient-background digitalrain-cursor digitalrain-idle digitalrain-sampler render-test',config_extra=extra) as s: - print('SESSION',s.root,flush=True) - def call(m,d=None): - r=s.call(m,d);assert not isinstance(r,dict) or not r.get('error'),r - return r - config=s.root/'dr.toml';config.write_text('''schema_version=1 -[global] -TIMEOUT=86400 -FOCUSED_TIMEOUT=86400 -CHECK_INTERVAL=1 -LOCK_TIMEOUT=0 -LOG_FILE="stderr" -[matrix] -COLOR="green" -FONT_SIZE=14 -DEPTH_LAYERS=4 -TRANSPARENT=true -TRANSPARENT_TIMEOUT=2 -BLACK_RAIN_TIMEOUT=2 -FADE_FRAMES=6 -POWER_OFF=false -HIDE_DESKTOP=false -''') - s.env['DIGITALRAIN_CONFIG']=str(config) - dr=s.spawn('digitalrain',[str(stage/'digitalrain')]) - control=s.root/'digitalrain/control.sock';s.wait(control.exists) - def request(m,**data): - stream=socket.socket(socket.AF_UNIX,socket.SOCK_SEQPACKET);stream.settimeout(3);stream.connect(str(control)) - stream.send(json.dumps({'version':1,'method':m,**data}).encode());r=json.loads(stream.recv(1024*1024));assert not r.get('error'),r - return stream,r - def status(): - conn,r=request('status');conn.close();return r - s.wait(lambda:len(status()['outputs'])==4) - owner,r=request('ambient',enabled=True) - assert r['ambient_active'] - time.sleep(.4);older=status();assert max(o['idle_seconds'] for o in older['outputs'])>.2,'Ambient ownership reset activity' - other=socket.socket(socket.AF_UNIX,socket.SOCK_SEQPACKET);other.connect(str(control));other.send(b'{"version":1,"method":"ambient","enabled":true}') - assert json.loads(other.recv(1024*1024)).get('error');other.close() - owner.close();s.wait(lambda:not status()['ambient_active']) - print('PASS bounded ownership, disconnect release and preserved per-monitor idle clocks',flush=True) - s.spawn('wallpaper',['python3',str(stage/'tests/ambient-layer.py'),'wallpaper']) - s.spawn('panel',['python3',str(stage/'tests/ambient-layer.py')]) - s.wait(lambda:any(v.get('app-id')=='ambient-fixture-panel' for v in call('window-rules/list-views'))) - conn,_=request('wake');conn.close() - controller=s.spawn('ambient-controller',[str(stage/'ambient-background'),'--daemon','--digitalrain',str(stage/'digitalrain')]) - s.wait(lambda:status()['ambient_active']) - s.wait(lambda:sum(v.get('app-id')=='ambient-matrix' for v in call('window-rules/list-views'))==4) - def snap(name): - p=s.root/(name+'.png');s.run(['grim','-o','VIRTUAL-1',str(p)]);return Image.open(p).convert('RGB') - time.sleep(.5);a=snap('rain');time.sleep(.3);b=snap('rain-next') - assert ImageChops.difference(a,b).getbbox(),'Matrix background not animated' - assert a.getpixel((10,5))[1]>150,'Panel hidden before idle' - s.wait(lambda:any(z['opacity']<.05 for z in call('ambient/status')['outputs']),timeout=6) - time.sleep(1);a=snap('idle-rain') - assert any(a.getpixel((x,100))[1]>20 for x in range(0,1920,10)), 'Glyphs absent under hidden desktop' - s.wait(lambda:all(o['idle_seconds']-(2 if o['focused'] else 1)>4.6 for o in status()['outputs']),timeout=10) - time.sleep(.3);black=snap('black') - assert max(max(black.getpixel((x,100))) for x in range(0,1920,40))<=1,'DR final black phase lost' - print('PASS always-running Matrix, real panel fade, and inherited DR black phases',flush=True) - assert call('ambient/toggle')['manual'];time.sleep(.35);a=snap('manual-rain') - assert ImageChops.difference(a,black).getbbox(),'Manual mode kept idle black phase' - call('stipc/move_cursor',{'x':900,'y':450});time.sleep(.4);assert call('ambient/status')['manual'] - call('ambient/restore');time.sleep(.4) - assert all(o['idle_seconds']<1.5 for o in status()['outputs']),'Exit did not reset idle timers' - assert all(z['opacity']>.95 for z in call('ambient/status')['outputs']) - conn,r=request('inhibit',output='HEADLESS-1',seconds=8,reason='private fixture');conn.close();time.sleep(2.5) - alphas={z['name']:z['opacity'] for z in call('ambient/status')['outputs']} - assert alphas['HEADLESS-1']>.95 and any(alphas[n]<.05 for n in ('HEADLESS-2','HEADLESS-3')),alphas - print('PASS manual persistence, idle reset and monitor-specific presentation inhibition',flush=True) - call('ambient/toggle');time.sleep(.2) - os.kill(controller.pid,signal.SIGKILL);controller.wait();s.processes.remove(controller) - s.wait(lambda:not status()['ambient_active']);time.sleep(3.4) - assert not call('ambient/status')['manual'] - assert all(z['opacity']>.95 for z in call('ambient/status')['outputs']) - assert not any(v.get('app-id')=='ambient-matrix' for v in call('window-rules/list-views')) - print('PASS controller crash releases ownership, kills only its renderers and restores desktop',flush=True) - print('ALL AMBIENT CONTROLLER CHECKS PASSED',flush=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/ambient-wcm.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/ambient-wcm.py deleted file mode 100755 index 88fa8a06b99f..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/ambient-wcm.py +++ /dev/null @@ -1,276 +0,0 @@ -#!/usr/bin/env python3 -"""Discover and edit Ambient Background through WCM in an isolated GPU session.""" -import configparser -import json -import os -from pathlib import Path -import socket -import struct -import subprocess -import tempfile -import time -import gi -gi.require_version('Atspi', '2.0') -from gi.repository import Atspi - -assert os.geteuid()!=0 -stage=Path(os.environ['AMBIENT_STAGE']) -root=Path(tempfile.mkdtemp(prefix='wcm-ambient-mainpage-')) -env=os.environ.copy() -for key in ('WAYLAND_DISPLAY','DISPLAY','WAYFIRE_SOCKET','_WAYFIRE_SOCKET','WAYFIRE_CONFIG_FILE', - 'WAYFIRE_DEFAULT_CONFIG_BACKEND','NO_AT_BRIDGE'): - env.pop(key,None) -env.update(XDG_RUNTIME_DIR=str(root),XDG_CONFIG_HOME=str(root/'config'),XDG_DATA_HOME=str(root/'data'), - XDG_CACHE_HOME=str(root/'cache'),XDG_STATE_HOME=str(root/'state'),WLR_BACKENDS='headless', - WLR_HEADLESS_OUTPUTS='3',WLR_RENDERER='gles2',WLR_RENDER_DRM_DEVICE='/dev/dri/renderD128', - WAYFIRE_PLUGIN_PATH=str(stage/'plugins'),WAYFIRE_PLUGIN_XML_PATH=str(stage/'metadata'), - GTK_USE_PORTAL='0',GIO_USE_VFS='local',GTK_MODULES='atk-bridge', - XDG_DATA_DIRS='/usr/local/share:/usr/share') -config=root/'wayfire.ini' -config.write_text(f'''# Keep unrelated preferences. -[core] -plugins = ipc stipc ipc-rules {stage}/plugins/libcube.so {stage}/plugins/libambient-background.so place grid -xwayland = false -vwidth = 4 -vheight = 3 -stitch_enabled = true -stitch_outputs = HEADLESS-1 HEADLESS-2 HEADLESS-3 -[ambient-background] -enabled=false -controller= -[cube] -activate = <ctrl> <alt> BTN_LEFT -dim = 0.25 -[grid] -toggle_scope = <super> KEY_ENTER -[output:HEADLESS-1] -mode = 1400x1000@60 -position = 0,0 -[output:HEADLESS-2] -mode = 1400x1000@60 -position = 1400,0 -[output:HEADLESS-3] -mode = 1400x1000@60 -position = 2800,0 -''') -original=config.read_text() -shell=root/'shell.ini';shell.write_text('') -compositor=subprocess.Popen([str(stage/'wayfire'),'-c',str(config)],env=env, - stdout=(root/'wayfire.log').open('w'),stderr=subprocess.STDOUT) -ui=None - -def wait(fn,timeout=15): - until=time.monotonic()+timeout - while time.monotonic()<until: - assert compositor.poll() is None,f'Compositor exited; {root}' - if ui is not None:assert ui.poll() is None,f'WCM exited; {root}' - value=fn() - if value:return value - time.sleep(.05) - raise TimeoutError(str(root)) - -def ipc(method,data=None): - with socket.socket(socket.AF_UNIX) as stream: - stream.settimeout(5);stream.connect(env['WAYFIRE_SOCKET']) - assert struct.unpack('3i',stream.getsockopt(socket.SOL_SOCKET,socket.SO_PEERCRED,12))[:2]==(compositor.pid,os.geteuid()) - payload=json.dumps({'method':method,'data':data or {}}).encode() - stream.sendall(struct.pack('<I',len(payload))+payload) - def read(n): - value=b'' - while len(value)<n: - block=stream.recv(n-len(value));assert block;value+=block - return value - result=json.loads(read(struct.unpack('<I',read(4))[0])) - assert not isinstance(result,dict) or not result.get('error'),result - return result - -def nodes(): - stack=[Atspi.get_desktop(0)] - while stack: - node=stack.pop() - if node is None:continue - yield node - stack.extend(node.get_child_at_index(i) for i in range(node.get_child_count())) - -def find(name,role): - return next((n for n in nodes() if n.get_name()==name and n.get_role()==role),None) - -def showing(node): - return node is not None and node.get_state_set().contains(Atspi.StateType.SHOWING) - -def overview_check(name='Show all workspace rows'): - # Generic WCM options place a separate label beside an unnamed checkbox. - label=find(name,Atspi.Role.LABEL) - if label is None:return None - container=label.get_parent() - for _ in range(4): - stack=[container];checks=[] - while stack: - node=stack.pop() - if node.get_role()==Atspi.Role.CHECK_BOX:checks.append(node) - stack.extend(node.get_child_at_index(i) for i in range(node.get_child_count())) - if len(checks)==1:return checks[0] - container=container.get_parent() - return None - -def value(): - parsed=configparser.ConfigParser(interpolation=None,strict=False) - parsed.read(config) - return parsed - -try: - env['WAYFIRE_SOCKET']=str(wait(lambda:next(root.glob('wayfire-*.socket'),None))) - env['WAYLAND_DISPLAY']=ipc('stipc/get_display')['wayland'] - ui=subprocess.Popen([str(stage/'wcm'),'--config',str(config),'--shell-config',str(shell)], - env=env,stdout=(root/'wcm.log').open('w'),stderr=subprocess.STDOUT) - page=wait(lambda:find('Ambient Background',Atspi.Role.PUSH_BUTTON)) - page.get_component_iface().scroll_to(Atspi.ScrollType.ANYWHERE) - wait(lambda:showing(page)) - assert page.get_action_iface().do_action(0) - wait(lambda:find('Live wallpaper',Atspi.Role.LABEL)) - assert config.read_text()==original,'Opening Ambient settings changed preferences' - def control(label_name,role): - label=find(label_name,Atspi.Role.LABEL) - if label is None:return None - parent=label.get_parent() - for _ in range(4): - stack=[parent];found=[] - while stack: - node=stack.pop() - if node.get_role()==role:found.append(node) - stack.extend(node.get_child_at_index(i) for i in range(node.get_child_count())) - if len(found)==1:return found[0] - parent=parent.get_parent() - return None - def tab(name): - item=wait(lambda:find(name,Atspi.Role.PAGE_TAB)) - assert item.get_parent().get_selection_iface().select_child(item.get_index_in_parent()) - def choose(label,item): - combo=wait(lambda:control(label,Atspi.Role.COMBO_BOX)) - wait(lambda:showing(combo)) - assert combo.get_action_iface().do_action(0) - choice=wait(lambda:next((n for n in nodes() if n.get_name()==item and n.get_role()==Atspi.Role.MENU_ITEM and showing(n)),None)) - assert choice.get_action_iface().do_action(0) - - choose('Live wallpaper','Nebula') - wait(lambda:ipc('ambient/status')['backend']=='nebula') - assert value()['ambient-background']['backend']=='nebula' - tab('Nebula') - choose('Nebula background','Desktop wallpaper') - wait(lambda:ipc('wayfire/get-config-option',{'option':'ambient-background/nebula_background'})['value']=='wallpaper') - choose('Nebula background','None') - nebula_speed=wait(lambda:control('Nebula speed',Atspi.Role.SPIN_BUTTON)) - assert nebula_speed.get_value_iface().set_current_value(1.2) - wait(lambda:float(ipc('ambient/status')['nebula']['nebula_speed'])==1.2) - assert float(value()['ambient-background']['nebula_speed'])==1.2 - tab('Desktop and idle') - choose('Live wallpaper','Digital Rain') - wait(lambda:ipc('wayfire/get-config-option',{'option':'ambient-background/backend'})['value']=='matrix') - assert value()['ambient-background']['backend']=='matrix' - choose('Live wallpaper','Hyperspace') - wait(lambda:ipc('wayfire/get-config-option',{'option':'ambient-background/backend'})['value']=='hyperspace') - delay=wait(lambda:control('Focused monitor idle delay',Atspi.Role.SPIN_BUTTON)) - assert delay.get_value_iface().set_current_value(300) - wait(lambda:float(ipc('wayfire/get-config-option',{'option':'ambient-background/focused_delay'})['value'])==300) - assert float(value()['ambient-background']['focused_delay'])==300 - for label in ['Enable ambient background','Inactive monitor idle delay','Desktop fade duration', - 'Desktop restore duration','Minimum desktop opacity','Ambient-only transition duration', - 'Fade to black when idle','Toggle ambient-only mode','Restore desktop']: - assert find(label,Atspi.Role.LABEL),label - assert ipc('wayfire/get-config-option',{'option':'ambient-background/toggle'})['value']=='none' - assert value()['grid']['toggle_scope']=='<super> KEY_ENTER' - assert 'libambient-background.so' in value()['core']['plugins'] - tab('Hyperspace') - fractal=wait(lambda:control('Fractal cloud detail',Atspi.Role.SPIN_BUTTON)) - assert fractal.get_value_iface().set_current_value(.35) - wait(lambda:float(ipc('ambient/status')['hyperspace']['hyperspace_fractal_detail'])==.35) - assert float(value()['ambient-background']['hyperspace_fractal_detail'])==.35 - choose('Hyperspace background','Desktop wallpaper') - wait(lambda:ipc('wayfire/get-config-option',{'option':'ambient-background/hyperspace_background'})['value']=='wallpaper') - choose('Hyperspace background','None') - hyper_speed=wait(lambda:control('Hyperspace speed',Atspi.Role.SPIN_BUTTON)) - cube_speed=ipc('wayfire/get-config-option',{'option':'cube/hyperspace_speed'})['value'] - assert hyper_speed.get_value_iface().set_current_value(1.25) - wait(lambda:float(ipc('wayfire/get-config-option',{'option':'ambient-background/hyperspace_speed'})['value'])==1.25) - assert ipc('wayfire/get-config-option',{'option':'cube/hyperspace_speed'})['value']==cube_speed - rare=wait(lambda:control('Rare anomalies',Atspi.Role.SPIN_BUTTON)) - pulsars=ipc('wayfire/get-config-option',{'option':'ambient-background/hyperspace_pulsars'})['value'] - assert rare.get_value_iface().set_current_value(.45) - wait(lambda:float(ipc('wayfire/get-config-option',{'option':'ambient-background/hyperspace_anomalies'})['value'])==.45) - assert ipc('wayfire/get-config-option',{'option':'ambient-background/hyperspace_pulsars'})['value']==pulsars - assert float(value()['ambient-background']['hyperspace_anomalies'])==.45 - print('PASS WCM Rare anomalies saves and updates independently of pulsars',flush=True) - asteroids=wait(lambda:control('Asteroid field density',Atspi.Role.SPIN_BUTTON)) - old_stars=ipc('wayfire/get-config-option',{'option':'ambient-background/hyperspace_star_density'})['value'] - assert asteroids.get_value_iface().set_current_value(.8) - wait(lambda:float(ipc('wayfire/get-config-option',{'option':'ambient-background/hyperspace_asteroids'})['value'])==.8) - assert float(value()['ambient-background']['hyperspace_asteroids'])==.8 - assert ipc('wayfire/get-config-option',{'option':'ambient-background/hyperspace_star_density'})['value']==old_stars - assert ipc('wayfire/get-config-option',{'option':'ambient-background/hyperspace_pulsars'})['value']==pulsars - print('PASS WCM Asteroid field density saves independently of stars and pulsars',flush=True) - colours=wait(lambda:control('Cloud colour variation',Atspi.Role.SPIN_BUTTON)) - stars=ipc('wayfire/get-config-option',{'option':'ambient-background/hyperspace_coloured_stars'})['value'] - assert colours.get_value_iface().set_current_value(.85) - wait(lambda:float(ipc('wayfire/get-config-option',{'option':'ambient-background/hyperspace_cloud_colours'})['value'])==.85) - assert ipc('wayfire/get-config-option',{'option':'ambient-background/hyperspace_coloured_stars'})['value']==stars - assert float(value()['ambient-background']['hyperspace_cloud_colours'])==.85 - print('PASS WCM Cloud colour variation saves independently of star colours',flush=True) - galaxy=wait(lambda:control('Distant galaxy density',Atspi.Role.SPIN_BUTTON)) - old_drift=ipc('wayfire/get-config-option',{'option':'ambient-background/hyperspace_distant_drift'})['value'] - assert galaxy.get_value_iface().set_current_value(1.1) - wait(lambda:float(ipc('wayfire/get-config-option',{'option':'ambient-background/hyperspace_galaxies'})['value'])==1.1) - assert float(value()['ambient-background']['hyperspace_galaxies'])==1.1 - assert ipc('wayfire/get-config-option',{'option':'ambient-background/hyperspace_distant_drift'})['value']==old_drift - assert ipc('wayfire/get-config-option',{'option':'ambient-background/hyperspace_star_density'})['value']==old_stars - print('PASS WCM Distant galaxy density saves independently of drift and stars',flush=True) - tab('Digital Rain') - choose('Digital Rain background','Desktop wallpaper') - wait(lambda:ipc('wayfire/get-config-option',{'option':'ambient-background/matrix_background'})['value']=='wallpaper') - choose('Digital Rain background','None') - glyphs=wait(lambda:control('Glyph size',Atspi.Role.SPIN_BUTTON)) - assert glyphs.get_value_iface().set_current_value(18) - wait(lambda:ipc('ambient/status')['matrix']['FONT_SIZE']==18) - tab('Lock screen') - choose('Lock controls monitor','HEADLESS-2') - wait(lambda:ipc('wayfire/get-config-option',{'option':'ambient-background/lock_output'})['value']=='HEADLESS-2') - assert value()['ambient-background']['lock_output']=='HEADLESS-2' - choose('Lock controls monitor','All monitors') - wait(lambda:ipc('wayfire/get-config-option',{'option':'ambient-background/lock_output'})['value']=='all') - assert value()['ambient-background']['lock_output']=='all' - assert ipc('wayfire/get-config-option',{'option':'ambient-background/lock_ui_on_interaction'})['value']=='true' - controls_delay=wait(lambda:control('Lock controls hide delay',Atspi.Role.SPIN_BUTTON)) - assert controls_delay.get_value_iface().set_current_value(20) - wait(lambda:float(ipc('wayfire/get-config-option',{'option':'ambient-background/lock_ui_hide_delay'})['value'])==20) - assert float(value()['ambient-background']['lock_ui_hide_delay'])==20 - print('PASS physical lock monitor/All selector and input-only UI settings save and update live',flush=True) - choose('Lock-screen background','Nebula') - wait(lambda:ipc('wayfire/get-config-option',{'option':'ambient-background/lock_background'})['value']=='nebula') - assert value()['ambient-background']['lock_background']=='nebula' - choose('Lock-screen background','Digital Rain') - wait(lambda:ipc('wayfire/get-config-option',{'option':'ambient-background/lock_background'})['value']=='digitalrain') - desktop_speed=ipc('wayfire/get-config-option',{'option':'ambient-background/hyperspace_speed'})['value'] - lock_speed=wait(lambda:control('Lock animation speed',Atspi.Role.SPIN_BUTTON)) - assert lock_speed.get_value_iface().set_current_value(.5) - wait(lambda:float(ipc('wayfire/get-config-option',{'option':'ambient-background/lock_speed'})['value'])==.5) - assert ipc('wayfire/get-config-option',{'option':'ambient-background/hyperspace_speed'})['value']==desktop_speed - assert value()['ambient-background']['lock_background']=='digitalrain' - assert float(value()['ambient-background']['lock_speed'])==.5 - for label in ['Lock animation brightness','Override lock colours','Lock colour','Show lock clock', - 'Show lock date','Lock fade-to-black delay','Lock fade duration', - 'Reveal lock controls on interaction','Lock controls fade duration']: - assert find(label,Atspi.Role.LABEL),label - choose('Lock-screen background','Same as Ambient') - print('PASS WCM Lock screen controls save separately from Ambient appearance',flush=True) - tab('Integration') - assert find('Edit idle, locking, alerts and audio settings',Atspi.Role.PUSH_BUTTON) - assert not find('Digital Rain',Atspi.Role.PUSH_BUTTON), 'Separate legacy page remains' - print('PASS independent appearance and background controls plus unified integration',flush=True) - print('PASS WCM main-screen discovery, effect choices, live idle-delay control, optional shortcut and retained preferences',flush=True) -finally: - if ui is not None: - ui.terminate() - try:ui.wait(timeout=5) - except subprocess.TimeoutExpired:ui.kill();ui.wait(timeout=5) - compositor.terminate() - try:compositor.wait(timeout=5) - except subprocess.TimeoutExpired:compositor.kill();compositor.wait(timeout=5) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/ambient.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/ambient.py deleted file mode 100755 index d70b8ddf6d6b..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/ambient.py +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env python3 -from pathlib import Path -import json,sys,time -from PIL import Image,ImageChops -stage=Path(sys.argv[1]).resolve();sys.path.insert(0,str(stage/'tests')) -from private_session import PrivateSession -extra=''' -[core] -vwidth=4 -vheight=3 -[cube] -render_at_rest=true -hyperspace_speed=0 -hyperspace_star_density=0 -hyperspace_distant_stars=0 -hyperspace_cloud_density=0 -hyperspace_glow=0 -hyperspace_pulsars=0 -hyperspace_anomalies=0 -hyperspace_dust_density=0 -[ambient-background] -hyperspace_speed=0 -hyperspace_star_density=0 -hyperspace_distant_stars=0 -hyperspace_cloud_density=0 -hyperspace_glow=0 -hyperspace_pulsars=0 -hyperspace_anomalies=0 -hyperspace_dust_density=0 -enabled=true -controller= -manual_duration=0.1 -restore_duration=0.05 -toggle=<super> <alt> KEY_A -''' -with PrivateSession(stage,plugins=False,hotplug=True,extra_plugins=' ambient-background render-test',config_extra=extra) as s: - print('SESSION',s.root,flush=True) - def call(m,d=None): - result=s.call(m,d);assert not isinstance(result,dict) or not result.get('error'),result - return result - def snap(name): - path=s.root/(name+'.png');s.run(['grim','-o','VIRTUAL-1',str(path)]) - return Image.open(path).convert('RGB') - def press(key): - keys=['KEY_LEFTMETA','KEY_LEFTALT','KEY_A'] if key.startswith('<') else [key] - for k in keys: call('stipc/feed_key',{'key':k,'state':True}) - for k in reversed(keys): call('stipc/feed_key',{'key':k,'state':False}) - def sync(values): - call('ambient/sync',{'outputs':[{'name':'HEADLESS-'+str(i),'opacity':a,'black':0,'effect':1} for i,a in enumerate(values,1)]});time.sleep(.18) - s.wait(lambda:len(call('ambient/status')['outputs'])==4) - call('stipc/move_cursor',{'x':1930,'y':10}) - s.spawn('seam-app',['alacritty','--config-file','/dev/null','--class','ambient-seam','-o','colors.primary.background="#D76F2E"','-e','sleep','180']) - view=s.wait(lambda:next((v for v in call('window-rules/list-views') if v.get('app-id')=='ambient-seam' and v.get('mapped')),None)) - time.sleep(.5) - call('window-rules/set-window-scope',{'id':view['id'],'scope':'virtual'}) - call('window-rules/configure-view',{'id':view['id'],'tiled-edges':0,'geometry':{'x':500,'y':360,'width':800,'height':200}}) - time.sleep(.5) - normal=snap('normal');assert normal.getpixel((550,450))==(215,111,46),normal.getpixel((550,450)) - sync([0,1,1,1]);split=snap('split') - assert split.getpixel((550,450))==(0,0,0),split.getpixel((550,450)) - assert split.getpixel((900,450))==(215,111,46),split.getpixel((900,450)) - sync([.5,1,1,1]);half=snap('half');assert max(abs(a-b) for a,b in zip(half.getpixel((550,450)),(108,56,23)))<=2,half.getpixel((550,450)) - print('PASS per-physical-monitor alpha, seam-spanning app and partial transparency',flush=True) - sync([1,1,1,1]);before=call('window-rules/list-views') - assert call('ambient/toggle')['manual'];time.sleep(.25) - assert all(z['opacity']==0 for z in call('ambient/status')['outputs']) - assert snap('manual').getpixel((900,450))==(0,0,0) - call('stipc/move_cursor',{'x':900,'y':450});time.sleep(.12) - assert call('ambient/status')['manual'],'Mouse dismissed manual mode' - press('KEY_A');assert call('ambient/status')['manual'] - press('KEY_ESC');time.sleep(.15) - assert not call('ambient/status')['manual'],'Escape failed to restore' - assert all(z['opacity']==1 for z in call('ambient/status')['outputs']) - call('stipc/move_cursor',{'x':1930,'y':10}) - assert snap('restored').getpixel((900,450))==(215,111,46) - press('<super> <alt> KEY_A');time.sleep(.15) - assert call('ambient/status')['manual'],'Configured toggle failed' - press('<super> <alt> KEY_A');time.sleep(.15) - assert not call('ambient/status')['manual'] - after=call('window-rules/list-views');assert [(v['id'],v.get('geometry')) for v in before]==[(v['id'],v.get('geometry')) for v in after] - print('PASS explicit toggle, Escape, mouse persistence, restore and unchanged window geometry',flush=True) - call('wayfire/set-config-options',{'ambient-background/hyperspace_speed':1.5,'ambient-background/hyperspace_dust_density':.9}) - call('ambient/toggle');time.sleep(.2) - a=snap('moving');time.sleep(.25);assert ImageChops.difference(a,snap('moving-next')).getbbox() - call('ambient/restore');time.sleep(.2) - plugins=call('wayfire/get-config-option',{'option':'core/plugins'})['value'] - call('wayfire/set-config-options',{'core/plugins':plugins+' cube'});call('wayfire/reload-plugins');time.sleep(.2) - call('test/cube',{'output':'VIRTUAL-1','angle':.65,'zoom':.55});time.sleep(.2) - a=snap('cube');time.sleep(.2);assert ImageChops.difference(a,snap('cube-next')).getbbox() - assert call('test/partial',{'output':'VIRTUAL-1'})['outside']==0 - call('test/cube',{'output':'VIRTUAL-1','stop':True});time.sleep(.3) - print('PASS continuously animated desktop and Cube backdrop; clipped damage',flush=True) - time.sleep(1.1);sync([0,1,1,1]);time.sleep(3.15) - assert all(z['opacity']==1 for z in call('ambient/status')['outputs']),'Watchdog left UI hidden' - print('PASS independent compositor watchdog restores UI',flush=True) - call('ambient/toggle');time.sleep(.2) - plugins=call('wayfire/get-config-option',{'option':'core/plugins'})['value'] - call('wayfire/set-config-options',{'core/plugins':' '.join(p for p in plugins.split() if p!='ambient-background')});call('wayfire/reload-plugins');time.sleep(.2) - assert any(v['id']==view['id'] for v in call('window-rules/list-views')) - print('PASS plugin unload restores desktop; ALL AMBIENT GPU CHECKS PASSED',flush=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/anomalies.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/anomalies.py deleted file mode 100755 index c5cc7ba72c01..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/anomalies.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 -"""Rare encounters: gradual arrival, independent controls, freeze and restore.""" -from pathlib import Path -import sys,time -import numpy as np -from PIL import Image -stage=Path(sys.argv[1]).resolve();sys.path.insert(0,str(stage/'tests')) -from private_session import PrivateSession -extra=""" -[ambient-background] -enabled=true -controller= -focused_delay=86400 -inactive_delay=86400 -hyperspace_speed=5 -hyperspace_seed=31415 -hyperspace_cloud_density=0 -hyperspace_star_density=0 -hyperspace_distant_stars=0 -hyperspace_dust_density=0 -hyperspace_glow=0 -hyperspace_pulsars=0 -hyperspace_anomalies=1 -hyperspace_asteroids=0 -hyperspace_galaxies=0 -""" -with PrivateSession(stage,hotplug=True,extra_plugins=' ambient-background render-test',config_extra=extra) as s: - print('SESSION',s.root,flush=True) - s.wait(lambda:len(s.call('ambient/status')['outputs'])==4) - assert s.call('ambient/status')['enabled'] - s.call('test/solid',{'key':'desktop','output':'VIRTUAL-1','layer':3,'x':0,'y':0,'w':40,'h':40,'r':0,'g':1,'b':0,'a':1}) - s.call('stipc/move_cursor',{'x':1930,'y':10}) - def snapshot(name): - s.call('ambient/sync',{'outputs':[{'name':'HEADLESS-'+str(i),'opacity':0,'black':0,'effect':1} for i in range(1,5)]}) - s.wait(lambda:all(z["opacity"]==0 for z in s.call("ambient/status")["outputs"])) - time.sleep(.08) - p=s.root/(name+'.png');s.run(['grim','-o','VIRTUAL-1',str(p)]) - return np.asarray(Image.open(p).convert('RGB')) - def settings(**options): - s.call('wayfire/set-config-options',{'ambient-background/'+k:v for k,v in options.items()});time.sleep(.15) - assert snapshot('quiet-start').max()==0, 'Encounter arrived without its quiet interval' - deadline=time.monotonic()+20 - while True: - frame=snapshot('arrival') - if frame.max()>12:break - assert time.monotonic()<deadline,'No rare encounter appeared' - time.sleep(.6) - time.sleep(2) - settings(hyperspace_speed=0) - frozen=snapshot('frozen');time.sleep(.3) - assert np.array_equal(frozen,snapshot('still-frozen')),'Speed zero moved encounter' - settings(hyperspace_anomalies=0) - assert snapshot('disabled').max()==0,'Encounter survived its zero-intensity control' - settings(hyperspace_anomalies=1) - assert np.array_equal(frozen,snapshot('restored')),'Changing intensity reset the encounter' - assert float(s.call('wayfire/get-config-option',{'option':'ambient-background/hyperspace_pulsars'})['value'])==0 - assert s.call('test/partial',{'output':'VIRTUAL-1'})['outside']==0 - print('PASS quiet interval, live encounter, independent intensity, frozen clock, restoration and partial damage',flush=True) - log=(s.root/'wayfire.log').read_text(errors='replace') - assert 'GL_INVALID' not in log and 'Shader compilation failed' not in log diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/asteroid-flyby.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/asteroid-flyby.py deleted file mode 100755 index 1905f11d3ceb..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/asteroid-flyby.py +++ /dev/null @@ -1,68 +0,0 @@ -#!/usr/bin/env python3 -"""Track an opaque close pass all the way to natural viewport clipping.""" -from pathlib import Path -import argparse, json, os, subprocess, tempfile -import numpy as np -from PIL import Image - -p = argparse.ArgumentParser() -p.add_argument('--renderer', type=Path, required=True) -a = p.parse_args() -assert os.geteuid() != 0 -root = Path(tempfile.mkdtemp(prefix='ambient-asteroid-flyby-')) -env = {'PATH': '/usr/bin:/bin', 'EGL_PLATFORM': 'surfaceless', 'HOME': str(root)} -results = [] -encounters = [(13, 6.325440168, False), (85, 74.757486343, True), - (370, 90.816016555, True)] -for seed, arrival, huge in encounters: - for width, height in [(960, 540), (2880, 540), (540, 960)]: - frames = [] - # Include ordinary and near-maximum huge bodies; track the entire pass. - for clock in np.arange(arrival-140/64-.04, arrival+.75, .04): - path = root / 'frame.rgba' - subprocess.run([str(a.renderer), str(path), str(seed), str(clock), str(width), str(height), - str(1 / 64), '2'], env=env, check=True, capture_output=True) - frame = np.frombuffer(path.read_bytes(), dtype=np.uint8).reshape(height, width, 4)[::-1].copy() - path.unlink() - mask = frame[:, :, :3].max(axis=2) > 8 - y, x = np.nonzero(mask) - if len(x) < 6: - continue - centre = (float(x.mean()) * 2 / width - 1, float(y.mean()) * 2 / height - 1) - touches_edge = bool(x.min()==0 or x.max()==width-1 or y.min()==0 or y.max()==height-1) - frames.append({'clock': float(clock), 'centre': centre, 'pixels': int(len(x)), - 'touches_edge': touches_edge, 'frame': frame}) - assert len(frames) > 15, ('Missing flyby', width, height) - first, last = frames[0], frames[-1] - first_offset = float(np.hypot(first['centre'][0]*width/height, first['centre'][1])) - last_offset = max(map(abs, last['centre'])) - visible_travel = last['clock'] - first['clock'] - # Large bodies need greater clearance but still enter inside the central - # portion of the viewport. Height units make this independent of aspect. - assert first_offset < (.4 if huge else .12), ('Arrival outside central flight corridor', first_offset, seed, width, height) - assert max(f['pixels'] for f in frames) > first['pixels']*3, ('No close approach', width, height) - assert last['touches_edge'], ('Body disappeared in front of the camera', last_offset, width, height) - assert last_offset > .65, ('Pass did not leave the central approach', last_offset, width, height) - assert visible_travel/3.6 < .7, ('Asteroid approaches too slowly at speed 2', visible_travel, seed) - edge_travel = .04*sum(max(map(abs,f['centre'])) > .65 for f in frames) - assert edge_travel/3.6 < .2, ('Rock lingered in the periphery at speed 2', edge_travel, seed, width, height) - # The final partly clipped facets must stay opaque: changing the colour - # behind them must not show through a central-area fade or early cutoff. - path = root/'white.rgba' - subprocess.run([str(a.renderer), str(path), str(seed), str(last['clock']), str(width), str(height), - str(1/64), '2', '0', '1'], env=env, check=True, capture_output=True) - white = np.frombuffer(path.read_bytes(), dtype=np.uint8).reshape(height,width,4)[::-1].copy() - body = last['frame'][:,:,:3].max(axis=2)>8 - assert np.abs(white[:,:,:3].astype(int)-last['frame'][:,:,:3].astype(int))[body].max() <= 1, \ - ('Body faded before completing its pass', width, height) - path.unlink() - for name, f in [('arrival', first), ('approach', frames[len(frames)//2]), ('pass', last)]: - Image.fromarray(f['frame'][:, :, :3]).save(root / f'{seed}-{width}x{height}-{name}.png') - results.append({'seed': seed, 'huge': huge, 'size': [width, height], 'first_offset': first_offset, 'last_offset': last_offset, - 'visible_travel': visible_travel, 'seconds_at_speed_2': visible_travel/3.6, - 'exit_touches_edge': last['touches_edge'], 'edge_seconds_at_speed_2': edge_travel/3.6, - 'close_body_opaque': True}) -(root / 'tracking.json').write_text(json.dumps(results, indent=2)) -print('PASS ordinary/huge central arrival, full opaque close pass, natural clipped exit and brief edge transit on wide/portrait outputs', flush=True) -print(json.dumps(results), flush=True) -print('ARTIFACTS', root, flush=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/asteroid-native.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/asteroid-native.py deleted file mode 100755 index 536121baba35..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/asteroid-native.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 -"""Private compositor: solid encounters, paused travel, Cube and partial damage.""" -from pathlib import Path -import sys,time -import numpy as np -from PIL import Image -stage=Path(sys.argv[1]).resolve();sys.path.insert(0,str(stage/'tests')) -from private_session import PrivateSession -extra=''' -[core] -vwidth=4 -vheight=3 -[cube] -render_at_rest=true -mouse_overview=true -[ambient-background] -enabled=true -controller= -focused_delay=86400 -inactive_delay=86400 -hyperspace_speed=0 -hyperspace_seed=129 -hyperspace_cloud_density=0 -hyperspace_star_density=0 -hyperspace_distant_stars=0 -hyperspace_dust_density=0 -hyperspace_glow=0 -hyperspace_pulsars=0 -hyperspace_anomalies=0 -hyperspace_galaxies=0 -hyperspace_asteroids=1 -''' -with PrivateSession(stage,hotplug=True,extra_plugins=' ambient-background cube render-test',config_extra=extra) as s: - print('SESSION',s.root,flush=True) - def call(method,data=None): - r=s.call(method,data);assert not isinstance(r,dict) or not r.get('error'),r;return r - def snapshot(name): - call('ambient/sync',{'outputs':[{'name':'HEADLESS-'+str(i),'opacity':0,'black':0,'effect':1} for i in range(1,5)]}) - time.sleep(.08);p=s.root/(name+'.png');s.run(['grim','-o','VIRTUAL-1',str(p)]) - return np.asarray(Image.open(p).convert('RGB')) - def settings(**options): - call('wayfire/set-config-options',{'ambient-background/'+k:v for k,v in options.items()});time.sleep(.12) - s.wait(lambda:len(call('ambient/status')['outputs'])==4) - call('stipc/move_cursor',{'x':1930,'y':10}) - first=snapshot('frozen-asteroids');assert first.max()>20 - time.sleep(.25);assert np.array_equal(first,snapshot('still-frozen')) - settings(hyperspace_asteroids=0);assert snapshot('disabled').max()==0 - settings(hyperspace_asteroids=1);assert np.array_equal(first,snapshot('restored')) - settings(hyperspace_speed=1.5);time.sleep(.4) - moved=snapshot('approaching');assert not np.array_equal(first,moved) - settings(hyperspace_speed=0) - frozen=snapshot('stopped');time.sleep(.25);assert np.array_equal(frozen,snapshot('still-stopped')) - assert call('test/partial',{'output':'VIRTUAL-1'})['outside']==0 - call('test/cube',{'output':'VIRTUAL-1','angle':.7,'zoom':.55});time.sleep(.25) - assert snapshot('cube-with-asteroids').max()>20 - assert call('test/partial',{'output':'VIRTUAL-1'})['outside']==0 - call('test/cube',{'output':'VIRTUAL-1','stop':True}) - log=(s.root/'wayfire.log').read_text(errors='replace') - assert 'GL_INVALID' not in log and 'GL_OUT_OF_MEMORY' not in log and 'Shader compilation failed' not in log - print('PASS native asteroid motion/freeze, density disable/restore, shared Cube rendering and clipped partial damage',flush=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/asteroid-reload.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/asteroid-reload.py deleted file mode 100755 index 0c13c92e7bbf..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/asteroid-reload.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python3 -"""Reload after a globally loaded old renderer; match a fresh candidate frame.""" -from pathlib import Path -import subprocess, sys, time -import numpy as np -from PIL import Image - -stage = Path(sys.argv[1]).resolve() -sys.path.insert(0, str(stage/'tests')) -from private_session import PrivateSession - -candidate = stage/'plugins/libambient-background.so' -old = stage/'plugins/old-r8/libambient-background.so' -exports = subprocess.check_output(['nm', '-D', '-C', str(candidate)], text=True) -assert 'ambient_asteroid_field' not in exports, 'Renderer internals are interposable' -config = ''' -[cube] -render_at_rest=true -[ambient-background] -enabled=true -controller= -focused_delay=86400 -inactive_delay=86400 -hyperspace_speed=0 -hyperspace_seed=129 -hyperspace_cloud_density=0 -hyperspace_star_density=0 -hyperspace_distant_stars=0 -hyperspace_dust_density=0 -hyperspace_glow=0 -hyperspace_pulsars=0 -hyperspace_anomalies=0 -hyperspace_galaxies=0 -hyperspace_asteroids=1 -''' - -def capture(s, name): - s.call('ambient/sync', {'outputs': [ - {'name': 'HEADLESS-'+str(i), 'opacity': 0, 'black': 0, 'effect': 1} - for i in range(1, 5)]}) - s.wait(lambda: all(z['opacity'] == 0 for z in s.call('ambient/status')['outputs'])) - s.wait(lambda: bool((s.call('ambient/status').get('scenes') or {}).get('VIRTUAL-1'))) - time.sleep(.08) - p = s.root/(name+'.png') - s.run(['grim', '-o', 'VIRTUAL-1', str(p)]) - return np.asarray(Image.open(p).convert('RGB')) - -def checked(s, method, data=None): - r = s.call(method, data) - assert not isinstance(r, dict) or not r.get('error'), r - return r - -with PrivateSession(stage, hotplug=True, extra_plugins=' ambient-background cube render-test', - config_extra=config) as s: - print('FRESH', s.root, flush=True) - checked(s, 'stipc/move_cursor', {'x': 1930, 'y': 10}) - expected = capture(s, 'fresh-renderer') - assert expected.max() > 20 and expected.min() == 0 - checked(s, 'test/cube', {'output': 'VIRTUAL-1', 'angle': .7, 'zoom': .55}) - expected_cube = capture(s, 'fresh-cube') - -with PrivateSession(stage, hotplug=True, extra_plugins=f' {old} cube render-test', - config_extra=config) as s: - print('RELOADED', s.root, flush=True) - checked(s, 'stipc/move_cursor', {'x': 1930, 'y': 10}) - previous = capture(s, 'old-renderer') - assert not np.array_equal(previous, expected), 'Fixture does not distinguish old/new rendering' - plugins = checked(s, 'wayfire/get-config-option', {'option': 'core/plugins'})['value'] - checked(s, 'wayfire/set-config-options', {'core/plugins': plugins.replace(str(old), str(candidate))}) - checked(s, 'wayfire/reload-plugins') - actual = capture(s, 'reloaded-renderer') - assert np.array_equal(actual, expected), 'Reload still renders using the resident old version' - checked(s, 'test/cube', {'output': 'VIRTUAL-1', 'angle': .7, 'zoom': .55}) - actual_cube = capture(s, 'reloaded-cube') - assert np.array_equal(actual_cube, expected_cube), 'Cube reload uses a different renderer' - checked(s, 'test/cube', {'output': 'VIRTUAL-1', 'stop': True}) - checked(s, 'wayfire/set-config-options', {'ambient-background/hyperspace_speed': 2.0}) - time.sleep(.15) - moved = capture(s, 'fast-pass') - assert not np.array_equal(moved, actual), 'Reloaded motion is frozen' - log = (s.root/'wayfire.log').read_text(errors='replace') - assert 'GL_INVALID' not in log and 'Shader compilation failed' not in log -print('PASS old-to-new native reload matches fresh geometry/material/trajectory, including Cube delegation', flush=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/asteroid-render.cpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/asteroid-render.cpp deleted file mode 100644 index 4f12333b8360..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/asteroid-render.cpp +++ /dev/null @@ -1,54 +0,0 @@ -// Private, deterministic EGL fixture for the shared compositor/lock renderer. -#include "hyperspace-background.hpp" -#include "hyperspace-lock.h" -#include <EGL/egl.h> -#include <cstdio> -#include <cstdlib> -#include <vector> -static void check(bool ok,const char *what) { - if (!ok) { fprintf(stderr,"%s EGL=%x GL=%x\n",what,eglGetError(),glGetError());std::exit(1); } -} -int main(int argc,char **argv) { - check(argc>=8,"args output seed distance width height density quality [stars]"); - int seed=atoi(argv[2]),width=atoi(argv[4]),height=atoi(argv[5]),quality=atoi(argv[7]); - double distance=atof(argv[3]),density=atof(argv[6]); - check(eglBindAPI(EGL_OPENGL_ES_API),"bind API"); - EGLDisplay display=eglGetDisplay(EGL_DEFAULT_DISPLAY);check(eglInitialize(display,nullptr,nullptr),"display"); - const EGLint attrs[]={EGL_SURFACE_TYPE,EGL_PBUFFER_BIT,EGL_RENDERABLE_TYPE,EGL_OPENGL_ES2_BIT, - EGL_RED_SIZE,8,EGL_GREEN_SIZE,8,EGL_BLUE_SIZE,8,EGL_ALPHA_SIZE,8,EGL_NONE}; - EGLConfig config;EGLint count;check(eglChooseConfig(display,attrs,&config,1,&count)&&count,"config"); - const EGLint size[]={EGL_WIDTH,width,EGL_HEIGHT,height,EGL_NONE}; - EGLSurface surface=eglCreatePbufferSurface(display,config,size); - const EGLint ctxattrs[]={EGL_CONTEXT_CLIENT_VERSION,2,EGL_NONE}; - EGLContext context=eglCreateContext(display,config,EGL_NO_CONTEXT,ctxattrs); - check(eglMakeCurrent(display,surface,surface,context),"current"); - json_object *settings=json_object_new_object(); - json_object_object_add(settings,"hyperspace_seed",json_object_new_int(seed)); - json_object_object_add(settings,"hyperspace_quality",json_object_new_int(quality)); - json_object_object_add(settings,"hyperspace_asteroids",json_object_new_double(density)); - json_object_object_add(settings,"hyperspace_brightness",json_object_new_double(1)); - json_object_object_add(settings,"hyperspace_distant_stars",json_object_new_double(argc>8 ? atof(argv[8]) : 0)); - ambient_lock_host::parameters=settings; - OpenGL::program_t texture; - ambient_lock_host::texture_program=&texture; - { - ambient_hyperspace_background engine([]{}); - std::array<double,24> state{double(seed),std::floor(distance/12),0,std::fmod(distance,12),0,0, - 1,-3,15,.5,.3,0,.2,.1,.65,1,-15,18,.4,.4,.08,0,.3,0}; - check(engine.restore_scene(state),"restore scene"); - wf::auxilliary_buffer_t destination;destination.width=width;destination.height=height; - wf::render_target_t target(destination);target.geometry={0,0,double(width),double(height)}; - float background=argc>9 ? atof(argv[9]) : 0; - glClearColor(background,background,background,1);glClear(GL_COLOR_BUFFER_BIT); - engine.render(target,target.geometry,{width,height}); - std::vector<unsigned char> pixels(width*height*4); - glReadPixels(0,0,width,height,GL_RGBA,GL_UNSIGNED_BYTE,pixels.data()); - check(glGetError()==GL_NO_ERROR,"render"); - FILE *file=fopen(argv[1],"wb");check(file,"output");fwrite(pixels.data(),pixels.size(),1,file);fclose(file); - fprintf(stderr,"GPU %s; %dx%d\n",glGetString(GL_RENDERER),width,height); - } - json_object_put(settings); - texture.free_resources(); - eglMakeCurrent(display,EGL_NO_SURFACE,EGL_NO_SURFACE,EGL_NO_CONTEXT); - eglDestroySurface(display,surface);eglDestroyContext(display,context);eglTerminate(display); -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/asteroid-sizes.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/asteroid-sizes.py deleted file mode 100755 index f2a5f805014f..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/asteroid-sizes.py +++ /dev/null @@ -1,78 +0,0 @@ -#!/usr/bin/env python3 -"""Compare actual GPU silhouettes at the same apparent pass position.""" -from pathlib import Path -import argparse, json, os, subprocess, tempfile -import numpy as np -from PIL import Image - -p = argparse.ArgumentParser() -p.add_argument('--renderer', type=Path, required=True) -p.add_argument('--before', type=Path) -a = p.parse_args() -assert os.geteuid() != 0 -root = Path(tempfile.mkdtemp(prefix='ambient-asteroid-sizes-')) -env = {'PATH': '/usr/bin:/bin', 'EGL_PLATFORM': 'surfaceless', 'HOME': str(root)} -width, height = 1920, 1080 -# Known isolated encounters; arrival times are independent of body size. -encounters = [ - ('fragment', 3, 49.632767797), ('fragment', 4, 77.714514494), ('fragment', 6, 40.783897400), - ('rock', 2, 48.280719876), ('rock', 8, 50.549680948), ('rock', 9, 19.262689114), - ('boulder', 13, 6.325440168), ('boulder', 15, 4.058984280), ('boulder', 30, 9.716809154), - ('huge', 1, 89.392536044), ('huge', 14, 35.469819069), ('huge', 85, 74.757486343)] - -def measure(renderer, seed, clock): - path = root/'frame.rgba' - subprocess.run([str(renderer), str(path), str(seed), str(clock), str(width), str(height), - str(1/64), '2'], env=env, check=True, capture_output=True) - frame = np.frombuffer(path.read_bytes(), dtype=np.uint8).reshape(height, width, 4)[::-1].copy() - path.unlink() - y, x = np.nonzero(frame[:, :, :3].max(axis=2) > 8) - if not len(x): - return None - offset = float(np.hypot((x.mean()-width/2)*2/height, (y.mean()-height/2)*2/height)) - return {'offset': offset, 'diameter': int(max(np.ptp(x), np.ptp(y))+1), - 'area': int(len(x)), 'frame': frame} - -def sample(renderer, label): - results = [] - for category, seed, arrival in encounters: - lo, hi = arrival-115/64, arrival - # At the desired screen position, every body is well inside the frame. - # Empty early samples can be subpixel fragments; empty late samples - # have already completed their pass. - for _ in range(16): - clock = (lo+hi)/2 - m = measure(renderer, seed, clock) - if m is None: - if arrival-clock > .5: - lo = clock - else: - hi = clock - elif m['offset'] < .30: - lo = clock - else: - hi = clock - m = measure(renderer, seed, (lo+hi)/2) - assert m is not None and abs(m['offset']-.30) < .015, (label, seed, m) - Image.fromarray(m.pop('frame')[:,:,:3]).save(root/f'{label}-{category}-{seed}.png') - results.append({'category': category, 'seed': seed, **m}) - return results - -after = sample(a.renderer, 'after') -span = max(v['diameter'] for v in after)/min(v['diameter'] for v in after) -medians = {category: float(np.median([v['diameter'] for v in after if v['category']==category])) - for category in ['fragment', 'rock', 'boulder', 'huge']} -assert span >= 20, ('Insufficient visible size spread', span) -assert medians['rock'] > medians['fragment']*2, medians -assert medians['boulder'] > medians['fragment']*8, medians -assert medians['huge'] > medians['boulder']*1.3, medians -report = {'after': after, 'diameter_span': span, 'median_diameters': medians} -if a.before: - before = sample(a.before, 'before') - old_span = max(v['diameter'] for v in before)/min(v['diameter'] for v in before) - assert span > old_span*3, ('Flight clearance still cancels the size spread', old_span, span) - report.update(before=before, previous_diameter_span=old_span) -(root/'sizes.json').write_text(json.dumps(report, indent=2)) -print('PASS actual silhouette sizes at matched screen position, including rare huge bodies', flush=True) -print(json.dumps(report), flush=True) -print('ARTIFACTS', root, flush=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/asteroids.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/asteroids.py deleted file mode 100755 index 0114780b81fe..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/asteroids.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python3 -"""Solid asteroid coverage, repeatability, world-clock seams and field gaps.""" -from pathlib import Path -import argparse,os,subprocess,tempfile -import numpy as np -from PIL import Image -p=argparse.ArgumentParser();p.add_argument('--renderer',type=Path,required=True) -a=p.parse_args();assert os.geteuid()!=0 -root=Path(tempfile.mkdtemp(prefix='ambient-asteroid-geometry-')) -env={'PATH':'/usr/bin:/bin','EGL_PLATFORM':'surfaceless','HOME':str(root)} -def render(name,seed=31415,distance=48,density=.65,quality=2,width=960,height=540,stars=0,background=0): - path=root/(name+'.rgba') - subprocess.run([str(a.renderer),str(path),str(seed),str(distance),str(width),str(height), - str(density),str(quality),str(stars),str(background)],env=env,check=True,capture_output=True) - frame=np.frombuffer(path.read_bytes(),dtype=np.uint8).reshape(height,width,4).copy()[::-1] - Image.fromarray(frame[:,:,:3]).save(root/(name+'.png'));return frame -first=render('active-field') -assert first[:,:,:3].max()>25 and (first[:,:,:3].max(axis=2)>8).sum()>100 -assert np.array_equal(first,render('repeatable')) -assert not np.array_equal(first,render('other-seed',seed=27182)) -assert not render('disabled',density=0)[:,:,:3].any() -denser=render('denser',seed=85,distance=74.0,density=1.3) -sparse=render('sparser-default',seed=85,distance=74.0,density=.35) -assert 0 < (sparse[:,:,:3].max(axis=2)>8).sum() < (denser[:,:,:3].max(axis=2)>8).sum() -for quality in [0,1,2]:assert render('quality-'+str(quality),quality=quality)[:,:,:3].max()>20 -for distance in [4,8,12,24,48,72,110,150,180,220,256]:render('rocks-'+str(distance),distance=distance) -quiet=render('quiet-space',distance=112) -assert (quiet[:,:,:3].max(axis=2)>8).sum() < (first[:,:,:3].max(axis=2)>8).sum() -assert not render('long-open-stretch',distance=180)[:,:,:3].any() -for distance in [12,256,384,49152]: - before=render('before-'+str(distance),distance=distance-.001) - after=render('after-'+str(distance),distance=0 if distance==49152 else distance) - assert np.quantile(np.abs(before.astype(int)-after.astype(int)),.999)<4,('Clock wrap popped',distance) -wide=render('panorama',width=2880,height=540,density=1) -assert (wide[:,960:1920,:3].max(axis=2)>8).sum()>10 -assert (wide[:,:,:3].max(axis=2)>8).sum()>100 -stars=render('rocks-stars',stars=2);empty=render('stars-only',density=0,stars=2) -body=first[:,:,:3].max(axis=2)>8 -opaque=render('opaque-silhouettes',background=1) -assert (opaque[:,:,:3].max(axis=2)[body]<150).sum()>100, 'Rocks do not occlude background light' -assert (empty[:,:,:3].max(axis=2)>80).any() -assert np.abs(stars[:,:,:3].astype(int)-empty[:,:,:3].astype(int))[body].max()>15 -print('PASS solid seeded shapes/materials, disabled/density/quality controls, field gaps, panoramic coverage and seamless bounded travel',flush=True) -print('ARTIFACTS',root,flush=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/backgrounds.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/backgrounds.py deleted file mode 100755 index a4e9f758ea7f..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/backgrounds.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env python3 -"""Check independent backdrop choices on real GPU output with synthetic wallpaper.""" -from pathlib import Path -import json, sys, time -from PIL import Image -stage=Path(sys.argv[1]).resolve();sys.path.insert(0,str(stage/'tests')) -from private_session import PrivateSession -extra=''' -[core] -vwidth=4 -vheight=3 -[cube] -render_at_rest=true -[ambient-background] -enabled=true -controller= -backend=hyperspace -focused_delay=86400 -inactive_delay=86400 -hyperspace_speed=0 -hyperspace_star_density=0 -hyperspace_distant_stars=0 -hyperspace_cloud_density=0 -hyperspace_glow=0 -hyperspace_pulsars=0 -hyperspace_anomalies=0 -hyperspace_dust_density=0 -''' -with PrivateSession(stage,hotplug=True,extra_plugins=' cube ambient-background digitalrain-cursor digitalrain-idle digitalrain-sampler render-test',config_extra=extra) as s: - print('SESSION',s.root,flush=True) - def call(method,data=None): - result=s.call(method,data);assert not isinstance(result,dict) or not result.get('error'),result - return result - cfg=s.root/'wayfire.ini';original=cfg.read_text() - choices={'backend':'hyperspace','hyperspace_background':'none','matrix_background':'none', - 'hyperspace_background_opacity':'1','matrix_background_opacity':'1'} - def settings(**values): - choices.update({k:str(v) for k,v in values.items()}) - cfg.write_text(original+'\n[ambient-background]\n'+''.join(k+'='+v+'\n' for k,v in choices.items())) - s.wait(lambda:all(call('wayfire/get-config-option',{'option':'ambient-background/'+k})['value']==v - or (k.endswith('_opacity') and float(call('wayfire/get-config-option',{'option':'ambient-background/'+k})['value'])==float(v)) for k,v in choices.items())) - time.sleep(.2) - def snapshot(name): - p=s.root/(name+'.png');s.run(['grim','-o','VIRTUAL-1',str(p)]) - image=Image.open(p).convert('RGB');assert image.getpixel((10,5))==(0,255,255),'Panel changed' - return image - s.spawn('wallpaper',['python3',str(stage/'tests/ambient-layer.py'),'wallpaper']) - s.spawn('panel',['python3',str(stage/'tests/ambient-layer.py')]) - s.wait(lambda:any(v.get('app-id')=='ambient-fixture-wallpaper' for v in call('window-rules/list-views'))) - time.sleep(.3) - assert snapshot('hyper-none').getpixel((100,100))==(0,0,0) - settings(hyperspace_background='wallpaper') - assert snapshot('hyper-wallpaper').getpixel((100,100))==(48,64,128) - settings(hyperspace_background_opacity=.5) - assert snapshot('hyper-half').getpixel((100,100))==(24,32,64) - settings(hyperspace_background='none',hyperspace_background_opacity=1) - assert snapshot('hyper-none-restored').getpixel((100,100))==(0,0,0) - print('PASS Hyperspace None, wallpaper, half opacity and immediate restoration',flush=True) - drcfg=s.root/'ambient-background.toml';drcfg.write_text('''schema_version=1 -[global] -TIMEOUT=86400 -FOCUSED_TIMEOUT=86400 -LOCK_TIMEOUT=0 -LOG_FILE="stderr" -[matrix] -TRANSPARENT_TIMEOUT=600 -BLACK_RAIN_TIMEOUT=600 -''') - s.env['AMBIENT_BACKGROUND_CONFIG']=str(drcfg) - s.spawn('idle-manager',[str(stage/'digitalrain')]) - s.wait(lambda:(s.root/'digitalrain/control.sock').exists()) - s.spawn('controller',[str(stage/'ambient-background'),'--daemon','--renderer',str(stage/'digitalrain')]) - settings(backend='matrix') - s.wait(lambda:sum(v.get('app-id')=='ambient-matrix' for v in call('window-rules/list-views'))==4) - time.sleep(.5) - def wallpaper_samples(image): - return sum(image.getpixel((x,y))[2]>image.getpixel((x,y))[1]+20 - for y in range(50,700,13) for x in range(20,1900,17)) - assert wallpaper_samples(snapshot('matrix-none'))==0,'Matrix retained wallpaper' - settings(matrix_background='wallpaper') - assert wallpaper_samples(snapshot('matrix-wallpaper'))>3000,'Matrix omitted selected wallpaper' - settings(matrix_background_opacity=.5) - image=snapshot('matrix-half') - assert sum(image.getpixel((x,100))==(24,32,64) for x in range(20,1900,17))>30 - settings(matrix_background='none',matrix_background_opacity=1) - assert wallpaper_samples(snapshot('matrix-none-restored'))==0 - call('test/cube',{'output':'VIRTUAL-1','angle':.65,'zoom':.55});time.sleep(.3) - assert snapshot('cube-matrix-none').getpixel((80,80))[2]<=snapshot('cube-matrix-none-2').getpixel((80,80))[1]+20 - assert call('test/partial',{'output':'VIRTUAL-1'})['outside']==0 - call('test/cube',{'output':'VIRTUAL-1','stop':True}) - assert choices['hyperspace_background']=='none' - print('PASS independent Matrix backdrop, opacity, Cube delegation and partial damage',flush=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/cloud-depth.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/cloud-depth.py deleted file mode 100755 index 72764f029193..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/cloud-depth.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env python3 -"""Controlled GPU images: cloud silhouettes travel radially, without a fog sheet.""" -from pathlib import Path -import os, shlex, subprocess, sys, tempfile -import numpy as np -from PIL import Image -from scipy.ndimage import uniform_filter -from scipy.signal import correlate2d -assert os.geteuid() != 0 -stage = Path(sys.argv[1]).resolve() -root = Path(tempfile.mkdtemp(prefix='ambient-cloud-depth-')) -shader = Path(os.environ.get('CLOUD_SHADER', str(stage/'effects/hyperspace/hyperspace-shaders.tpp'))) -subprocess.run(['c++','-O2','-std=c++17',f'-DSHADER_PATH="{shader}"', - f'-DANOMALY_PATH="{stage}/effects/hyperspace/hyperspace-anomalies.tpp"', - str(stage/'tests/cloud-shader.cpp'),'-o',str(root/'renderer'), - *shlex.split(subprocess.check_output(['pkg-config','--cflags','--libs','egl','glesv2'],text=True))],check=True) -env={'PATH':'/usr/bin:/bin','EGL_PLATFORM':'surfaceless','HOME':str(root)} -def render(name,phase=0,epoch=0,seed=31415,detail=2,density=.85,kind=None,age=8,colours=.75): - args=[str(root/'renderer'),str(root/(name+'.rgba')),str(phase),str(epoch),str(seed),'960','540',str(detail),str(density)] - args += [str(kind if kind is not None else -1),str(age),str(colours)] - subprocess.run(args,env=env,check=True,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) - rgba=np.frombuffer((root/(name+'.rgba')).read_bytes(),dtype=np.uint8).reshape(540,960,4).copy() - Image.fromarray(rgba[:,:,:3][::-1]).save(root/(name+'.png')) - return rgba -first=render('clouds-start');next_frame=render('clouds-next',phase=1) -coverage=(first[:,:,:3].max(axis=2)>3).mean() -assert .05 < coverage < .55, ('Clouds became an all-screen fog sheet',coverage) -assert not render('clouds-disabled',density=0).any(), 'Cloud density zero left a fixed central glow' -assert np.array_equal(first,render('repeatable')), 'Seeded cloud field changed without travel' -assert not np.array_equal(first,render('other-seed',seed=27182)), 'Journey seed ignored' -# Track texture patches externally. A forward journey moves persistent -# silhouettes away from the vanishing point; boiling noise has no such flow. -a=first[:,:,:3].astype(float).mean(axis=2);b=next_frame[:,:,:3].astype(float).mean(axis=2) -flow=[] -for y in range(40,500,25): - for x in range(40,920,25): - patch=a[y-6:y+7,x-6:x+7];std=patch.std() - if patch.mean()<4 or std<1.5:continue - area=b[y-26:y+27,x-26:x+27] - means=uniform_filter(area,size=13)[6:-6,6:-6] - sigma=np.sqrt(np.maximum(1e-5,uniform_filter(area*area,size=13)[6:-6,6:-6]-means*means)) - corr=correlate2d(area,patch-patch.mean(),mode='valid')/(169*std*sigma) - yy,xx=np.unravel_index(np.argmax(corr),corr.shape);dy=yy-20;dx=xx-20 - distance=np.hypot(x-480,y-270) - if corr[yy,xx]<.9 or abs(dx)>=20 or abs(dy)>=20 or distance<60:continue - radial=((x-480)*dx+(y-270)*dy)/distance - lateral=abs((x-480)*dy-(y-270)*dx)/distance - flow.append((radial,lateral)) -flow=np.array(flow) -assert len(flow)>35, ('Cloud silhouettes lost coherent structure',len(flow)) -assert (flow[:,0]>0).mean()>.9, 'Cloud motion does not approach the camera' -assert np.median(flow[:,0])>2 and np.median(flow[:,1])<1.5, 'Clouds boil or slide sideways instead of travelling' -tinted=render('chosen-tint',colours=0);palette=render('full-palette',colours=1) -assert np.array_equal(tinted[:,:,3],palette[:,:,3]), 'Colour variation changed cloud absorption' -assert np.abs(tinted[:,:,:3].astype(int)-palette[:,:,:3].astype(int)).max()>10, 'Cloud palette has no visible effect' -bright=palette[:,:,:3].max(axis=2)>8 -cold=(palette[:,:,2].astype(int)>palette[:,:,0].astype(int)+3)&bright -warm=(palette[:,:,0].astype(int)>palette[:,:,2].astype(int)+3)&bright -assert cold.sum()>100 and warm.sum()>100, ('No varied warm/cool bank colours',int(cold.sum()),int(warm.sum())) -print('PASS seeded cloud colours, global-tint mode, warm/cool variation and unchanged absorption',flush=True) -print('PASS open space and coherent perspective travel;',len(flow),'tracked patches; radial',round(float(np.median(flow[:,0])),2),'pixels',flush=True) -for epoch in [0,4095]: - before=render('before-wrap-'+str(epoch),phase=63.999,epoch=epoch) - after=render('after-wrap-'+str(epoch),phase=0,epoch=(epoch+1)%4096) - assert np.quantile(np.abs(before.astype(int)-after.astype(int)),.999)<3, 'Cloud recycling popped at clock wrap' -for detail in [0,1,2]: - assert render('quality-'+str(detail),detail=detail).max()>10 -binary=render('binary-stars',kind=0);remnant=render('broken-remnant',kind=1) -assert (binary[:,:,:3].max(axis=2)>10).sum()>25 -assert (remnant[:,:,:3].max(axis=2)>3).sum()>400 -assert not np.array_equal(binary,remnant), 'Encounter kinds rendered identically' -assert not np.array_equal(binary,render('binary-next',kind=0,age=12)), 'Binary orbit is frozen' -print('PASS seam-free recycling, bounded clock, quality levels and distinct encounter shaders',flush=True) -print('ARTIFACTS',root,flush=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/cloud-shader.cpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/cloud-shader.cpp deleted file mode 100644 index ddd6b653266a..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/cloud-shader.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include <EGL/egl.h> -#include <GLES2/gl2.h> -#include <cstdio> -#include <cstdlib> -#include <cstdint> -#include <vector> -#include <string> -#include SHADER_PATH -#include ANOMALY_PATH -void check(bool ok,const char *what) {if(!ok){fprintf(stderr,"%s EGL=%x GL=%x\n",what,eglGetError(),glGetError());exit(1);}} -GLuint shader(GLenum type,const char *source) { - GLuint id=glCreateShader(type);glShaderSource(id,1,&source,nullptr);glCompileShader(id); - GLint compiled;glGetShaderiv(id,GL_COMPILE_STATUS,&compiled); - if(!compiled){char log[8192];glGetShaderInfoLog(id,sizeof(log),nullptr,log);fprintf(stderr,"%s\n",log);}check(compiled,"compile");return id; -} -float random(uint32_t v){v^=v>>16;v*=0x7feb352du;v^=v>>15;v*=0x846ca68bu;v^=v>>16;return(v&0xffffffu)/16777216.f;} -int main(int argc,char **argv) { - check(argc>=7,"args output phase epoch seed width height [detail density]"); - int width=atoi(argv[5]),height=atoi(argv[6]),seed=atoi(argv[4]); - check(eglBindAPI(EGL_OPENGL_ES_API),"bind API"); - EGLDisplay display=eglGetDisplay(EGL_DEFAULT_DISPLAY);check(eglInitialize(display,nullptr,nullptr),"display"); - const EGLint attrs[]={EGL_SURFACE_TYPE,EGL_PBUFFER_BIT,EGL_RENDERABLE_TYPE,EGL_OPENGL_ES2_BIT,EGL_RED_SIZE,8,EGL_GREEN_SIZE,8,EGL_BLUE_SIZE,8,EGL_ALPHA_SIZE,8,EGL_NONE}; - EGLConfig config;EGLint count;check(eglChooseConfig(display,attrs,&config,1,&count)&&count,"config"); - const EGLint size[]={EGL_WIDTH,width,EGL_HEIGHT,height,EGL_NONE};EGLSurface surface=eglCreatePbufferSurface(display,config,size); - const EGLint ctxattrs[]={EGL_CONTEXT_CLIENT_VERSION,2,EGL_NONE};EGLContext context=eglCreateContext(display,config,EGL_NO_CONTEXT,ctxattrs); - check(eglMakeCurrent(display,surface,surface,context),"current"); - GLuint program=glCreateProgram();glAttachShader(program,shader(GL_VERTEX_SHADER,hyperspace_vertex));glAttachShader(program,shader(GL_FRAGMENT_SHADER,(argc>9 && atoi(argv[9])>=0)?hyperspace_anomaly_fragment:hyperspace_cloud_fragment));glLinkProgram(program); - GLint linked;glGetProgramiv(program,GL_LINK_STATUS,&linked);check(linked,"link");glUseProgram(program); - auto uniform=[&](const char *n,float f){glUniform1f(glGetUniformLocation(program,n),f);}; - glUniform2f(glGetUniformLocation(program,"resolution"),width,height); - const GLfloat mvp[]={2.f/width,0,0,0,0,2.f/height,0,0,0,0,1,0,-1,-1,0,1}; - glUniformMatrix4fv(glGetUniformLocation(program,"mvp"),1,GL_FALSE,mvp); - glUniform3f(glGetUniformLocation(program,"tint"),.2,.5215686,1); - glUniform3f(glGetUniformLocation(program,"scene_offset"),64*random(seed),64*random(seed+1),64*random(seed+2)); - glUniform1i(glGetUniformLocation(program,"detail"),argc>7?atoi(argv[7]):2); - uniform("colour_variation",argc>11?atof(argv[11]):.75); - uniform("phase",atof(argv[2]));uniform("epoch",atof(argv[3]));uniform("cloud_density",argc>8?atof(argv[8]):.85); - uniform("turbulence",.65);uniform("variation",1.15);uniform("ribbons",.75);uniform("glow",.65);uniform("brightness",.85);uniform("energy",1); - glUniform2f(glGetUniformLocation(program,"centre"),width*.42,height*.44); - uniform("size",.1);uniform("angle",.4);uniform("shape",.35);uniform("age",argc>10?atof(argv[10]):8);uniform("strength",1); - glUniform1i(glGetUniformLocation(program,"kind"),argc>9?atoi(argv[9]):0); - const GLfloat quad[]={0,0,(float)width,0,(float)width,(float)height,0,0,(float)width,(float)height,0,(float)height}; - GLuint attr=glGetAttribLocation(program,"position");glEnableVertexAttribArray(attr);glVertexAttribPointer(attr,2,GL_FLOAT,GL_FALSE,0,quad); - glViewport(0,0,width,height);glDrawArrays(GL_TRIANGLES,0,6);std::vector<unsigned char> pixels(width*height*4);glReadPixels(0,0,width,height,GL_RGBA,GL_UNSIGNED_BYTE,pixels.data());check(glGetError()==GL_NO_ERROR,"render"); - FILE *file=fopen(argv[1],"wb");check(file,"output");fwrite(pixels.data(),pixels.size(),1,file);fclose(file); - fprintf(stderr,"GPU %s; %dx%d\n",glGetString(GL_RENDERER),width,height); - eglMakeCurrent(display,EGL_NO_SURFACE,EGL_NO_SURFACE,EGL_NO_CONTEXT);eglDestroySurface(display,surface);eglDestroyContext(display,context);eglTerminate(display);return 0; -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/galaxies-native.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/galaxies-native.py deleted file mode 100755 index 13cbe2f72b1c..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/galaxies-native.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python3 -"""Private compositor: solid encounters, paused travel, Cube and partial damage.""" -from pathlib import Path -import sys,time -import numpy as np -from PIL import Image -stage=Path(sys.argv[1]).resolve();sys.path.insert(0,str(stage/'tests')) -from private_session import PrivateSession -extra=''' -[core] -vwidth=4 -vheight=3 -[cube] -render_at_rest=true -mouse_overview=true -[ambient-background] -enabled=true -controller= -focused_delay=86400 -inactive_delay=86400 -hyperspace_speed=0 -hyperspace_seed=129 -hyperspace_cloud_density=0 -hyperspace_star_density=0 -hyperspace_distant_stars=0 -hyperspace_dust_density=0 -hyperspace_glow=0 -hyperspace_pulsars=0 -hyperspace_anomalies=0 -hyperspace_galaxies=1.5 -hyperspace_distant_drift=1 -hyperspace_asteroids=0 -''' -with PrivateSession(stage,hotplug=True,extra_plugins=' ambient-background cube render-test',config_extra=extra) as s: - print('SESSION',s.root,flush=True) - def call(method,data=None): - r=s.call(method,data);assert not isinstance(r,dict) or not r.get('error'),r;return r - def snapshot(name): - call('ambient/sync',{'outputs':[{'name':'HEADLESS-'+str(i),'opacity':0,'black':0,'effect':1} for i in range(1,5)]}) - time.sleep(.08);p=s.root/(name+'.png');s.run(['grim','-o','VIRTUAL-1',str(p)]) - return np.asarray(Image.open(p).convert('RGB')) - def settings(**options): - call('wayfire/set-config-options',{'ambient-background/'+k:v for k,v in options.items()});time.sleep(.12) - s.wait(lambda:len(call('ambient/status')['outputs'])==4) - call('stipc/move_cursor',{'x':1930,'y':10}) - first=snapshot('frozen-galaxies');assert first.max()>20 - time.sleep(.25);assert np.array_equal(first,snapshot('still-frozen')) - settings(hyperspace_galaxies=0);assert snapshot('disabled').max()==0 - settings(hyperspace_galaxies=1.5);assert np.array_equal(first,snapshot('restored')) - settings(hyperspace_speed=1.5);time.sleep(.4) - moved=snapshot('approaching');assert not np.array_equal(first,moved) - settings(hyperspace_speed=0) - frozen=snapshot('stopped');time.sleep(.25);assert np.array_equal(frozen,snapshot('still-stopped')) - assert call('test/partial',{'output':'VIRTUAL-1'})['outside']==0 - call('test/cube',{'output':'VIRTUAL-1','angle':.7,'zoom':.55});time.sleep(.25) - assert snapshot('cube-with-galaxies').max()>20 - assert call('test/partial',{'output':'VIRTUAL-1'})['outside']==0 - call('test/cube',{'output':'VIRTUAL-1','stop':True}) - log=(s.root/'wayfire.log').read_text(errors='replace') - assert 'GL_INVALID' not in log and 'GL_OUT_OF_MEMORY' not in log and 'Shader compilation failed' not in log - print('PASS native galaxy motion/freeze, density disable/restore, shared Cube rendering and clipped partial damage',flush=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/galaxies-reload.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/galaxies-reload.py deleted file mode 100755 index 45dded8e0e4d..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/galaxies-reload.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python3 -"""Reload after a globally loaded old renderer; match a fresh candidate frame.""" -from pathlib import Path -import subprocess, sys, time -import numpy as np -from PIL import Image - -stage = Path(sys.argv[1]).resolve() -sys.path.insert(0, str(stage/'tests')) -from private_session import PrivateSession - -candidate = stage/'plugins/libambient-background.so' -old = stage/'plugins/old-r14/libambient-background.so' -exports = subprocess.check_output(['nm', '-D', '-C', str(candidate)], text=True) -assert 'ambient_galaxy_field' not in exports, 'Renderer internals are interposable' -config = ''' -[cube] -render_at_rest=true -[ambient-background] -enabled=true -controller= -focused_delay=86400 -inactive_delay=86400 -hyperspace_speed=0 -hyperspace_seed=129 -hyperspace_cloud_density=0 -hyperspace_star_density=0 -hyperspace_distant_stars=0 -hyperspace_dust_density=0 -hyperspace_glow=0 -hyperspace_pulsars=0 -hyperspace_anomalies=0 -hyperspace_galaxies=1.5 -hyperspace_distant_drift=1 -hyperspace_asteroids=0 -''' - -def capture(s, name): - s.call('ambient/sync', {'outputs': [ - {'name': 'HEADLESS-'+str(i), 'opacity': 0, 'black': 0, 'effect': 1} - for i in range(1, 5)]}) - s.wait(lambda: all(z['opacity'] == 0 for z in s.call('ambient/status')['outputs'])) - s.wait(lambda: bool((s.call('ambient/status').get('scenes') or {}).get('VIRTUAL-1'))) - time.sleep(.08) - p = s.root/(name+'.png') - s.run(['grim', '-o', 'VIRTUAL-1', str(p)]) - return np.asarray(Image.open(p).convert('RGB')) - -def checked(s, method, data=None): - r = s.call(method, data) - assert not isinstance(r, dict) or not r.get('error'), r - return r - -with PrivateSession(stage, hotplug=True, extra_plugins=' ambient-background cube render-test', - config_extra=config) as s: - print('FRESH', s.root, flush=True) - checked(s, 'stipc/move_cursor', {'x': 1930, 'y': 10}) - expected = capture(s, 'fresh-renderer') - assert expected.max() > 20 and expected.min() == 0 - checked(s, 'test/cube', {'output': 'VIRTUAL-1', 'angle': .7, 'zoom': .55}) - expected_cube = capture(s, 'fresh-cube') - -with PrivateSession(stage, hotplug=True, extra_plugins=f' {old} cube render-test', - config_extra=config) as s: - print('RELOADED', s.root, flush=True) - checked(s, 'stipc/move_cursor', {'x': 1930, 'y': 10}) - previous = capture(s, 'old-renderer') - assert not np.array_equal(previous, expected), 'Fixture does not distinguish old/new rendering' - plugins = checked(s, 'wayfire/get-config-option', {'option': 'core/plugins'})['value'] - checked(s, 'wayfire/set-config-options', {'core/plugins': plugins.replace(str(old), str(candidate))}) - checked(s, 'wayfire/reload-plugins') - actual = capture(s, 'reloaded-renderer') - assert np.array_equal(actual, expected), 'Reload still renders using the resident old version' - checked(s, 'test/cube', {'output': 'VIRTUAL-1', 'angle': .7, 'zoom': .55}) - actual_cube = capture(s, 'reloaded-cube') - assert np.array_equal(actual_cube, expected_cube), 'Cube reload uses a different renderer' - checked(s, 'test/cube', {'output': 'VIRTUAL-1', 'stop': True}) - checked(s, 'wayfire/set-config-options', {'ambient-background/hyperspace_speed': 2.0}) - time.sleep(.15) - moved = capture(s, 'fast-pass') - assert not np.array_equal(moved, actual), 'Reloaded motion is frozen' - log = (s.root/'wayfire.log').read_text(errors='replace') - assert 'GL_INVALID' not in log and 'Shader compilation failed' not in log -print('PASS old-to-new native reload matches fresh deep galaxy rendering, including Cube delegation', flush=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/galaxies.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/galaxies.py deleted file mode 100755 index 9ff2f1a30934..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/galaxies.py +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env python3 -"""Actual shared GPU renderer: deep galaxies, coherent drift and bounded wrap.""" -from pathlib import Path -import argparse, json, os, subprocess, tempfile -import numpy as np -from PIL import Image -from scipy.ndimage import label, center_of_mass - -p=argparse.ArgumentParser();p.add_argument('--renderer',type=Path,required=True) -a=p.parse_args();assert os.geteuid()!=0 -root=Path(tempfile.mkdtemp(prefix='ambient-galaxies-')) -env={'PATH':'/usr/bin:/bin','EGL_PLATFORM':'surfaceless','HOME':str(root)} -def render(name,seed=31415,drift=0,density=1.5,quality=2,width=1920,height=1080,brightness=1,opacity=1): - path=root/(name+'.rgba') - subprocess.run([str(a.renderer),str(path),str(seed),str(drift),str(width),str(height), - str(density),str(quality),str(brightness),str(opacity)],env=env,check=True,capture_output=True) - frame=np.frombuffer(path.read_bytes(),dtype=np.uint8).reshape(height,width,4)[::-1].copy() - path.unlink();Image.fromarray(frame[:,:,:3]).save(root/(name+'.png')) - return frame[:,:,:3] - -first=render('deep-field');assert first.max()>25 and (first.max(axis=2)>8).sum()>200 -assert np.array_equal(first,render('repeatable')) -assert not np.array_equal(first,render('other-seed',seed=27182)) -assert not render('disabled',density=0).any() -assert not render('brightness-zero',brightness=0).any() -half=render('half-opacity',opacity=.5) -assert np.abs(half.astype(float)-first/2).max()<=1.5,'Galaxies bypass shared effect opacity' -assert (render('sparse',density=.7).max(axis=2)>8).sum()<(first.max(axis=2)>8).sum() - -# Track emitted silhouettes rather than duplicating the renderer's equations. -def components(frame): - labels,count=label(frame.max(axis=2)>3) - sizes=np.bincount(labels.ravel()) - ids=[i for i in range(1,count+1) if sizes[i]>12] - return np.array(center_of_mass(frame.sum(axis=2),labels,ids))[:,::-1] -next_frame=render('slow-drift',drift=.04) -centres=components(first);later=components(next_frame) -assert len(centres)>3 and len(later)>3 -flows=[] -for centre in centres: - destination=later[np.argmin(np.linalg.norm(later-centre,axis=1))] - delta=destination-centre - radial=centre-np.array([960,540]);distance=np.linalg.norm(radial) - if distance<80 or np.linalg.norm(delta)>15:continue - flows.append((float(np.dot(delta,radial)/distance),float(np.linalg.norm(delta)))) -assert len(flows)>3 and sum(radial>0 for radial,_ in flows)/len(flows)>.8,flows -assert .05<np.median([distance for _,distance in flows])<8,flows -before=render('before-drift-wrap',drift=2*np.pi-.00001) -after=render('after-drift-wrap',drift=0) -assert np.abs(before.astype(int)-after.astype(int)).max()<4,'Deep field jumped at bounded clock wrap' -for quality in [0,1,2]:assert render('quality-'+str(quality),quality=quality).max()>20 -wide=render('panorama',width=2880,height=540,density=2) -for segment in np.split(wide,3,axis=1):assert (segment.max(axis=2)>5).sum()>10,'Galaxies concentrate in one monitor' -assert render('portrait',width=540,height=960,density=2).max()>15 -warm=(first[:,:,0].astype(int)>first[:,:,2].astype(int)+5) -cool=(first[:,:,2].astype(int)>first[:,:,0].astype(int)+3) -assert warm.sum()>25 and cool.sum()>25,'Missing warm cores or cool stellar discs' -report={'visible_pixels':int((first.max(axis=2)>8).sum()),'tracked_centres':len(flows), - 'median_motion_pixels':float(np.median([distance for _,distance in flows])), - 'wrap_max_difference':int(np.abs(before.astype(int)-after.astype(int)).max())} -(root/'checks.json').write_text(json.dumps(report,indent=2)) -print('PASS seeded deep galaxies, independent density, slow radial parallax, brightness/opacity, quality, panoramic coverage and seamless drift wrap',flush=True) -print(json.dumps(report),flush=True);print('ARTIFACTS',root,flush=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/galaxy-render.cpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/galaxy-render.cpp deleted file mode 100644 index 71deac513794..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/galaxy-render.cpp +++ /dev/null @@ -1,54 +0,0 @@ -// Private, deterministic EGL fixture for the shared compositor/lock renderer. -#include "hyperspace-background.hpp" -#include "hyperspace-lock.h" -#include <EGL/egl.h> -#include <cstdio> -#include <cstdlib> -#include <vector> -static void check(bool ok,const char *what) { - if (!ok) { fprintf(stderr,"%s EGL=%x GL=%x\n",what,eglGetError(),glGetError());std::exit(1); } -} -int main(int argc,char **argv) { - check(argc>=8,"args output seed drift width height density quality [brightness opacity]"); - int seed=atoi(argv[2]),width=atoi(argv[4]),height=atoi(argv[5]),quality=atoi(argv[7]); - double drift=atof(argv[3]),density=atof(argv[6]); - check(eglBindAPI(EGL_OPENGL_ES_API),"bind API"); - EGLDisplay display=eglGetDisplay(EGL_DEFAULT_DISPLAY);check(eglInitialize(display,nullptr,nullptr),"display"); - const EGLint attrs[]={EGL_SURFACE_TYPE,EGL_PBUFFER_BIT,EGL_RENDERABLE_TYPE,EGL_OPENGL_ES2_BIT, - EGL_RED_SIZE,8,EGL_GREEN_SIZE,8,EGL_BLUE_SIZE,8,EGL_ALPHA_SIZE,8,EGL_NONE}; - EGLConfig config;EGLint count;check(eglChooseConfig(display,attrs,&config,1,&count)&&count,"config"); - const EGLint size[]={EGL_WIDTH,width,EGL_HEIGHT,height,EGL_NONE}; - EGLSurface surface=eglCreatePbufferSurface(display,config,size); - const EGLint ctxattrs[]={EGL_CONTEXT_CLIENT_VERSION,2,EGL_NONE}; - EGLContext context=eglCreateContext(display,config,EGL_NO_CONTEXT,ctxattrs); - check(eglMakeCurrent(display,surface,surface,context),"current"); - json_object *settings=json_object_new_object(); - json_object_object_add(settings,"hyperspace_seed",json_object_new_int(seed)); - json_object_object_add(settings,"hyperspace_quality",json_object_new_int(quality)); - json_object_object_add(settings,"hyperspace_galaxies",json_object_new_double(density)); - json_object_object_add(settings,"hyperspace_brightness",json_object_new_double(argc>8 ? atof(argv[8]) : 1)); - json_object_object_add(settings,"hyperspace_distant_stars",json_object_new_double(0)); - ambient_lock_host::parameters=settings; - OpenGL::program_t texture; - ambient_lock_host::texture_program=&texture; - { - ambient_hyperspace_background engine([]{}); - std::array<double,24> state{double(seed),0,0,0,0,drift, - 1,-3,15,.5,.3,0,.2,.1,.65,1,-15,18,.4,.4,.08,0,.3,0}; - check(engine.restore_scene(state),"restore scene"); - wf::auxilliary_buffer_t destination;destination.width=width;destination.height=height; - wf::render_target_t target(destination);target.geometry={0,0,double(width),double(height)}; - float background=0; - glClearColor(background,background,background,1);glClear(GL_COLOR_BUFFER_BIT); - engine.render(target,target.geometry,{width,height},argc>9 ? atof(argv[9]) : 1); - std::vector<unsigned char> pixels(width*height*4); - glReadPixels(0,0,width,height,GL_RGBA,GL_UNSIGNED_BYTE,pixels.data()); - check(glGetError()==GL_NO_ERROR,"render"); - FILE *file=fopen(argv[1],"wb");check(file,"output");fwrite(pixels.data(),pixels.size(),1,file);fclose(file); - fprintf(stderr,"GPU %s; %dx%d\n",glGetString(GL_RENDERER),width,height); - } - json_object_put(settings); - texture.free_resources(); - eglMakeCurrent(display,EGL_NO_SURFACE,EGL_NO_SURFACE,EGL_NO_CONTEXT); - eglDestroySurface(display,surface);eglDestroyContext(display,context);eglTerminate(display); -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/lock-mode.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/lock-mode.py deleted file mode 100755 index 8944056869b1..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/lock-mode.py +++ /dev/null @@ -1,196 +0,0 @@ -"""Animated lock appearance and real test-service PAM in a private GPU session. -No test authentication or synthetic input endpoint is installed in production. -""" -from pathlib import Path -import argparse,json,os,shutil,signal,socket,sys,time -from PIL import Image,ImageChops -p=argparse.ArgumentParser();p.add_argument('--stage',type=Path,required=True) -p.add_argument('--auth-helper',type=Path,required=True);p.add_argument('--binary-dir',type=Path) -a=p.parse_args();assert os.geteuid()!=0 -sys.path.insert(0,str(a.stage/'tests'));from private_session import PrivateSession -extra=''' -[ambient-background] -enabled=true -controller= -hyperspace_quality=0 -hyperspace_speed=.5 -hyperspace_seed=173 -lock_show_clock=false -lock_show_date=false -lock_black_delay=0 -lock_brightness=1 -[output:HEADLESS-1] -mode=320x240@60 -position=0,0 -[output:HEADLESS-2] -mode=320x240@60 -position=320,0 -[output:HEADLESS-3] -mode=320x240@60 -position=640,0 -[output:HEADLESS-4] -position=960,0 -''' -with PrivateSession(a.stage,extra_plugins=' ambient-background session-lock digitalrain-idle digitalrain-cursor digitalrain-power lock-test desktop-test',config_extra=extra) as s: - print('SESSION',s.root,flush=True) - bins=s.root/'bin';bins.mkdir() - for name in ['digitalrain-lock','digitalrain']: - shutil.copy2((a.binary_dir or a.stage)/name,bins/name) - shutil.copy2(a.auth_helper,bins/'digitalrain-auth') - cfg=s.root/'rain.toml';cfg.write_text('[global]\nTIMEOUT=999\nFOCUSED_TIMEOUT=999\nINHIBIT_VIDEO=false\n[matrix]\nBLACK_RAIN_TIMEOUT=0\nPOWER_OFF=false\n') - s.env['AMBIENT_BACKGROUND_CONFIG']=str(cfg) - def call(m,d=None): - r=s.call(m,d);assert not isinstance(r,dict) or not r.get('error'),r;return r - def wait(fn,timeout=8): - end=time.monotonic()+timeout - while time.monotonic()<end: - try: - r=fn() - if r:return r - except (ConnectionError,FileNotFoundError):pass - time.sleep(.04) - raise AssertionError('Timeout '+str(s.root)) - def status(method='status'): - with socket.socket(socket.AF_UNIX,socket.SOCK_SEQPACKET) as stream: - stream.settimeout(4);stream.connect(str(s.root/'digitalrain/lock.sock')) - assert __import__('struct').unpack('3i',stream.getsockopt(socket.SOL_SOCKET,socket.SO_PEERCRED,12))[1]==os.geteuid() - stream.send(json.dumps({'method':method}).encode());return json.loads(stream.recv(16384)) - def settings(**values): - before=status()['theme_revision'];call('wayfire/set-config-options',{'ambient-background/'+k:v for k,v in values.items()}) - wait(lambda:status()['theme_revision']>before);time.sleep(.15) - def shot(name,output='VIRTUAL-1'): - file=s.root/(name+'.png');s.run(['grim','-o',output,str(file)],timeout=8) - return Image.open(file).convert('RGB') - def key(code):call('test/key',{'code':code}) - def password(text): - codes={'r':19,'a':30,'i':23,'n':49,'-':12,'t':20,'e':18,'s':31,'7':8,'x':45} - for c in text:key(codes[c]);time.sleep(.02) - key(28) - def clear_prompt():key(1);wait(lambda:status()['controls_opacity']==0) - call('digitalrain/hide_cursor') - call('test/solid',{'output':'VIRTUAL-1','key':'private','layer':3,'x':0,'y':0,'w':960,'h':240,'r':.8,'g':0.,'b':0.}) - wait(lambda:(204,0,0) in shot('desktop').get_flattened_data()) - original={k:call('wayfire/get-config-option',{'option':'ambient-background/'+k})['value'] for k in ['backend','hyperspace_speed','hyperspace_brightness','hyperspace_color','matrix_color']} - lock=s.spawn('locker',[str(bins/'digitalrain-lock')]);wait(lambda:status()['state']=='locked') - wait(lambda:all(call('test/lock-state').values())) - assert status()['controls_opacity']==0 and not status()['clock_visible'] - assert status()['background']=='hyperspace';time.sleep(.25) - first=shot('hyperspace');assert first.getbbox() and (204,0,0) not in first.get_flattened_data() - time.sleep(.3);assert ImageChops.difference(first,shot('hyperspace-moving')).getbbox() - print('PASS actual hyperspace lock surfaces cover every physical output and move',flush=True) - settings(lock_speed='0');frozen=shot('frozen');time.sleep(.3) - assert not ImageChops.difference(frozen,shot('still-frozen')).getbbox() - settings(lock_brightness='0');assert not shot('dark').getbbox() - settings(lock_brightness='1',lock_speed='1',lock_background='nebula') - assert status()['background']=='nebula';time.sleep(.3) - nebula=shot('nebula-lock');assert nebula.getbbox() and (204,0,0) not in nebula.get_flattened_data() - time.sleep(.3);assert ImageChops.difference(nebula,shot('nebula-lock-moving')).getbbox() - settings(lock_speed='0');frozen_nebula=shot('nebula-lock-frozen');time.sleep(.3) - assert not ImageChops.difference(frozen_nebula,shot('nebula-lock-still')).getbbox() - settings(lock_brightness='0');assert not shot('nebula-lock-dark').getbbox() - print('PASS Nebula lock switching, continuous motion, freeze and brightness; every output stays protected',flush=True) - settings(lock_brightness='1',lock_speed='1',lock_background='digitalrain' ,lock_colour_override='true',lock_colour='0 0 1 1') - assert status()['background']=='digitalrain' - def blue(): - img=shot('blue');totals=[sum(p[c] for p in img.get_flattened_data()) for c in range(3)] - return totals[2]>1.5*max(totals[:2]) and totals[2]>1000 - wait(blue) - assert all(call('wayfire/get-config-option',{'option':'ambient-background/'+k})['value']==v for k,v in original.items()) - print('PASS independent effect, frozen motion, brightness and colour override preserve Ambient preferences',flush=True) - settings(lock_background='black',lock_show_clock='true',lock_show_date='true', - lock_output='HEADLESS-2',lock_ui_hide_delay='.9',lock_ui_fade_duration='.3') - assert not shot('controls-initially-hidden').getbbox() - key(42) - wait(lambda:0<status()['controls_opacity']<1) - partial=shot('controls-appearing') - wait(lambda:status()['controls_opacity']==1) - selected=shot('controls-selected-monitor') - assert not selected.crop((0,0,320,240)).getbbox() - assert selected.crop((320,0,640,240)).getbbox() - assert not selected.crop((640,0,960,240)).getbbox() - assert not shot('other-output-hidden','HEADLESS-4').getbbox() - assert status()['controls_outputs']==['HEADLESS-2'] and status()['clock_visible'] - assert sum(sum(p) for p in partial.get_flattened_data()) < sum(sum(p) for p in selected.get_flattened_data()) - key(45) # An unfinished password must be discarded when controls fade away. - wait(lambda:status()['controls_opacity']==0) - assert not shot('controls-idle-hidden').getbbox() and all(call('test/lock-state').values()) - settings(lock_output='all',lock_ui_hide_delay='0');status('wake') - wait(lambda:status()['controls_opacity']==1) - every=shot('controls-all-monitors') - assert all(every.crop((i*320,0,(i+1)*320,240)).getbbox() for i in range(3)) - assert shot('controls-separate-monitor','HEADLESS-4').getbbox() - settings(lock_output='HEADLESS-4') - assert not shot('controls-away-from-joined-output').getbbox() - assert shot('controls-on-separate-output','HEADLESS-4').getbbox() - call('test/remove-output',{'output':'HEADLESS-4'}) - wait(lambda:status()['controls_outputs']!=['HEADLESS-4']) - assert call('wayfire/get-config-option',{'option':'ambient-background/lock_output'})['value']=='HEADLESS-4' - assert all(call('test/lock-state').values()) - settings(lock_output='DISCONNECTED-MONITOR') - fallback=shot('controls-fallback') - assert fallback.crop((0,0,320,240)).getbbox() - assert not fallback.crop((320,0,960,240)).getbbox() - assert status()['controls_outputs']==['HEADLESS-1'] - clear_prompt();assert not shot('controls-escape-hidden').getbbox() - print('PASS input-only clock/prompt fade, idle password clearing, individual/all physical monitors and disconnected fallback',flush=True) - settings(lock_output='all',lock_ui_on_interaction='false') - wait(lambda:status()['controls_opacity']==1) - settings(lock_background='black',lock_show_clock='true',lock_show_date='true',lock_speed='0') - clock=shot('clock') - assert all(clock.crop((i*320,0,(i+1)*320,240)).getbbox() for i in range(3)) - settings(lock_show_date='false');assert ImageChops.difference(clock,shot('clock-no-date')).getbbox() - settings(lock_show_clock='false');assert not shot('plain-black').getbbox() - print('PASS clock/date appear on all three physical monitors with independent controls',flush=True) - settings(lock_ui_on_interaction='true',lock_background='hyperspace',lock_speed='1',lock_black_delay='1.2',lock_fade_duration='.5') - status('wake');clear_prompt();wait(lambda:0<status()['background_opacity']<.95) - wait(lambda:status()['phase']=='black');assert not shot('faded-black').getbbox() - stat=Path(f'/proc/{lock.pid}/stat').read_text().split();time.sleep(.7) - later=Path(f'/proc/{lock.pid}/stat').read_text().split() - assert sum(map(int,later[13:15]))-sum(map(int,stat[13:15]))<=8 - status('wake');assert all(call('test/output-power').values()) - assert status()['state']=='locked' and all(call('test/lock-state').values()) - settings(lock_black_delay='0');clear_prompt();wait(lambda:status()['background_opacity']>.99) - assert shot('resumed').getbbox() - print('PASS smooth black fade pauses GPU rendering; wake restores animation while remaining locked',flush=True) - password('x');time.sleep(.7);assert lock.poll() is None and status()['state']=='locked' - lock.send_signal(signal.SIGUSR1);assert all(call('test/lock-state').values()) - time.sleep(2);password('rain-test-7');assert lock.wait(8)==0;s.processes.remove(lock) - wait(lambda:not any(call('test/lock-state').values())) - print('PASS bad password and saver signals retain protection; only real PAM success unlocks',flush=True) - lock=s.spawn('locker-crash',[str(bins/'digitalrain-lock')]);wait(lambda:status()['state']=='locked') - lock.kill();lock.wait(4);s.processes.remove(lock);assert all(call('test/lock-state').values()) - lock=s.spawn('locker-recover',[str(bins/'digitalrain-lock')]);wait(lambda:status()['state']=='locked') - call('test/add-output');time.sleep(.4);assert all(call('test/lock-state').values()) - if 'HEADLESS-5' in call('test/output-power'): - call('test/remove-output',{'output':'HEADLESS-5'});time.sleep(.2) - assert all(call('test/lock-state').values()) - password('rain-test-7');assert lock.wait(8)==0;s.processes.remove(lock) - print('PASS renderer crash stays locked, replacement recovers, newly connected output is protected',flush=True) - for path in s.root.glob('locker*.log'): - log=path.read_text();assert 'Ambient lock shader:' not in log and 'framebuffer allocation failed' not in log,log - print('PASS lock-mode integration',flush=True) - -# Power policy gets a fresh compositor, so previous output hot-plug and live -# output-control changes do not alter the fixture's initial monitor topology. -power_session=PrivateSession(a.stage,config_extra=extra+""" -[core] -plugins=ipc stipc ipc-rules ambient-background session-lock digitalrain-idle digitalrain-cursor digitalrain-power lock-test desktop-test -[ambient-background] -lock_black_delay=1 -lock_fade_duration=.2 -""") -power_session.env['WLR_HEADLESS_OUTPUTS']='3' -with power_session as s: - print('POWER SESSION',s.root,flush=True) - bins=s.root/'bin';bins.mkdir() - shutil.copy2((a.binary_dir or a.stage)/'digitalrain-lock',bins/'digitalrain-lock') - shutil.copy2(a.auth_helper,bins/'digitalrain-auth') - cfg=s.root/'rain.toml';cfg.write_text('[global]\nTIMEOUT=999\nINHIBIT_VIDEO=false\n[matrix]\nPOWER_OFF=true\n') - s.env['AMBIENT_BACKGROUND_CONFIG']=str(cfg) - powered=s.spawn('locker-power',[str(bins/'digitalrain-lock')]);wait(lambda:status()['state']=='locked') - physical=[f'HEADLESS-{i}' for i in range(1,4)] - wait(lambda:all(not call('test/output-power')[name] for name in physical)) - status('wake');wait(lambda:all(call('test/output-power')[name] for name in physical)) - assert all(call('test/lock-state').values()) - password('rain-test-7');assert powered.wait(8)==0;s.processes.remove(powered) - print('PASS locked power-off and wake preserve protection and pause animation',flush=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/nebula-native.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/nebula-native.py deleted file mode 100755 index 16e34b1991d3..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/nebula-native.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python3 -"""Private compositor: solid encounters, paused travel, Cube and partial damage.""" -from pathlib import Path -import sys,time -import numpy as np -from PIL import Image -stage=Path(sys.argv[1]).resolve();sys.path.insert(0,str(stage/'tests')) -from private_session import PrivateSession -extra=''' -[core] -vwidth=4 -vheight=3 -[cube] -render_at_rest=true -mouse_overview=true -[ambient-background] -enabled=true -controller= -focused_delay=86400 -inactive_delay=86400 -hyperspace_speed=0 -hyperspace_seed=129 -hyperspace_cloud_density=0 -hyperspace_star_density=0 -hyperspace_distant_stars=0 -hyperspace_dust_density=0 -hyperspace_glow=0 -hyperspace_pulsars=0 -hyperspace_anomalies=0 -hyperspace_galaxies=1.5 -backend=nebula -nebula_speed=0 -nebula_seed=129 -nebula_cloud_density=.85 -nebula_star_density=1 -hyperspace_distant_drift=1 -hyperspace_asteroids=0 -''' -with PrivateSession(stage,hotplug=True,extra_plugins=' ambient-background cube render-test',config_extra=extra) as s: - print('SESSION',s.root,flush=True) - def call(method,data=None): - r=s.call(method,data);assert not isinstance(r,dict) or not r.get('error'),r;return r - def snapshot(name): - call('ambient/sync',{'outputs':[{'name':'HEADLESS-'+str(i),'opacity':0,'black':0,'effect':1} for i in range(1,5)]}) - time.sleep(.08);p=s.root/(name+'.png');s.run(['grim','-o','VIRTUAL-1',str(p)]) - return np.asarray(Image.open(p).convert('RGB')) - def settings(**options): - call('wayfire/set-config-options',{'ambient-background/'+k:v for k,v in options.items()});time.sleep(.12) - s.wait(lambda:len(call('ambient/status')['outputs'])==4) - call('stipc/move_cursor',{'x':1930,'y':10}) - first=snapshot('frozen-galaxies');assert first.max()>20 - time.sleep(.25);assert np.array_equal(first,snapshot('still-frozen')) - settings(nebula_cloud_density=0,nebula_star_density=0);assert snapshot('disabled').max()==0 - settings(nebula_cloud_density=.85,nebula_star_density=1);assert np.array_equal(first,snapshot('restored')) - settings(nebula_speed=1.5);time.sleep(.4) - moved=snapshot('approaching');assert not np.array_equal(first,moved) - settings(nebula_speed=0) - frozen=snapshot('stopped');time.sleep(.25);assert np.array_equal(frozen,snapshot('still-stopped')) - assert call('test/partial',{'output':'VIRTUAL-1'})['outside']==0 - call('test/cube',{'output':'VIRTUAL-1','angle':.7,'zoom':.55});time.sleep(.25) - assert snapshot('cube-with-galaxies').max()>20 - assert call('test/partial',{'output':'VIRTUAL-1'})['outside']==0 - call('test/cube',{'output':'VIRTUAL-1','stop':True}) - log=(s.root/'wayfire.log').read_text(errors='replace') - assert 'GL_INVALID' not in log and 'GL_OUT_OF_MEMORY' not in log and 'Shader compilation failed' not in log - print('PASS native Nebula motion/freeze, stars/clouds disable/restore, shared Cube rendering and clipped partial damage',flush=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/nebula-reload.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/nebula-reload.py deleted file mode 100755 index 153a4ab7576b..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/nebula-reload.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -"""Reload after a globally loaded old renderer; match a fresh candidate frame.""" -from pathlib import Path -import subprocess, sys, time -import numpy as np -from PIL import Image - -stage = Path(sys.argv[1]).resolve() -sys.path.insert(0, str(stage/'tests')) -from private_session import PrivateSession - -candidate = stage/'plugins/libambient-background.so' -old = stage/'plugins/old-r15/libambient-background.so' -exports = subprocess.check_output(['nm', '-D', '-C', str(candidate)], text=True) -assert 'ambient_nebula_background' not in exports, 'Renderer internals are interposable' -config = ''' -[cube] -render_at_rest=true -[ambient-background] -enabled=true -controller= -focused_delay=86400 -inactive_delay=86400 -hyperspace_speed=0 -hyperspace_seed=129 -hyperspace_cloud_density=0 -hyperspace_star_density=0 -hyperspace_distant_stars=0 -hyperspace_dust_density=0 -hyperspace_glow=0 -hyperspace_pulsars=0 -hyperspace_anomalies=0 -hyperspace_galaxies=1.5 -backend=nebula -nebula_speed=0 -nebula_seed=129 -nebula_cloud_density=.85 -nebula_star_density=1 -hyperspace_distant_drift=1 -hyperspace_asteroids=0 -''' - -def capture(s, name): - s.call('ambient/sync', {'outputs': [ - {'name': 'HEADLESS-'+str(i), 'opacity': 0, 'black': 0, 'effect': 1} - for i in range(1, 5)]}) - s.wait(lambda: all(z['opacity'] == 0 for z in s.call('ambient/status')['outputs'])) - - time.sleep(.08) - p = s.root/(name+'.png') - s.run(['grim', '-o', 'VIRTUAL-1', str(p)]) - return np.asarray(Image.open(p).convert('RGB')) - -def checked(s, method, data=None): - r = s.call(method, data) - assert not isinstance(r, dict) or not r.get('error'), r - return r - -with PrivateSession(stage, hotplug=True, extra_plugins=' ambient-background cube render-test', - config_extra=config) as s: - print('FRESH', s.root, flush=True) - checked(s, 'stipc/move_cursor', {'x': 1930, 'y': 10}) - expected = capture(s, 'fresh-renderer') - assert expected.max() > 20 and expected.min() >= 0 - checked(s, 'test/cube', {'output': 'VIRTUAL-1', 'angle': .7, 'zoom': .55}) - expected_cube = capture(s, 'fresh-cube') - -with PrivateSession(stage, hotplug=True, extra_plugins=f' {old} cube render-test', - config_extra=config) as s: - print('RELOADED', s.root, flush=True) - checked(s, 'stipc/move_cursor', {'x': 1930, 'y': 10}) - previous = capture(s, 'old-renderer') - assert not np.array_equal(previous, expected), 'Fixture does not distinguish old/new rendering' - plugins = checked(s, 'wayfire/get-config-option', {'option': 'core/plugins'})['value'] - checked(s, 'wayfire/set-config-options', {'core/plugins': plugins.replace(str(old), str(candidate))}) - checked(s, 'wayfire/reload-plugins') - actual = capture(s, 'reloaded-renderer') - assert np.array_equal(actual, expected), 'Reload still renders using the resident old version' - checked(s, 'test/cube', {'output': 'VIRTUAL-1', 'angle': .7, 'zoom': .55}) - actual_cube = capture(s, 'reloaded-cube') - assert np.array_equal(actual_cube, expected_cube), 'Cube reload uses a different renderer' - checked(s, 'test/cube', {'output': 'VIRTUAL-1', 'stop': True}) - checked(s, 'wayfire/set-config-options', {'ambient-background/nebula_speed': 2.0}) - time.sleep(.15) - moved = capture(s, 'fast-pass') - assert not np.array_equal(moved, actual), 'Reloaded motion is frozen' - log = (s.root/'wayfire.log').read_text(errors='replace') - assert 'GL_INVALID' not in log and 'Shader compilation failed' not in log -print('PASS r15-to-r16 native reload matches fresh Nebula rendering, including Cube delegation', flush=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/nebula-render.cpp b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/nebula-render.cpp deleted file mode 100644 index 039abaacb99c..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/nebula-render.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// Private, deterministic EGL fixture for the shared compositor/lock renderer. -#include "hyperspace-background.hpp" -#include "../effects/nebula/nebula-background.hpp" -#include "hyperspace-lock.h" -#include <EGL/egl.h> -#include <cstdio> -#include <cstdlib> -#include <vector> -static void check(bool ok,const char *what) { - if (!ok) { fprintf(stderr,"%s EGL=%x GL=%x\n",what,eglGetError(),glGetError());std::exit(1); } -} -int main(int argc,char **argv) { - check(argc>=7,"args output settings-json width height opacity background"); - int width=atoi(argv[3]),height=atoi(argv[4]); - check(eglBindAPI(EGL_OPENGL_ES_API),"bind API"); - EGLDisplay display=eglGetDisplay(EGL_DEFAULT_DISPLAY);check(eglInitialize(display,nullptr,nullptr),"display"); - const EGLint attrs[]={EGL_SURFACE_TYPE,EGL_PBUFFER_BIT,EGL_RENDERABLE_TYPE,EGL_OPENGL_ES2_BIT, - EGL_RED_SIZE,8,EGL_GREEN_SIZE,8,EGL_BLUE_SIZE,8,EGL_ALPHA_SIZE,8,EGL_NONE}; - EGLConfig config;EGLint count;check(eglChooseConfig(display,attrs,&config,1,&count)&&count,"config"); - const EGLint size[]={EGL_WIDTH,width,EGL_HEIGHT,height,EGL_NONE}; - EGLSurface surface=eglCreatePbufferSurface(display,config,size); - const EGLint ctxattrs[]={EGL_CONTEXT_CLIENT_VERSION,2,EGL_NONE}; - EGLContext context=eglCreateContext(display,config,EGL_NO_CONTEXT,ctxattrs); - check(eglMakeCurrent(display,surface,surface,context),"current"); - json_object *settings=json_object_from_file(argv[2]);check(settings,"settings"); - ambient_lock_host::parameters=settings; - OpenGL::program_t texture;ambient_lock_host::texture_program=&texture; - { - wf::auxilliary_buffer_t destination;destination.width=width;destination.height=height; - wf::render_target_t target(destination);target.geometry={0,0,double(width),double(height)}; - float background=atof(argv[6]);glClearColor(background,background,background,1);glClear(GL_COLOR_BUFFER_BIT); - json_object *backend=nullptr,*scene=nullptr; - json_object_object_get_ex(settings,"_backend",&backend);json_object_object_get_ex(settings,"_scene",&scene); - if (backend && std::string(json_object_get_string(backend))=="nebula") { - ambient_nebula_background engine([]{}); - check(engine.restore_scene({json_object_get_double(json_object_array_get_idx(scene,0)),json_object_get_double(json_object_array_get_idx(scene,1))}),"nebula scene"); - engine.render(target,target.geometry,{width,height},atof(argv[5])); - } else { - ambient_hyperspace_background engine([]{});std::array<double,24> state; - for (unsigned i=0;i<state.size();i++) state[i]=json_object_get_double(json_object_array_get_idx(scene,i)); - check(engine.restore_scene(state),"hyperspace scene");engine.render(target,target.geometry,{width,height},atof(argv[5])); - } - std::vector<unsigned char> pixels(width*height*4);glReadPixels(0,0,width,height,GL_RGBA,GL_UNSIGNED_BYTE,pixels.data()); - check(glGetError()==GL_NO_ERROR,"render");FILE *file=fopen(argv[1],"wb");check(file,"output");fwrite(pixels.data(),pixels.size(),1,file);fclose(file); - fprintf(stderr,"GPU %s; %dx%d\n",glGetString(GL_RENDERER),width,height); - } - json_object_put(settings); - texture.free_resources(); - eglMakeCurrent(display,EGL_NO_SURFACE,EGL_NO_SURFACE,EGL_NO_CONTEXT); - eglDestroySurface(display,surface);eglDestroyContext(display,context);eglTerminate(display); -} diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/nebula.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/nebula.py deleted file mode 100755 index 21418595f0b7..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/nebula.py +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env python3 -"""Private actual-GPU checks for Nebula and reused Hyperspace cloud detail.""" -from pathlib import Path -import argparse,json,os,subprocess,tempfile,xml.etree.ElementTree as ET -import numpy as np -from PIL import Image -p=argparse.ArgumentParser();p.add_argument('--stage',type=Path,required=True);a=p.parse_args();assert os.geteuid()!=0 -root=Path(tempfile.mkdtemp(prefix='ambient-nebula-')) -metadata=ET.parse(a.stage/'metadata/ambient-background.xml') -def defaults(): - result={} - for o in metadata.findall('.//option'): - value=o.findtext('default');kind=o.get('type') - if value is not None and (o.get('name').startswith('nebula_') or o.get('name').startswith('hyperspace_')): - result[o.get('name')]=float(value) if kind=='double' else int(value) if kind=='int' else value - return result -def render(name,backend='nebula',phase=0,seed=173,width=960,height=540,opacity=1,background=0,**options): - settings=defaults();settings.update(options);settings['_backend']=backend;settings[backend+'_seed']=seed - settings['_scene']=[seed,phase] if backend=='nebula' else [seed,0,0,0,phase,0,1,-3,15,.5,.3,0,.2,.1,.65,1,-15,18,.4,.4,.08,0,.3,0] - cfg=root/(name+'.json');cfg.write_text(json.dumps(settings));out=root/(name+'.rgba') - subprocess.run([str(a.stage/'nebula-render'),str(out),str(cfg),str(width),str(height),str(opacity),str(background)], - env={'PATH':'/usr/bin:/bin','EGL_PLATFORM':'surfaceless','HOME':str(root)},check=True,capture_output=True) - frame=np.frombuffer(out.read_bytes(),dtype=np.uint8).reshape(height,width,4)[::-1].copy();out.unlink() - Image.fromarray(frame[:,:,:3]).save(root/(name+'.png'));return frame[:,:,:3] -first=render('nebula');assert first.max()>25 and (first.max(axis=2)>10).sum()>1000 -assert np.array_equal(first,render('repeatable')) -assert not np.array_equal(first,render('different-seed',seed=27182)) -assert not np.array_equal(first,render('moving',phase=.03)) -assert not render('no-light',nebula_cloud_density=0,nebula_star_density=0).any() -assert not render('no-brightness',nebula_brightness=0).any() -half=render('half',opacity=.5);assert np.abs(half.astype(float)-first/2).max()<=1.5 -wallpaper=render('transparent',background=.15,nebula_cloud_density=0,nebula_star_density=0) -assert wallpaper.min()>=37 and wallpaper.max()<=39,'No-background shader erases the backdrop' -assert not np.array_equal(first,render('no-stars',nebula_star_density=0)) -assert not np.array_equal(first,render('no-clouds',nebula_cloud_density=0)) -for quality in range(3):assert render('detail-'+str(quality),nebula_quality=quality).max()>20 -for w,h in [(1920,360),(360,640)]: - f=render(f'geometry-{w}-{h}',width=w,height=h) - for segment in np.array_split(f,3,axis=1):assert (segment.max(axis=2)>8).sum()>100 -wrap=render('wrap',phase=2*np.pi-1e-6);assert np.abs(wrap.astype(int)-first.astype(int)).max()<5 -isolated=dict(hyperspace_star_density=0,hyperspace_distant_stars=0,hyperspace_galaxies=0,hyperspace_dust_density=0,hyperspace_pulsars=0,hyperspace_anomalies=0,hyperspace_asteroids=0,hyperspace_cloud_density=1.5,hyperspace_quality=2) -base=render('hyperspace-original',backend='hyperspace',**isolated) -folded=render('hyperspace-fractal',backend='hyperspace',hyperspace_fractal_detail=.65,**isolated) -assert base.max()>10 and folded.max()>10 and not np.array_equal(base,folded) -assert np.array_equal(folded,render('hyperspace-fixed',backend='hyperspace',hyperspace_fractal_detail=.65,**isolated)) -print('PASS seeded Nebula, independent stars/clouds, brightness, opacity, transparent backdrop, detail, panoramic/portrait coverage, motion and clock wrap; reused Hyperspace cloud detail',flush=True) -print('ARTIFACTS',root,flush=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/output-fade.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/output-fade.py deleted file mode 100755 index 3fe3d548562d..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/output-fade.py +++ /dev/null @@ -1,114 +0,0 @@ -"""Final desktop composition must fade per monitor, including every Cube row. - -Only private synthetic desktop elements and private application windows are read. -""" -from pathlib import Path -import sys -import time -from PIL import Image - -stage = Path(sys.argv[1]).resolve() -sys.path.insert(0, str(stage / 'tests')) -from private_session import PrivateSession - -extra = ''' -[core] -vwidth=4 -vheight=3 -[cube] -render_at_rest=true -mouse_overview=true -rotate_layer_shell=* -[ambient-background] -enabled=true -controller= -restore_duration=.05 -manual_duration=.1 -hyperspace_speed=0 -hyperspace_star_density=0 -hyperspace_distant_stars=0 -hyperspace_cloud_density=0 -hyperspace_glow=0 -hyperspace_pulsars=0 -hyperspace_anomalies=0 -hyperspace_asteroids=0 -hyperspace_dust_density=0 -''' -with PrivateSession(stage, hotplug=True, extra_plugins=' ambient-background cube render-test', config_extra=extra) as s: - print('SESSION', s.root, flush=True) - def call(method, data=None): - result = s.call(method, data) - assert not isinstance(result, dict) or not result.get('error'), result - return result - def snap(name): - path = s.root / (name + '.png') - s.run(['grim', '-o', 'VIRTUAL-1', str(path)]) - return Image.open(path).convert('RGB') - def sync(alpha): - call('ambient/sync', {'outputs': [{'name': 'HEADLESS-' + str(i), 'opacity': a, 'black': 0, 'effect': 1} - for i, a in enumerate(alpha, 1)]}) - time.sleep(.18) - def coloured(image, left, right): - return sum(max(p) > 25 for p in image.crop((left, 0, right, image.height)).get_flattened_data()) - def application_geometry(): - view = next(v for v in call('window-rules/list-views') if v.get('app-id') == 'ambient-output-test') - output = next(o for o in call('window-rules/list-outputs') if o['name'] == 'VIRTUAL-1') - geometry = view['geometry'].copy() - # Cube's lock inhibition ends its preview and selects the visible face. - # Compare the application's absolute workspace position, not the - # coordinates relative to a potentially different current workspace. - geometry['x'] += output['workspace']['x'] * 1920 - geometry['y'] += output['workspace']['y'] * 720 - return geometry - s.wait(lambda: len(call('ambient/status')['outputs']) == 4) - call('stipc/move_cursor', {'x': 1930, 'y': 10}) - for layer in [1, 3, 4, 5]: - call('test/solid', {'key': 'layer-' + str(layer), 'output': 'VIRTUAL-1', 'layer': layer, - 'x': 0, 'y': (layer - 1) * 25, 'w': 1920, 'h': 20, 'r': 0, 'g': 1, 'b': 1, 'a': 1}) - s.spawn('app', ['alacritty', '--config-file', '/dev/null', '--class', 'ambient-output-test', - '-o', 'colors.primary.background="#D76F2E"', '-e', 'sleep', '180']) - view = s.wait(lambda: next((v for v in call('window-rules/list-views') - if v.get('app-id') == 'ambient-output-test' and v.get('mapped')), None)) - call('window-rules/set-window-scope', {'id': view['id'], 'scope': 'virtual'}) - call('window-rules/configure-view', {'id': view['id'], 'tiled-edges': 0, - 'geometry': {'x': 100, 'y': 280, 'width': 1600, 'height': 300}}) - time.sleep(.3) - original = application_geometry() - for angle in [0, .65, 1.57, 3.14, 4.71]: - call('test/cube', {'output': 'VIRTUAL-1', 'angle': angle, 'zoom': .45}) - sync([1, 1, 1, 1]) - normal = snap('normal-' + str(angle)) - assert coloured(normal, 0, 640) > 100, ('Fixture has no Cube content on first monitor', angle) - sync([0, 1, 1, 1]) - faded = snap('faded-' + str(angle)) - leak = coloured(faded, 0, 640) - assert leak == 0, ('Unfaded Cube faces or rows leaked onto idle monitor', angle, leak) - assert faded.crop((640, 0, 1920, 720)).tobytes() == normal.crop((640, 0, 1920, 720)).tobytes(), 'Active monitors changed' - sync([.5, 1, 1, 1]) - half = snap('half-' + str(angle)) - samples = [(x, y) for y in range(0, 720, 9) for x in range(0, 640, 9) if max(normal.getpixel((x, y))) > 25] - assert samples - assert max(abs(a / 2 - b) for xy in samples for a, b in zip(normal.getpixel(xy), half.getpixel(xy))) <= 3 - print('PASS output mask covers front, side, rear and stacked Cube rows; active monitors and 50% fade preserved', flush=True) - assert application_geometry() == original, (original, application_geometry()) - print('PASS output fading preserves application geometry', flush=True) - sync([0, 0, 0, 1]) - call('wayfire/set-config-options', {'ambient-background/hyperspace_speed': 1.5, - 'ambient-background/hyperspace_asteroids': 1, - 'ambient-background/hyperspace_star_density': 1}) - time.sleep(.3) - assert coloured(snap('live-wallpaper'), 0, 1920) > 100, 'Fade hid the live wallpaper' - assert call('test/partial', {'output': 'VIRTUAL-1'})['outside'] == 0 - print('PASS live wallpaper remains visible and partial output damage stays clipped', flush=True) - call('test/inhibit', {'inhibit': True}) - time.sleep(.15) - assert coloured(snap('inhibited'), 0, 640) > 100, 'Lock inhibition did not restore desktop' - call('test/inhibit', {'inhibit': False}) - print('PASS lock inhibition bypasses the ambient cover', flush=True) - for layer in [6, 7]: - call('test/solid', {'key': 'protected', 'output': 'VIRTUAL-1', 'layer': layer, - 'x': 100, 'y': 200, 'w': 120, 'h': 80, 'r': 1, 'g': 0, 'b': 0, 'a': 1}) - sync([0, 0, 0, 1]) - assert snap('protected-' + str(layer)).getpixel((150, 240)) == (255, 0, 0), 'Ambient cover obscured a protected layer' - call('test/solid', {'key': 'protected', 'close': True}) - print('PASS screensaver and lock layers remain above output fading', flush=True) diff --git a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/stars-frustum.py b/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/stars-frustum.py deleted file mode 100755 index 645d5c7b0e1e..000000000000 --- a/gui-apps/ambient-background/files/ambient-background-1.0.0-r15/tests/stars-frustum.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python3 -"""GPU regression: travelling stars occupy the full panoramic view.""" -from pathlib import Path -import os,sys,time -import numpy as np -from PIL import Image -stage=Path(sys.argv[1]).resolve();sys.path.insert(0,str(stage/'tests')) -from private_session import PrivateSession -extra=""" -[cube] -render_at_rest=true -[ambient-background] -enabled=true -controller= -focused_delay=86400 -inactive_delay=86400 -hyperspace_speed=0 -hyperspace_seed=31415 -hyperspace_quality=2 -hyperspace_cloud_density=0 -hyperspace_star_density=1 -hyperspace_distant_stars=0 -hyperspace_dust_density=0 -hyperspace_glow=0 -hyperspace_pulsars=0 -hyperspace_anomalies=0 -hyperspace_brightness=1 -""" -module=os.environ.get('AMBIENT_MODULE','ambient-background') -with PrivateSession(stage,hotplug=True,extra_plugins=' cube '+module+' render-test',config_extra=extra) as s: - print('SESSION',s.root,flush=True) - s.wait(lambda:len(s.call('ambient/status')['outputs'])==4) - s.call('stipc/move_cursor',{'x':1930,'y':10});time.sleep(.25) - def snapshot(name): - p=s.root/(name+'.png');s.run(['grim','-o','VIRTUAL-1',str(p)]) - return np.asarray(Image.open(p).convert('RGB')) - still=snapshot('full-frustum') - assert still.shape==(720,1920,3) - mask=still.max(axis=2)>12 - outer=np.concatenate([mask[:,0:384],mask[:,1536:1920]],axis=1).mean() - centre=mask[:,768:1152].mean() - ratio=outer/max(centre,1e-9) - assert outer>.0001 and ratio>.65, ('Travelling stars collapsed into a central cone',float(outer),float(centre),float(ratio)) - time.sleep(.2);assert np.array_equal(still,snapshot('frozen')) - s.call('wayfire/set-config-options',{'ambient-background/hyperspace_speed':1.5}) - time.sleep(.3);moving=snapshot('moving');time.sleep(.3) - assert not np.array_equal(moving,snapshot('moving-next')) - assert s.call('test/partial',{'output':'VIRTUAL-1'})['outside']==0 - print('PASS full-frustum travelling stars and depth bridge; outer/central density',round(float(ratio),3),'; freeze, animation and damage',flush=True) diff --git a/gui-apps/wayfire-3d-windows/wayfire-3d-windows-0.1.0-r19.ebuild b/gui-apps/wayfire-3d-windows/wayfire-3d-windows-0.1.0-r19.ebuild deleted file mode 100644 index a3c482bc83e8..000000000000 --- a/gui-apps/wayfire-3d-windows/wayfire-3d-windows-0.1.0-r19.ebuild +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2026 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 -EAPI=8 -inherit toolchain-funcs -DESCRIPTION="Physical window depth with Cube and Expo rendering handoff" -HOMEPAGE="https://github.com/okcomput3/wayfire-v-kube" -LICENSE="MIT" -SLOT="0" -KEYWORDS="~amd64" -DEPEND=">=gui-wm/wayfire-0.11.0-r64:0= media-libs/glm dev-libs/json-c:=" -RDEPEND="${DEPEND}" -BDEPEND="virtual/pkgconfig" -src_unpack() { - mkdir -p "${S}" || die - cp -a "${FILESDIR}/${PF}/." "${S}/" || die -} -src_compile() { - emake CXX="$(tc-getCXX)" CPPFLAGS="${CPPFLAGS}" CXXFLAGS="${CXXFLAGS}" LDFLAGS="${LDFLAGS}" -} -src_install() { - emake CXX="$(tc-getCXX)" CPPFLAGS="${CPPFLAGS}" CXXFLAGS="${CXXFLAGS}" LDFLAGS="${LDFLAGS}" DESTDIR="${D}" PREFIX="${EPREFIX}/usr" install - dodoc README.md UPSTREAM LICENSE.upstream -} -pkg_postinst() { - elog "WCM → Effects → 3D Windows controls depth modes, shell surfaces and Float cushion." - elog "The packaged companion Cube uses the existing Cube settings and Ambient interface." - elog "Use 3d-windows-r17/lib3d-windows.so; existing companion Cube paths remain compatible." - elog "Use switcher-3d-windows-r12/libswitcher.so to keep Alt+Tab on Ambient." - elog "Use cube-3d-windows-r19/libcube.so for visible Expo previews and stable Cube focus." - elog "Monitor power loss and restitching skip outputs still under construction." - elog "Child dialogs and nested popups retain native stacking above their parents." - elog "Raised and overlapping windows settle continuously after Cube animation." - elog "Super+T keeps raised windows captured in native stacking order." - elog "Previous r15/r14/r13/r12 module paths remain available during the configuration handoff." -} diff --git a/gui-apps/wf-kill/wf-kill-9999-r1.ebuild b/gui-apps/wf-kill/wf-kill-9999-r1.ebuild deleted file mode 100644 index 8919d016b186..000000000000 --- a/gui-apps/wf-kill/wf-kill-9999-r1.ebuild +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright 2019-2026 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -EAPI=8 - -inherit git-r3 meson - -DESCRIPTION="A simple wayfire plugin and program to get information from wayfire." -HOMEPAGE="https://github.com/soreau/wf-kill" -EGIT_REPO_URI="https://github.com/soreau/wf-kill.git" -# Pinned so the downstream patches stay reproducible. -EGIT_COMMIT="361958b0d916510e2c07562dd56ab9d77c40092a" - -LICENSE="MIT" -SLOT="0" -KEYWORDS="" - -DEPEND=" - dev-libs/wayland - dev-libs/wayland-protocols - gui-libs/wf-config:= - gui-libs/wlroots:0.20= - gui-wm/wayfire:= -" -RDEPEND="${DEPEND}" -BDEPEND=" - dev-util/wayland-scanner - virtual/pkgconfig -" - -PATCHES=( - "${FILESDIR}/killtopview.patch" - "${FILESDIR}/${PN}-grab-safety.patch" -) - -src_install() { - meson_src_install - exeinto "/usr/$(get_libdir)/wayfire/wf-kill-r1" - doexe "${BUILD_DIR}/src/libwf-kill.so" -} diff --git a/gui-libs/pixdecor/pixdecor-9999-r2.ebuild b/gui-libs/pixdecor/pixdecor-9999-r2.ebuild deleted file mode 100644 index 0cc816803cbd..000000000000 --- a/gui-libs/pixdecor/pixdecor-9999-r2.ebuild +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2026 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -EAPI=8 - -inherit git-r3 meson - -DESCRIPTION="Wayfire decorator with rounded corners, shadows, and animated effects" -HOMEPAGE="https://github.com/soreau/pixdecor" -EGIT_REPO_URI="https://github.com/soreau/pixdecor.git" -EGIT_BRANCH="master" -# Keep the downstream patch reproducible until it is promoted to srcux. -EGIT_COMMIT="222e61d971b045a821cf212a6e221367c05d966b" - -PATCHES=( "${FILESDIR}/${PN}-sculpted-frames.patch" "${FILESDIR}/${PN}-shell-theme.patch" ) - -LICENSE="MIT" -SLOT="0" -# Upstream has no test suite. -RESTRICT="test" - -RDEPEND=" - dev-libs/glib:2 - dev-libs/wayland - gui-libs/wf-config:= - gui-libs/wlroots:0.20= - >=gui-wm/wayfire-0.11.0:=[gles3] - media-libs/libglvnd - x11-libs/cairo - x11-libs/pango - x11-libs/pixman -" -DEPEND=" - ${RDEPEND} - media-libs/glm - sys-kernel/linux-headers -" -BDEPEND=" - sys-devel/gettext - virtual/pkgconfig -" - -src_install() { - meson_src_install - exeinto "/usr/$(get_libdir)/wayfire/pixdecor-shell-r2" - doexe "${BUILD_DIR}/src/libpixdecor.so" - dodoc README.md -} - -pkg_postinst() { - elog "Add pixdecor to the plugins list in the [core] section of wayfire.ini." - elog "Disable other decoration plugins; Pixdecor owns window frames." - elog "Frame settings are in WCM -> Pix Decoration -> Window frames." - elog "Sculpted adds border depth and pinstripe color/opacity." -} diff --git a/gui-libs/pixdecor/pixdecor-9999-r3.ebuild b/gui-libs/pixdecor/pixdecor-9999-r3.ebuild deleted file mode 100644 index 661ab61dca01..000000000000 --- a/gui-libs/pixdecor/pixdecor-9999-r3.ebuild +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2026 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -EAPI=8 - -inherit git-r3 meson - -DESCRIPTION="Wayfire decorator with rounded corners, shadows, and animated effects" -HOMEPAGE="https://github.com/soreau/pixdecor" -EGIT_REPO_URI="https://github.com/soreau/pixdecor.git" -EGIT_BRANCH="master" -# Keep the downstream patch reproducible until it is promoted to srcux. -EGIT_COMMIT="222e61d971b045a821cf212a6e221367c05d966b" - -PATCHES=( - "${FILESDIR}/${PN}-sculpted-frames.patch" - "${FILESDIR}/${PN}-shell-theme.patch" - "${FILESDIR}/${PN}-shade-input.patch" -) - -LICENSE="MIT" -SLOT="0" -# Upstream has no test suite. -RESTRICT="test" - -RDEPEND=" - dev-libs/glib:2 - dev-libs/wayland - gui-libs/wf-config:= - gui-libs/wlroots:0.20= - >=gui-wm/wayfire-0.11.0:=[gles3] - media-libs/libglvnd - x11-libs/cairo - x11-libs/pango - x11-libs/pixman -" -DEPEND=" - ${RDEPEND} - media-libs/glm - sys-kernel/linux-headers -" -BDEPEND=" - sys-devel/gettext - virtual/pkgconfig -" - -src_install() { - meson_src_install - exeinto "/usr/$(get_libdir)/wayfire/pixdecor-shell-r3" - doexe "${BUILD_DIR}/src/libpixdecor.so" - dodoc README.md -} - -pkg_postinst() { - elog "Add pixdecor to the plugins list in the [core] section of wayfire.ini." - elog "Disable other decoration plugins; Pixdecor owns window frames." - elog "Frame settings are in WCM -> Pix Decoration -> Window frames." - elog "Sculpted adds border depth and pinstripe color/opacity." -} diff --git a/gui-libs/pixdecor/pixdecor-9999-r4.ebuild b/gui-libs/pixdecor/pixdecor-9999-r4.ebuild deleted file mode 100644 index 460d2850c094..000000000000 --- a/gui-libs/pixdecor/pixdecor-9999-r4.ebuild +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright 2026 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -EAPI=8 - -inherit git-r3 meson - -DESCRIPTION="Wayfire decorator with rounded corners, shadows, and animated effects" -HOMEPAGE="https://github.com/soreau/pixdecor" -EGIT_REPO_URI="https://github.com/soreau/pixdecor.git" -EGIT_BRANCH="master" -# Keep the downstream patches reproducible until they are promoted upstream. -EGIT_COMMIT="222e61d971b045a821cf212a6e221367c05d966b" - -PATCHES=( - "${FILESDIR}/${PN}-sculpted-frames.patch" - "${FILESDIR}/${PN}-shell-theme.patch" - "${FILESDIR}/${PN}-shade-input.patch" - "${FILESDIR}/${PN}-render-hook-tracking.patch" - "${FILESDIR}/${PN}-shade-deco-lifetime.patch" - "${FILESDIR}/${PN}-shade-output-lifetime.patch" - "${FILESDIR}/${PN}-theme-ipc-validate.patch" -) - -LICENSE="MIT" -SLOT="0" -# Upstream has no test suite. -RESTRICT="test" - -RDEPEND=" - dev-libs/glib:2 - dev-libs/wayland - gui-libs/wf-config:= - gui-libs/wlroots:0.20= - >=gui-wm/wayfire-0.11.0:=[gles3] - media-libs/libglvnd - x11-libs/cairo - x11-libs/pango - x11-libs/pixman -" -DEPEND=" - ${RDEPEND} - media-libs/glm - sys-kernel/linux-headers -" -BDEPEND=" - sys-devel/gettext - virtual/pkgconfig -" - -src_install() { - meson_src_install - exeinto "/usr/$(get_libdir)/wayfire/pixdecor-shell-r4" - doexe "${BUILD_DIR}/src/libpixdecor.so" - dodoc README.md -} - -pkg_postinst() { - elog "Add pixdecor to the plugins list in the [core] section of wayfire.ini." - elog "Disable other decoration plugins; Pixdecor owns window frames." - elog "Frame settings are in WCM -> Pix Decoration -> Window frames." - elog "Sculpted adds border depth and pinstripe color/opacity." - elog "This revision installs to pixdecor-shell-r4; point wayfire.ini at it." -} diff --git a/gui-libs/pixdecor/pixdecor-9999.ebuild b/gui-libs/pixdecor/pixdecor-9999.ebuild deleted file mode 100644 index 98475e78e2f8..000000000000 --- a/gui-libs/pixdecor/pixdecor-9999.ebuild +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2026 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -EAPI=8 - -inherit git-r3 meson - -DESCRIPTION="Wayfire decorator with rounded corners, shadows, and animated effects" -HOMEPAGE="https://github.com/soreau/pixdecor" -EGIT_REPO_URI="https://github.com/soreau/pixdecor.git" -EGIT_BRANCH="master" - -LICENSE="MIT" -SLOT="0" -# Upstream has no test suite. -RESTRICT="test" - -RDEPEND=" - dev-libs/glib:2 - dev-libs/wayland - gui-libs/wf-config:= - gui-libs/wlroots:0.20= - >=gui-wm/wayfire-0.11.0:=[gles3] - media-libs/libglvnd - x11-libs/cairo - x11-libs/pango - x11-libs/pixman -" -DEPEND=" - ${RDEPEND} - media-libs/glm - sys-kernel/linux-headers -" -BDEPEND=" - sys-devel/gettext - virtual/pkgconfig -" - -pkg_postinst() { - elog "Add pixdecor to the plugins list in the [core] section of wayfire.ini." - elog "Disable other decoration plugins, then restart Wayfire." -} diff --git a/gui-libs/wayfire-plugins-custom/Manifest b/gui-libs/wayfire-plugins-custom/Manifest index 6693168a2c04..e6cf884d6ca1 100644 --- a/gui-libs/wayfire-plugins-custom/Manifest +++ b/gui-libs/wayfire-plugins-custom/Manifest @@ -1,2 +1 @@ DIST wayfire-plugins-custom-0.1.0-r7.tar.xz 68248 BLAKE2B a317b72c7144bc47f719b0b498dbf5afc2317d078361188d4d8fe09deb5973bfbb0b789014de1609c9c75f277ca8dee416e4bbf82a840dab019f304e5c9e1869 SHA512 0341d13241132eeb5ef07df804693e739b8af566212b81c1c9f90d1a08c4c1b3788886b1b40ef84a18e3dfeb67482e284b22893c5414359373755dafd04980b8 -DIST wayfire-plugins-custom-0.1.0.tar.xz 49616 BLAKE2B 95ded516c8e7070eb1c712e4edeb43b0ff0645d18f72e568d57c2c4f9ef3817d6d301f0f2665e7600865dfe8ce4c3ace3255ca0b71bbf5540884f684ebdd0313 SHA512 c8e6ea88a72c673758b4d4fa410e777341d3ce4362b8151ae4aebdd088394fc3fae25c896ce65c949a5a86ad98ae3ec3c0227b034daa711e2df35244a50cc6b0 diff --git a/gui-libs/wayfire-plugins-custom/wayfire-plugins-custom-0.1.0-r1.ebuild b/gui-libs/wayfire-plugins-custom/wayfire-plugins-custom-0.1.0-r1.ebuild deleted file mode 100644 index 325c3243f443..000000000000 --- a/gui-libs/wayfire-plugins-custom/wayfire-plugins-custom-0.1.0-r1.ebuild +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright 2026 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -EAPI=8 -PYTHON_COMPAT=( python3_{13..14} ) -PYTHON_OPTIONAL=1 -inherit meson python-single-r1 - -DESCRIPTION="Independent custom Wayfire plugins for visual cues, window memory and Show Desktop" -HOMEPAGE="" -SRC_URI="${P}.tar.xz" -S="${WORKDIR}/${P}" -LICENSE="MIT visual-cues? ( all-rights-reserved ) window-state? ( all-rights-reserved )" -SLOT="0" -KEYWORDS="~amd64" -IUSE="+visual-cues +window-state +panel-desktop test" -REQUIRED_USE=" - || ( visual-cues window-state panel-desktop ) - window-state? ( ${PYTHON_REQUIRED_USE} ) -" -RESTRICT="fetch mirror !test? ( test )" - -RDEPEND=" - >=gui-wm/wayfire-0.11.0-r19:0= - visual-cues? ( x11-libs/pango ) - window-state? ( >=dev-db/sqlite-3.37:3 ${PYTHON_DEPS} ) - !<gui-libs/wayfire-visual-cues-0.1.0-r3 - !<gui-libs/wayfire-window-state-0.1.0-r3 - !<gui-libs/wayfire-panel-desktop-0.1.0-r1 -" -DEPEND="${RDEPEND} - visual-cues? ( dev-cpp/nlohmann_json ) - window-state? ( dev-cpp/nlohmann_json ) -" -BDEPEND="virtual/pkgconfig window-state? ( ${PYTHON_DEPS} )" - -pkg_setup() { - if use window-state; then - python-single-r1_pkg_setup - fi -} - - -src_configure() { - local emesonargs=( - $(meson_use visual-cues) - $(meson_use window-state) - $(meson_use panel-desktop) - $(meson_use test tests) - ) - meson_src_configure -} - -src_install() { - meson_src_install - use window-state && python_fix_shebang "${ED}/usr/bin/wayfire-state" - dodoc README.md ORIGINS.json - local plugin - for plugin in visual-cues window-state panel-desktop; do - if use "${plugin}"; then - docinto "plugins/${plugin}" - dodoc "plugins/${plugin}/README.md" "plugins/${plugin}/LICENSE" - fi - done -} - -pkg_postinst() { - elog "This collection retains the visual-cues, window-state and panel-desktop plugin names." - elog "Existing library paths and window-state databases remain compatible." - elog "Select modules with USE flags; enable them separately in Wayfire." -} - -pkg_nofetch() { - einfo "Obtain the private ${P}.tar.xz from the owner and place it in ${DISTDIR}." -} diff --git a/gui-libs/wayfire-plugins-custom/wayfire-plugins-custom-0.1.0-r10.ebuild b/gui-libs/wayfire-plugins-custom/wayfire-plugins-custom-0.1.0-r10.ebuild deleted file mode 100644 index e8e151085c86..000000000000 --- a/gui-libs/wayfire-plugins-custom/wayfire-plugins-custom-0.1.0-r10.ebuild +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright 2026 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -EAPI=8 -PYTHON_COMPAT=( python3_{13..14} ) -PYTHON_OPTIONAL=1 -inherit meson python-single-r1 - -DESCRIPTION="Independent custom Wayfire plugins for visual cues, window memory, Show Desktop and workspace backgrounds" -HOMEPAGE="" -SRC_URI="https://www.srcux.com/distfiles/${PN}-0.1.0-r7.tar.xz" -S="${WORKDIR}/${PN}-0.1.0-r7" -LICENSE="MIT visual-cues? ( all-rights-reserved ) window-state? ( all-rights-reserved )" -SLOT="0" -KEYWORDS="~amd64" -IUSE="+visual-cues +window-state +panel-desktop +workspace-backgrounds test" -REQUIRED_USE=" - || ( visual-cues window-state panel-desktop workspace-backgrounds ) - window-state? ( ${PYTHON_REQUIRED_USE} ) -" -RESTRICT="mirror !test? ( test )" - -src_prepare() { - default - eapply "${FILESDIR}/window-state-exact-actions-v1.patch" - eapply "${FILESDIR}/window-state-control-exclusion-v1.patch" - eapply "${FILESDIR}/window-state-preview-ipc-budget-v1.patch" - eapply "${FILESDIR}/window-state-settings-exclusion-v1.patch" -} - -RDEPEND=" - >=gui-wm/wayfire-0.11.0-r19:0= - workspace-backgrounds? ( >=gui-wm/wayfire-0.11.0-r29:0= x11-libs/cairo x11-libs/gdk-pixbuf:2 ) - visual-cues? ( x11-libs/pango ) - window-state? ( >=dev-db/sqlite-3.37:3 ${PYTHON_DEPS} ) - !<gui-libs/wayfire-visual-cues-0.1.0-r3 - !<gui-libs/wayfire-window-state-0.1.0-r3 - !<gui-libs/wayfire-panel-desktop-0.1.0-r1 -" -DEPEND="${RDEPEND} - workspace-backgrounds? ( dev-cpp/nlohmann_json ) - visual-cues? ( dev-cpp/nlohmann_json ) - window-state? ( dev-cpp/nlohmann_json ) -" -BDEPEND="virtual/pkgconfig window-state? ( ${PYTHON_DEPS} )" - -pkg_setup() { - if use window-state; then - python-single-r1_pkg_setup - fi -} - - -src_configure() { - local emesonargs=( - $(meson_use visual-cues) - $(meson_use window-state) - $(meson_use panel-desktop) - $(meson_use workspace-backgrounds) - -Dworkspace-backgrounds-test-api=false - $(meson_use test tests) - ) - meson_src_configure -} - -src_install() { - meson_src_install - use window-state && python_fix_shebang "${ED}/usr/bin/wayfire-state" - dodoc README.md ORIGINS.json - local plugin - for plugin in visual-cues window-state panel-desktop workspace-backgrounds; do - if use "${plugin}"; then - docinto "plugins/${plugin}" - dodoc "plugins/${plugin}/README.md" "plugins/${plugin}/LICENSE" - fi - done -} - -pkg_postinst() { - elog "Workspace backgrounds: start the matching Wayfire, enable workspace-backgrounds and ipc, then open WCM Displays → Backgrounds." - elog "This collection retains the visual-cues, window-state and panel-desktop plugin names." - elog "Existing library paths and window-state databases remain compatible." - elog "Select modules with USE flags; enable them separately in Wayfire. Drag snapping is configured in Move." -} diff --git a/gui-libs/wayfire-plugins-extra/wayfire-plugins-extra-0.11.2-r2.ebuild b/gui-libs/wayfire-plugins-extra/wayfire-plugins-extra-0.11.2-r2.ebuild deleted file mode 100644 index 601c07cf0850..000000000000 --- a/gui-libs/wayfire-plugins-extra/wayfire-plugins-extra-0.11.2-r2.ebuild +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2019-2026 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -EAPI=8 - -inherit meson - -DESCRIPTION="extra plugins for wayfire" -HOMEPAGE="https://github.com/WayfireWM/wayfire-plugins-extra" - -if [[ ${PV} == 9999 ]]; then - inherit git-r3 - EGIT_REPO_URI="https://github.com/WayfireWM/wayfire-plugins-extra.git" - SLOT="0/0.12" -else - SRC_URI="https://github.com/WayfireWM/wayfire-plugins-extra/releases/download/v${PV}/${P}.tar.xz" - KEYWORDS="~amd64 ~arm64" - SLOT="0/$(ver_cut 1-2)" -fi - -LICENSE="MIT" - -# No upstream suite; private integration fixtures are in tests/force-fullscreen-hotplug. -RESTRICT="test" - -WAYFIRE_REVDEP=" - dev-libs/glib:2 - dev-libs/libsigc++:2 - gui-libs/wf-config:= - gui-libs/wlroots:0.20 - x11-libs/cairo -" - -DEPEND=" - ${WAYFIRE_REVDEP} - dev-cpp/glibmm:2 - dev-libs/boost - dev-libs/libevdev - dev-libs/wayland - >=gui-wm/wayfire-0.11:= - media-libs/glm - media-libs/libglvnd -" -RDEPEND="${DEPEND}" -BDEPEND=" - >=dev-libs/wayland-protocols-1.12 - dev-util/wayland-scanner - sys-devel/gettext - virtual/pkgconfig -" - -PATCHES=( - "${FILESDIR}/wayfire-plugins-extra-0.11.0-workspace-focus.patch" - "${FILESDIR}/wayfire-plugins-extra-0.11.0-force-fullscreen-teardown.patch" -) diff --git a/gui-libs/wayfire-plugins-extra/wayfire-plugins-extra-0.11.2-r3.ebuild b/gui-libs/wayfire-plugins-extra/wayfire-plugins-extra-0.11.2-r3.ebuild deleted file mode 100644 index fedfa86bf077..000000000000 --- a/gui-libs/wayfire-plugins-extra/wayfire-plugins-extra-0.11.2-r3.ebuild +++ /dev/null @@ -1,64 +0,0 @@ -# Copyright 2019-2026 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -EAPI=8 - -inherit meson - -DESCRIPTION="extra plugins for wayfire" -HOMEPAGE="https://github.com/WayfireWM/wayfire-plugins-extra" - -if [[ ${PV} == 9999 ]]; then - inherit git-r3 - EGIT_REPO_URI="https://github.com/WayfireWM/wayfire-plugins-extra.git" - SLOT="0/0.12" -else - SRC_URI="https://github.com/WayfireWM/wayfire-plugins-extra/releases/download/v${PV}/${P}.tar.xz" - KEYWORDS="~amd64 ~arm64" - SLOT="0/$(ver_cut 1-2)" -fi - -LICENSE="MIT" - -# No upstream suite; private integration fixtures are in tests/force-fullscreen-hotplug. -RESTRICT="test" - -WAYFIRE_REVDEP=" - dev-libs/glib:2 - dev-libs/libsigc++:2 - gui-libs/wf-config:= - gui-libs/wlroots:0.20 - x11-libs/cairo -" - -DEPEND=" - ${WAYFIRE_REVDEP} - dev-cpp/glibmm:2 - dev-libs/boost - dev-libs/libevdev - dev-libs/wayland - >=gui-wm/wayfire-0.11:= - media-libs/glm - media-libs/libglvnd -" -RDEPEND="${DEPEND}" -BDEPEND=" - >=dev-libs/wayland-protocols-1.12 - dev-util/wayland-scanner - sys-devel/gettext - virtual/pkgconfig -" - -PATCHES=( - "${FILESDIR}/wayfire-plugins-extra-0.11.0-workspace-focus.patch" - "${FILESDIR}/wayfire-plugins-extra-0.11.0-force-fullscreen-teardown.patch" - "${FILESDIR}/wayfire-plugins-extra-0.11.2-force-fullscreen-physical-scope.patch" -) - -src_install() { - meson_src_install - - # A distinct library path permits replacement while the old plugin is cached. - exeinto "/usr/$(get_libdir)/wayfire/force-fullscreen-physical-r3" - doexe "${BUILD_DIR}/src/libforce-fullscreen.so" -} diff --git a/gui-libs/wayfire-plugins-extra/wayfire-plugins-extra-0.11.2-r4.ebuild b/gui-libs/wayfire-plugins-extra/wayfire-plugins-extra-0.11.2-r4.ebuild deleted file mode 100644 index 449b2a072ce3..000000000000 --- a/gui-libs/wayfire-plugins-extra/wayfire-plugins-extra-0.11.2-r4.ebuild +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright 2019-2026 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -EAPI=8 - -inherit meson - -DESCRIPTION="extra plugins for wayfire" -HOMEPAGE="https://github.com/WayfireWM/wayfire-plugins-extra" - -if [[ ${PV} == 9999 ]]; then - inherit git-r3 - EGIT_REPO_URI="https://github.com/WayfireWM/wayfire-plugins-extra.git" - SLOT="0/0.12" -else - SRC_URI="https://github.com/WayfireWM/wayfire-plugins-extra/releases/download/v${PV}/${P}.tar.xz" - KEYWORDS="~amd64 ~arm64" - SLOT="0/$(ver_cut 1-2)" -fi - -LICENSE="MIT" - -# No upstream suite; private integration fixtures are in tests/force-fullscreen-hotplug. -RESTRICT="test" - -WAYFIRE_REVDEP=" - dev-libs/glib:2 - dev-libs/libsigc++:2 - gui-libs/wf-config:= - gui-libs/wlroots:0.20 - x11-libs/cairo -" - -DEPEND=" - ${WAYFIRE_REVDEP} - dev-cpp/glibmm:2 - dev-libs/boost - dev-libs/libevdev - dev-libs/wayland - >=gui-wm/wayfire-0.11:= - media-libs/glm - media-libs/libglvnd -" -RDEPEND="${DEPEND}" -BDEPEND=" - >=dev-libs/wayland-protocols-1.12 - dev-util/wayland-scanner - sys-devel/gettext - virtual/pkgconfig -" - -PATCHES=( - "${FILESDIR}/wayfire-plugins-extra-0.11.0-workspace-focus.patch" - "${FILESDIR}/wayfire-plugins-extra-0.11.0-force-fullscreen-teardown.patch" - "${FILESDIR}/wayfire-plugins-extra-0.11.2-force-fullscreen-physical-scope.patch" - "${FILESDIR}/wayfire-plugins-extra-0.11.2-join-views-multi-view.patch" -) - -src_install() { - meson_src_install - - # A distinct library path permits replacement while the old plugin is cached. - exeinto "/usr/$(get_libdir)/wayfire/force-fullscreen-physical-r3" - doexe "${BUILD_DIR}/src/libforce-fullscreen.so" -} diff --git a/gui-wm/wayfire/files/wayfire-0.11.0-snap-zones.patch b/gui-wm/wayfire/files/wayfire-0.11.0-snap-zones.patch deleted file mode 100644 index 29c9212983ae..000000000000 --- a/gui-wm/wayfire/files/wayfire-0.11.0-snap-zones.patch +++ /dev/null @@ -1,271 +0,0 @@ ---- a/plugins/grid/grid.cpp -+++ b/plugins/grid/grid.cpp -@@ -18,6 +18,7 @@ - #include <wayfire/plugins/common/preview-indication.hpp> - #include "wayfire/plugins/grid.hpp" - #include "wayfire/plugins/crossfade.hpp" -+#include "wayfire/plugins/snap-zones.hpp" - #include <wayfire/window-manager.hpp> - - #include <wayfire/plugins/wobbly/wobbly-signal.hpp> -@@ -117,6 +118,7 @@ - class wayfire_grid : public wf::plugin_interface_t, public wf::per_output_tracker_mixin_t<> - { - std::vector<std::string> slots = {"unused", "bl", "b", "br", "l", "c", "r", "tl", "t", "tr"}; -+ grid_snap_zones_t snap_zones; - wf::ipc_activator_t bindings[10]; - wf::ipc_activator_t restore{"grid/restore"}; - wf::ipc_activator_t zone_maximize{"grid/zone_maximize"}; -@@ -315,6 +317,7 @@ - void init() override - { - init_output_tracking(); -+ snap_zones.init(); - default_scope.set_callback([this] { reset_policy_zones(); }); - scope_rules.set_callback([this] { reset_policy_zones(); }); - auto scope_handler = [this] (std::string scope) -@@ -563,6 +566,7 @@ - - void fini() override - { -+ snap_zones.fini(); - clear_zone_preview(); - fini_output_tracking(); - } ---- /dev/null -+++ b/plugins/grid/wayfire/plugins/snap-zones.hpp -@@ -0,0 +1,223 @@ -+#pragma once -+ -+#include "grid.hpp" -+#include <wayfire/output-layout.hpp> -+#include <wayfire/output-stitch.hpp> -+#include <wayfire/window-scope.hpp> -+#include <wayfire/scene-render.hpp> -+#include <wayfire/scene-operations.hpp> -+#include <wayfire/plugins/ipc/ipc-helpers.hpp> -+#include <wayfire/plugins/ipc/ipc-method-repository.hpp> -+#include <wayfire/plugins/common/shared-core-data.hpp> -+#include <wayfire/util.hpp> -+#include <algorithm> -+ -+namespace -+{ -+struct snap_region_t -+{ -+ wf::geometry_t geometry; -+ int slot; -+}; -+ -+// Split only where calc_slot can change its answer. Both the map and desktop -+// overlay use this partition of the same workarea and physical pointer bounds -+// used by a real drag; panel reservations and gaps are included naturally. -+std::vector<snap_region_t> snap_regions(wf::geometry_t bounds, wf::geometry_t area, int edge, int corner) -+{ -+ auto cuts = [] (double lo, double hi, double a, double b, int edge, int corner, bool horizontal) -+ { -+ std::vector<double> values{lo, hi}; -+ // Match calc_slot's integer pointer coordinates and asymmetric < / <= tests. -+ auto near = [horizontal] (double v) { return horizontal ? std::floor(v) + 1 : std::ceil(v); }; -+ auto far = [horizontal] (double v) { return horizontal ? std::ceil(v) : std::floor(v) + 1; }; -+ for (double v : {near(a + edge), far(b - edge), near(a + corner), far(b - corner)}) -+ if (v > lo && v < hi) values.push_back(v); -+ std::sort(values.begin(), values.end()); -+ values.erase(std::unique(values.begin(), values.end()), values.end()); -+ return values; -+ }; -+ auto xs = cuts(bounds.x, bounds.x + bounds.width, area.x, area.x + area.width, edge, corner, true); -+ auto ys = cuts(bounds.y, bounds.y + bounds.height, area.y, area.y + area.height, edge, corner, false); -+ std::vector<snap_region_t> regions; -+ for (size_t y = 1; y < ys.size(); ++y) -+ for (size_t x = 1; x < xs.size(); ++x) -+ { -+ // calc_slot accepts integer pointer coordinates, as does Move. -+ wf::point_t p{int(std::ceil(xs[x - 1])), int(std::ceil(ys[y - 1]))}; -+ if (p.x >= xs[x] || p.y >= ys[y]) continue; -+ int slot = wf::grid::calc_slot(area, p, edge, corner); -+ if (!slot) continue; -+ wf::geometry_t g{xs[x - 1], ys[y - 1], xs[x] - xs[x - 1], ys[y] - ys[y - 1]}; -+ // Merge adjacent cells to avoid displaying implementation seams. -+ if (!regions.empty() && regions.back().slot == slot && regions.back().geometry.y == g.y && -+ regions.back().geometry.height == g.height && -+ regions.back().geometry.x + regions.back().geometry.width == g.x) -+ regions.back().geometry.width += g.width; -+ else regions.push_back({g, slot}); -+ } -+ for (size_t i = 0; i < regions.size(); ++i) -+ for (size_t j = i + 1; j < regions.size();) -+ { -+ auto& a = regions[i]; auto& b = regions[j]; -+ if (a.slot == b.slot && a.geometry.x == b.geometry.x && a.geometry.width == b.geometry.width && -+ a.geometry.y + a.geometry.height == b.geometry.y) -+ { -+ a.geometry.height += b.geometry.height; -+ regions.erase(regions.begin() + j); -+ } else ++j; -+ } -+ return regions; -+} -+ -+class snap_zones_node_t : public wf::scene::node_t -+{ -+ class render_instance_t : public wf::scene::simple_render_instance_t<snap_zones_node_t> -+ { -+ public: -+ using simple_render_instance_t::simple_render_instance_t; -+ void render(const wf::scene::render_instruction_t& data) override -+ { -+ for (auto& r : self->regions) -+ { -+ bool corner = r.slot == 1 || r.slot == 3 || r.slot == 7 || r.slot == 9; -+ wf::color_t color = corner ? wf::color_t{0.68, 0.48, 0.96, 0.42} : wf::color_t{0.26, 0.72, 0.90, 0.38}; -+ data.pass->add_rect(color, data.target, r.geometry, data.damage); -+ } -+ } -+ }; -+ std::vector<snap_region_t> regions; -+ wf::geometry_t bounds; -+ public: -+ snap_zones_node_t(std::vector<snap_region_t> regions, wf::geometry_t bounds) : -+ node_t(false), regions(std::move(regions)), bounds(bounds) {} -+ std::optional<wf::scene::input_node_t> find_node_at(const wf::pointf_t&) override { return {}; } -+ wf::geometry_t get_bounding_box() override { return bounds; } -+ std::string stringify() const override { return "grid-snap-zones"; } -+ void gen_render_instances(std::vector<wf::scene::render_instance_uptr>& instances, -+ wf::scene::damage_callback damage, wf::output_t *output) override -+ { -+ instances.push_back(std::make_unique<render_instance_t>(this, damage, output)); -+ } -+}; -+ -+class grid_snap_zones_t -+{ -+ wf::shared_data::ref_ptr_t<wf::ipc::method_repository_t> repository; -+ wf::option_wrapper_t<int> edge{"move/snap_threshold"}, corner{"move/quarter_snap_threshold"}; -+ wf::option_wrapper_t<bool> enabled{"move/enable_snap"}; -+ wf::wl_timer<false> lease; -+ std::shared_ptr<snap_zones_node_t> overlay; -+ std::string owner, scope = "physical", displayed; -+ std::vector<snap_region_t> render_regions; -+ wf::geometry_t render_bounds; -+ -+ wf::json_t snapshot(const std::string& selected_scope) -+ { -+ auto reply = wf::ipc::json_ok(); reply["version"] = 1; -+ reply["edge"] = edge.value(); reply["corner"] = corner.value(); reply["enabled"] = enabled.value(); -+ reply["config_file"] = std::string(getenv("WAYFIRE_CONFIG_FILE") ?: ""); -+ reply["scope"] = selected_scope; reply["monitors"] = wf::json_t::array(); -+ render_regions.clear(); -+ const std::vector<std::string> names{"", "bl", "b", "br", "l", "c", "r", "tl", "t", "tr"}; -+ bool first = true; -+ auto add = [&] (wf::output_t *output, std::string name, wf::geometry_t bounds, wf::geometry_t area) -+ { -+ auto origin = wf::origin(output->get_layout_geometry()); -+ auto global_bounds = bounds + origin; -+ auto monitor = wf::json_t(); monitor["name"] = name; -+ monitor["geometry"] = wf::ipc::geometry_to_json(global_bounds); -+ monitor["workarea"] = wf::ipc::geometry_to_json(area + origin); -+ monitor["regions"] = wf::json_t::array(); -+ if (first) { render_bounds = global_bounds; first = false; } -+ else -+ { -+ auto x = std::min(render_bounds.x, global_bounds.x), y = std::min(render_bounds.y, global_bounds.y); -+ render_bounds = {x, y, std::max(render_bounds.x + render_bounds.width, global_bounds.x + global_bounds.width) - x, -+ std::max(render_bounds.y + render_bounds.height, global_bounds.y + global_bounds.height) - y}; -+ } -+ for (auto r : snap_regions(bounds, area, edge, corner)) -+ { -+ r.geometry = r.geometry + origin; -+ wf::json_t region; region["geometry"] = wf::ipc::geometry_to_json(r.geometry); -+ region["slot"] = names[r.slot]; -+ region["target"] = wf::ipc::geometry_to_json(wf::grid::get_slot_dimensions(area, r.slot) + origin); -+ monitor["regions"].append(region); render_regions.push_back(r); -+ } -+ reply["monitors"].append(monitor); -+ }; -+ for (auto output : wf::get_core().output_layout->get_outputs()) -+ { -+ if (std::string(output->handle->name).find("NOOP-") == 0) continue; -+ auto layout = output->get_data<wf::output_stitch_layout_t>(); -+ if (selected_scope == "physical" && layout && !layout->zones.empty()) -+ for (auto& zone : layout->zones) add(output, zone.name, zone.geometry, wf::physical_zone_workarea(output, zone)); -+ else add(output, output->handle->name, output->get_relative_geometry(), output->workarea->get_workarea()); -+ } -+ return reply; -+ } -+ -+ void hide() -+ { -+ if (overlay) wf::scene::remove_child(overlay); -+ overlay.reset(); displayed.clear(); owner.clear(); -+ lease.disconnect(); -+ } -+ -+ void show(const wf::json_t& state) -+ { -+ auto content = state.serialize(); -+ if (content == displayed) return; -+ if (overlay) wf::scene::remove_child(overlay); -+ overlay.reset(); displayed = content; -+ if (render_regions.empty()) return; -+ overlay = std::make_shared<snap_zones_node_t>(render_regions, render_bounds); -+ wf::scene::add_front(wf::get_core().scene(), overlay); -+ } -+ -+ wf::signal::connection_t<wf::output_layout_configuration_changed_signal> layout_changed = -+ [this] (auto) { if (!owner.empty()) show(snapshot(scope)); }; -+ wf::signal::connection_t<wf::reload_config_signal> config_changed = -+ [this] (auto) { if (!owner.empty()) show(snapshot(scope)); }; -+ wf::signal::connection_t<wf::workarea_changed_signal> workarea_changed = -+ [this] (auto) { if (!owner.empty()) show(snapshot(scope)); }; -+ public: -+ void init() -+ { -+ repository->register_method("grid/snap-zones", [this] (wf::json_t data) -+ { -+ std::string selected = "physical"; -+ if (data.has_member("scope")) -+ { -+ if (!data["scope"].is_string()) return wf::ipc::json_error("scope must be physical or virtual"); -+ selected = data["scope"].as_string(); -+ if (selected != "physical" && selected != "virtual") return wf::ipc::json_error("scope must be physical or virtual"); -+ } -+ if (data.has_member("visible")) -+ { -+ if (!data["visible"].is_bool() || !data.has_member("owner") || !data["owner"].is_string() || -+ data["owner"].as_string().empty() || data["owner"].as_string().size() > 128) -+ return wf::ipc::json_error("visible requires a boolean and an owner identifier"); -+ auto requested = data["owner"].as_string(); -+ if (data["visible"].as_bool()) -+ { -+ if (!owner.empty() && owner != requested) return wf::ipc::json_error("Snap zones are already displayed by another settings window."); -+ owner = requested; scope = selected; -+ } else if (owner == requested) hide(); -+ } -+ auto reply = snapshot(selected); -+ if (data.has_member("visible") && data["visible"].as_bool()) -+ { -+ show(reply); -+ lease.set_timeout(2500, [this] { hide(); }); -+ } -+ reply["visible"] = bool(overlay); -+ return reply; -+ }); -+ wf::get_core().output_layout->connect(&layout_changed); -+ wf::get_core().connect(&config_changed); -+ wf::get_core().connect(&workarea_changed); -+ } -+ void fini() { hide(); repository->unregister_method("grid/snap-zones"); } -+}; -+} ---- a/metadata/grid.xml -+++ b/metadata/grid.xml -@@ -2,7 +2,7 @@ - <wayfire> - <plugin name="grid"> - <_short>Grid</_short> -- <_long>Position windows within their physical or VIRTUAL workarea. Repeating a position shortcut cycles through 100%, 80%, and 60% of that slot's normal width and height. Corners remain anchored; sides and center stay centered. A different position starts at 100%.</_long> -+ <_long>Position windows on a physical monitor or the VIRTUAL desktop. Repeating a position shortcut cycles through 100%, 80%, and 60% of its normal size.</_long> - <category>Window Management</category> - <option name="toggle_scope" type="activator"> - <_short>Toggle physical and VIRTUAL scope</_short> diff --git a/gui-wm/wayfire/wayfire-0.11.0-r21.ebuild b/gui-wm/wayfire/wayfire-0.11.0-r21.ebuild deleted file mode 100644 index 10b793ea780f..000000000000 --- a/gui-wm/wayfire/wayfire-0.11.0-r21.ebuild +++ /dev/null @@ -1,201 +0,0 @@ -# Copyright 2019-2026 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -EAPI=8 - -inherit dot-a meson toolchain-funcs - -DESCRIPTION="compiz like 3D wayland compositor" -HOMEPAGE="https://github.com/WayfireWM/wayfire" - -if [[ ${PV} == 9999 ]]; then - inherit git-r3 - EGIT_REPO_URI="https://github.com/WayfireWM/${PN}.git" - SLOT="0/0.12" - - IUSE="vulkan" - - # vulkan support is experimental and uses a bundled fork of wlroots - # keep it only in the live ebuild for now - RDEPEND="vulkan? ( media-libs/vulkan-loader )" - DEPEND="vulkan? ( dev-util/vulkan-headers )" - COMMON_DEPEND=" - !vulkan? ( - gui-libs/wlroots:0.20[drm(+),libinput(+),x11-backend,X?] - ) - vulkan? ( - dev-libs/libliftoff - media-libs/lcms:2 - media-libs/libdisplay-info:= - media-libs/mesa - sys-auth/seatd:= - x11-libs/libdrm - x11-libs/xcb-util-errors - x11-libs/xcb-util-renderutil - x11-libs/xcb-util-wm - ) - " -else - SRC_URI="https://github.com/WayfireWM/${PN}/releases/download/v${PV}/${P}.tar.xz" - KEYWORDS="~amd64 ~arm64 ~riscv" - SLOT="0/$(ver_cut 1-2)" - - COMMON_DEPEND="gui-libs/wlroots:0.20[drm(+),libinput(+),x11-backend,X?]" -fi - -LICENSE="MIT" -IUSE+=" X +dbus +gles3 nls openmp test" -RESTRICT="!test? ( test )" - -# bundled wlroots has the following dependency string according to included headers. -# wlroots[drm,gles2-renderer,libinput,x11-backend?,X?] -# enable x11-backend with X and vice versa -COMMON_DEPEND+=" - dev-cpp/nlohmann_json - dev-libs/glib:2 - dev-libs/libevdev - dev-libs/libinput:= - dev-libs/wayland - dev-libs/yyjson:= - >=dev-libs/wayland-protocols-1.12 - gui-libs/wf-config:${SLOT} - media-libs/glm - media-libs/libglvnd - media-libs/libjpeg-turbo:= - media-libs/libpng:= - virtual/libudev:= - x11-libs/cairo - x11-libs/libxkbcommon - x11-libs/pango - x11-libs/pixman - dbus? ( sys-apps/dbus ) - X? ( x11-libs/libxcb:= ) -" - -RDEPEND+=" - ${COMMON_DEPEND} - x11-misc/xkeyboard-config -" -DEPEND+=" - ${COMMON_DEPEND} - test? ( dev-cpp/doctest ) -" -BDEPEND=" - dev-util/wayland-scanner - virtual/pkgconfig - openmp? ( - || ( - sys-devel/gcc[openmp] - llvm-runtimes/clang-runtime[openmp] - ) - ) - nls? ( - sys-devel/gettext - ) -" - -PATCHES=( - "${FILESDIR}"/${P}-transparent-cube.patch - "${FILESDIR}"/${P}-rotate-layer-shell.patch - "${FILESDIR}"/${P}-screensaver-layer.patch - "${FILESDIR}"/${P}-desktop-sampling.patch - "${FILESDIR}"/${P}-cube-damage-stacking.patch - "${FILESDIR}"/${P}-cube-panel-overlay.patch - "${FILESDIR}"/${P}-stitched-outputs.patch - "${FILESDIR}"/${P}-virtual-output-polish.patch - "${FILESDIR}"/${P}-wcm-displays.patch - "${FILESDIR}"/${P}-window-scope.patch - "${FILESDIR}"/${P}-float-on-top.patch - "${FILESDIR}"/${P}-physical-tiling.patch - "${FILESDIR}"/${P}-cube-key-repeat.patch - "${FILESDIR}"/${P}-cube-repeat-minimum-20.patch - "${FILESDIR}"/${P}-scope-toggle.patch - "${FILESDIR}"/${P}-dialog-family-stacking.patch - "${FILESDIR}"/${P}-physical-grid.patch - "${FILESDIR}"/${P}-grid-preview.patch - "${FILESDIR}"/${P}-grid-cycle.patch - "${FILESDIR}"/${P}-snap-zones.patch -) - -# SCREENSAVER changes scene layer indices. Rebuild plugins against the matching -# ABI; wf-config above retains its upstream 0.11 subslot. -SLOT="${SLOT}.screensaver1" - -pkg_pretend() { - [[ ${MERGE_TYPE} != binary ]] && use openmp && tc-check-openmp -} - -pkg_setup() { - [[ ${MERGE_TYPE} != binary ]] && use openmp && tc-check-openmp -} - -src_prepare() { - default - - local dbusrunsession=$(usev dbus dbus-run-session) - sed -e "s:@EPREFIX@:${EPREFIX}:g" -e "s:@DBUS_RUN_SESSION@:${dbusrunsession}:" \ - "${FILESDIR}"/wayfire-session-2 > "${T}"/wayfire-session || die - sed -e "s:@EPREFIX@:${EPREFIX}:" \ - "${FILESDIR}"/wayfire-session.desktop > "${T}"/wayfire-session.desktop || die -} - -src_configure() { - lto-guarantee-fat - - local emesonargs=( - $(meson_feature test tests) - $(meson_feature X xwayland) - $(meson_use gles3 enable_gles32) - $(meson_feature nls build_locales) - $(meson_use openmp enable_openmp) - -Duse_system_wfconfig=enabled - ) - - if [[ ${PV} == 9999 ]]; then - emesonargs+=( - $(meson_feature !vulkan use_system_wlroots) - $(meson_use vulkan vulkan_effects) - ) - else - emesonargs+=( - -Duse_system_wlroots=enabled - -Dvulkan_effects=false - ) - fi - - meson_src_configure -} - -src_install() { - meson_src_install - - # A distinct packaged path permits upgrading a resident cached plugin. - exeinto "/usr/$(get_libdir)/wayfire/wm-actions" - doexe "${BUILD_DIR}/plugins/wm-actions/libwm-actions.so" - exeinto "/usr/$(get_libdir)/wayfire/grid" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-physical" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/move-physical" - doexe "${BUILD_DIR}/plugins/single_plugins/libmove.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-preview" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-cycle" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-zones" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - dobin "${T}"/wayfire-session - - insinto "/usr/share/wayland-sessions/" - insopts -m644 - doins wayfire.desktop - doins "${T}"/wayfire-session.desktop - - insinto "/usr/share/wayfire/" - doins wayfire.ini - - insinto "/etc" - doins "${FILESDIR}"/wayfire.env - - strip-lto-bytecode -} diff --git a/gui-wm/wayfire/wayfire-0.11.0-r31.ebuild b/gui-wm/wayfire/wayfire-0.11.0-r31.ebuild deleted file mode 100644 index 23872d7eacc4..000000000000 --- a/gui-wm/wayfire/wayfire-0.11.0-r31.ebuild +++ /dev/null @@ -1,219 +0,0 @@ -# Copyright 2019-2026 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -EAPI=8 - -inherit dot-a meson toolchain-funcs - -DESCRIPTION="compiz like 3D wayland compositor" -HOMEPAGE="https://github.com/WayfireWM/wayfire" - -if [[ ${PV} == 9999 ]]; then - inherit git-r3 - EGIT_REPO_URI="https://github.com/WayfireWM/${PN}.git" - SLOT="0/0.12" - - IUSE="vulkan" - - # vulkan support is experimental and uses a bundled fork of wlroots - # keep it only in the live ebuild for now - RDEPEND="vulkan? ( media-libs/vulkan-loader )" - DEPEND="vulkan? ( dev-util/vulkan-headers )" - COMMON_DEPEND=" - !vulkan? ( - gui-libs/wlroots:0.20[drm(+),libinput(+),x11-backend,X?] - ) - vulkan? ( - dev-libs/libliftoff - media-libs/lcms:2 - media-libs/libdisplay-info:= - media-libs/mesa - sys-auth/seatd:= - x11-libs/libdrm - x11-libs/xcb-util-errors - x11-libs/xcb-util-renderutil - x11-libs/xcb-util-wm - ) - " -else - SRC_URI="https://github.com/WayfireWM/${PN}/releases/download/v${PV}/${P}.tar.xz" - KEYWORDS="~amd64 ~arm64 ~riscv" - SLOT="0/$(ver_cut 1-2)" - - COMMON_DEPEND="gui-libs/wlroots:0.20[drm(+),libinput(+),x11-backend,X?]" -fi - -LICENSE="MIT" -IUSE+=" X +dbus +gles3 nls openmp test" -RESTRICT="!test? ( test )" - -# bundled wlroots has the following dependency string according to included headers. -# wlroots[drm,gles2-renderer,libinput,x11-backend?,X?] -# enable x11-backend with X and vice versa -COMMON_DEPEND+=" - dev-cpp/nlohmann_json - dev-libs/glib:2 - dev-libs/libevdev - dev-libs/libinput:= - dev-libs/wayland - dev-libs/yyjson:= - >=dev-libs/wayland-protocols-1.12 - gui-libs/wf-config:${SLOT} - media-libs/glm - media-libs/libglvnd - media-libs/libjpeg-turbo:= - media-libs/libpng:= - virtual/libudev:= - x11-libs/cairo - x11-libs/libxkbcommon - x11-libs/pango - x11-libs/pixman - dbus? ( sys-apps/dbus ) - X? ( x11-libs/libxcb:= ) -" - -RDEPEND+=" - ${COMMON_DEPEND} - x11-misc/xkeyboard-config -" -DEPEND+=" - ${COMMON_DEPEND} - test? ( dev-cpp/doctest ) -" -BDEPEND=" - dev-util/wayland-scanner - virtual/pkgconfig - openmp? ( - || ( - sys-devel/gcc[openmp] - llvm-runtimes/clang-runtime[openmp] - ) - ) - nls? ( - sys-devel/gettext - ) -" - -PATCHES=( - "${FILESDIR}"/${P}-transparent-cube.patch - "${FILESDIR}"/${P}-rotate-layer-shell.patch - "${FILESDIR}"/${P}-screensaver-layer.patch - "${FILESDIR}"/${P}-desktop-sampling.patch - "${FILESDIR}"/${P}-cube-damage-stacking.patch - "${FILESDIR}"/${P}-cube-panel-overlay.patch - "${FILESDIR}"/${P}-stitched-outputs.patch - "${FILESDIR}"/${P}-virtual-output-polish.patch - "${FILESDIR}"/${P}-wcm-displays.patch - "${FILESDIR}"/${P}-window-scope.patch - "${FILESDIR}"/${P}-float-on-top.patch - "${FILESDIR}"/${P}-physical-tiling.patch - "${FILESDIR}"/${P}-cube-key-repeat.patch - "${FILESDIR}"/${P}-cube-repeat-minimum-20.patch - "${FILESDIR}"/${P}-scope-toggle.patch - "${FILESDIR}"/${P}-dialog-family-stacking.patch - "${FILESDIR}"/${P}-physical-grid.patch - "${FILESDIR}"/${P}-grid-preview.patch - "${FILESDIR}"/${P}-grid-cycle.patch - "${FILESDIR}"/${P}-physical-output-focus.patch - "${FILESDIR}"/${P}-oswitch-local-symbols.patch - "${FILESDIR}"/${P}-oswitch-wrap.patch - "${FILESDIR}"/${P}-popup-physical-scope.patch - "${FILESDIR}"/${P}-move-snapping.patch - "${FILESDIR}"/${P}-move-held-redraw.patch - "${FILESDIR}"/${P}-workspace-backgrounds.patch - "${FILESDIR}"/${P}-show-desktop-workspace.patch - "${FILESDIR}"/${P}-vswitch-held-target.patch -) - -# SCREENSAVER changes scene layer indices. Rebuild plugins against the matching -# ABI; wf-config above retains its upstream 0.11 subslot. -SLOT="${SLOT}.screensaver1" - -pkg_pretend() { - [[ ${MERGE_TYPE} != binary ]] && use openmp && tc-check-openmp -} - -pkg_setup() { - [[ ${MERGE_TYPE} != binary ]] && use openmp && tc-check-openmp -} - -src_prepare() { - default - - local dbusrunsession=$(usev dbus dbus-run-session) - sed -e "s:@EPREFIX@:${EPREFIX}:g" -e "s:@DBUS_RUN_SESSION@:${dbusrunsession}:" \ - "${FILESDIR}"/wayfire-session-2 > "${T}"/wayfire-session || die - sed -e "s:@EPREFIX@:${EPREFIX}:" \ - "${FILESDIR}"/wayfire-session.desktop > "${T}"/wayfire-session.desktop || die -} - -src_configure() { - lto-guarantee-fat - - local emesonargs=( - $(meson_feature test tests) - $(meson_feature X xwayland) - $(meson_use gles3 enable_gles32) - $(meson_feature nls build_locales) - $(meson_use openmp enable_openmp) - -Duse_system_wfconfig=enabled - ) - - if [[ ${PV} == 9999 ]]; then - emesonargs+=( - $(meson_feature !vulkan use_system_wlroots) - $(meson_use vulkan vulkan_effects) - ) - else - emesonargs+=( - -Duse_system_wlroots=enabled - -Dvulkan_effects=false - ) - fi - - meson_src_configure -} - -src_install() { - meson_src_install - - # A distinct packaged path permits upgrading a resident cached plugin. - exeinto "/usr/$(get_libdir)/wayfire/wm-actions" - doexe "${BUILD_DIR}/plugins/wm-actions/libwm-actions.so" - exeinto "/usr/$(get_libdir)/wayfire/grid" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-physical" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/move-physical" - doexe "${BUILD_DIR}/plugins/single_plugins/libmove.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-preview" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-cycle" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/oswitch-physical-r24" - doexe "${BUILD_DIR}/plugins/single_plugins/liboswitch.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-move-r27" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/move-snap-r27" - doexe "${BUILD_DIR}/plugins/single_plugins/libmove.so" - exeinto "/usr/$(get_libdir)/wayfire/move-redraw-r28" - doexe "${BUILD_DIR}/plugins/single_plugins/libmove.so" - exeinto "/usr/$(get_libdir)/wayfire/wm-actions-desktop-r30" - doexe "${BUILD_DIR}/plugins/wm-actions/libwm-actions.so" - exeinto "/usr/$(get_libdir)/wayfire/vswitch-held-r31" - doexe "${BUILD_DIR}/plugins/vswitch/libvswitch.so" - dobin "${T}"/wayfire-session - - insinto "/usr/share/wayland-sessions/" - insopts -m644 - doins wayfire.desktop - doins "${T}"/wayfire-session.desktop - - insinto "/usr/share/wayfire/" - doins wayfire.ini - - insinto "/etc" - doins "${FILESDIR}"/wayfire.env - - strip-lto-bytecode -} diff --git a/gui-wm/wayfire/wayfire-0.11.0-r67.ebuild b/gui-wm/wayfire/wayfire-0.11.0-r67.ebuild deleted file mode 100644 index 0ff047836840..000000000000 --- a/gui-wm/wayfire/wayfire-0.11.0-r67.ebuild +++ /dev/null @@ -1,304 +0,0 @@ -# Copyright 2019-2026 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -EAPI=8 - -inherit dot-a meson toolchain-funcs - -DESCRIPTION="compiz like 3D wayland compositor" -HOMEPAGE="https://github.com/WayfireWM/wayfire" - -if [[ ${PV} == 9999 ]]; then - inherit git-r3 - EGIT_REPO_URI="https://github.com/WayfireWM/${PN}.git" - SLOT="0/0.12" - - IUSE="vulkan" - - # vulkan support is experimental and uses a bundled fork of wlroots - # keep it only in the live ebuild for now - RDEPEND="vulkan? ( media-libs/vulkan-loader )" - DEPEND="vulkan? ( dev-util/vulkan-headers )" - COMMON_DEPEND=" - !vulkan? ( - gui-libs/wlroots:0.20[drm(+),libinput(+),x11-backend,X?] - ) - vulkan? ( - dev-libs/libliftoff - media-libs/lcms:2 - media-libs/libdisplay-info:= - media-libs/mesa - sys-auth/seatd:= - x11-libs/libdrm - x11-libs/xcb-util-errors - x11-libs/xcb-util-renderutil - x11-libs/xcb-util-wm - ) - " -else - SRC_URI="https://github.com/WayfireWM/${PN}/releases/download/v${PV}/${P}.tar.xz" - KEYWORDS="~amd64 ~arm64 ~riscv" - SLOT="0/$(ver_cut 1-2)" - - COMMON_DEPEND="gui-libs/wlroots:0.20[drm(+),libinput(+),x11-backend,X?]" -fi - -LICENSE="MIT" -IUSE+=" X +dbus +gles3 nls openmp test" -RESTRICT="!test? ( test )" - -# bundled wlroots has the following dependency string according to included headers. -# wlroots[drm,gles2-renderer,libinput,x11-backend?,X?] -# enable x11-backend with X and vice versa -COMMON_DEPEND+=" - dev-cpp/nlohmann_json - dev-libs/glib:2 - dev-libs/libevdev - dev-libs/libinput:= - dev-libs/wayland - dev-libs/yyjson:= - >=dev-libs/wayland-protocols-1.12 - gui-libs/wf-config:${SLOT} - media-libs/glm - media-libs/libglvnd - media-libs/libjpeg-turbo:= - media-libs/libpng:= - virtual/libudev:= - x11-libs/cairo - x11-libs/libxkbcommon - x11-libs/pango - x11-libs/pixman - dbus? ( sys-apps/dbus ) - X? ( x11-libs/libxcb:= ) -" - -RDEPEND+=" - >=dev-lang/python-3.11 - ${COMMON_DEPEND} - x11-misc/xkeyboard-config -" -DEPEND+=" - ${COMMON_DEPEND} - test? ( dev-cpp/doctest ) -" -BDEPEND=" - dev-util/wayland-scanner - virtual/pkgconfig - openmp? ( - || ( - sys-devel/gcc[openmp] - llvm-runtimes/clang-runtime[openmp] - ) - ) - nls? ( - sys-devel/gettext - ) -" - -PATCHES=( - "${FILESDIR}"/${P}-transparent-cube.patch - "${FILESDIR}"/${P}-rotate-layer-shell.patch - "${FILESDIR}"/${P}-screensaver-layer.patch - "${FILESDIR}"/${P}-desktop-sampling.patch - "${FILESDIR}"/${P}-cube-damage-stacking.patch - "${FILESDIR}"/${P}-cube-panel-overlay.patch - "${FILESDIR}"/${P}-stitched-outputs.patch - "${FILESDIR}"/${P}-virtual-output-polish.patch - "${FILESDIR}"/${P}-wcm-displays.patch - "${FILESDIR}"/${P}-window-scope.patch - "${FILESDIR}"/${P}-float-on-top.patch - "${FILESDIR}"/${P}-physical-tiling.patch - "${FILESDIR}"/${P}-cube-key-repeat.patch - "${FILESDIR}"/${P}-cube-repeat-minimum-20.patch - "${FILESDIR}"/${P}-scope-toggle.patch - "${FILESDIR}"/${P}-dialog-family-stacking.patch - "${FILESDIR}"/${P}-physical-grid.patch - "${FILESDIR}"/${P}-grid-preview.patch - "${FILESDIR}"/${P}-grid-cycle.patch - "${FILESDIR}"/${P}-physical-output-focus.patch - "${FILESDIR}"/${P}-oswitch-local-symbols.patch - "${FILESDIR}"/${P}-oswitch-wrap.patch - "${FILESDIR}"/${P}-popup-physical-scope.patch - "${FILESDIR}"/${P}-move-snapping.patch - "${FILESDIR}"/${P}-move-held-redraw.patch - "${FILESDIR}"/${P}-workspace-backgrounds.patch - "${FILESDIR}"/${P}-show-desktop-workspace.patch - "${FILESDIR}"/${P}-vswitch-held-target.patch - "${FILESDIR}"/${P}-decoration-window-menu.patch - "${FILESDIR}"/${P}-layer-move-old-damage.patch - "${FILESDIR}"/${P}-workspace-grid-events.patch - "${FILESDIR}"/${P}-r35-fullscreen-subpasses.patch - "${FILESDIR}"/${P}-r36-cube-fixed-top.patch - "${FILESDIR}"/${P}-r37-cube-carried-visibility.patch - "${FILESDIR}"/${P}-r38-cube-workspace-lifecycle.patch - "${FILESDIR}"/${P}-r39-cube-workspace-rows.patch - "${FILESDIR}"/${P}-r40-cube-row-translation.patch - "${FILESDIR}"/${P}-r41-cube-complete-row-translation.patch - "${FILESDIR}"/${P}-r42-grid-monitor-window-move.patch - "${FILESDIR}"/${P}-r43-cube-mouse-overview.patch - "${FILESDIR}"/${P}-r44-cube-rigid-stack.patch - "${FILESDIR}"/${P}-r45-cube-stack-tilt.patch - "${FILESDIR}"/${P}-r46-cube-resting-faces.patch - "${FILESDIR}"/${P}-r47-cube-direct-drag.patch - "${FILESDIR}"/${P}-r48-cube-wheel-zoom.patch - "${FILESDIR}"/${P}-r49-cube-square-footprint.patch - "${FILESDIR}"/${P}-r50-cube-center-pivot.patch - "${FILESDIR}"/${P}-r51-cube-centered-entry.patch - "${FILESDIR}"/${P}-r53-cube-warp.patch - "${FILESDIR}"/${P}-r55-cube-backdrop-opacity.patch - "${FILESDIR}"/${P}-r56-cube-warp-rest.patch - "${FILESDIR}"/${P}-r57-cube-hyperspace.patch - "${FILESDIR}"/${P}-r58-cube-nebula.patch - "${FILESDIR}"/${P}-r59-ambient-background.patch - "${FILESDIR}"/${P}-r60-ambient-package-split.patch - "${FILESDIR}"/${P}-r65-output-transforms.patch - "${FILESDIR}"/${P}-drag-escape.patch - "${FILESDIR}"/${P}-r67-grid-gap.patch -) - -# SCREENSAVER changes scene layer indices. Rebuild plugins against the matching -# ABI; wf-config above retains its upstream 0.11 subslot. -SLOT="${SLOT}.screensaver1" - -pkg_pretend() { - [[ ${MERGE_TYPE} != binary ]] && use openmp && tc-check-openmp -} - -pkg_setup() { - [[ ${MERGE_TYPE} != binary ]] && use openmp && tc-check-openmp -} - -src_prepare() { - default - - local dbusrunsession=$(usev dbus dbus-run-session) - sed -e "s:@EPREFIX@:${EPREFIX}:g" -e "s:@DBUS_RUN_SESSION@:${dbusrunsession}:" \ - "${FILESDIR}"/wayfire-session-2 > "${T}"/wayfire-session || die - sed -e "s:@EPREFIX@:${EPREFIX}:" \ - "${FILESDIR}"/wayfire-session.desktop > "${T}"/wayfire-session.desktop || die -} - -src_configure() { - lto-guarantee-fat - - local emesonargs=( - $(meson_feature test tests) - $(meson_feature X xwayland) - $(meson_use gles3 enable_gles32) - $(meson_feature nls build_locales) - $(meson_use openmp enable_openmp) - -Duse_system_wfconfig=enabled - ) - - if [[ ${PV} == 9999 ]]; then - emesonargs+=( - $(meson_feature !vulkan use_system_wlroots) - $(meson_use vulkan vulkan_effects) - ) - else - emesonargs+=( - -Duse_system_wlroots=enabled - -Dvulkan_effects=false - ) - fi - - meson_src_configure -} - -src_install() { - meson_src_install - - exeinto "/usr/$(get_libdir)/wayfire/cube-ambient-r59" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - - # A distinct packaged path permits upgrading a resident cached plugin. - exeinto "/usr/$(get_libdir)/wayfire/wm-actions" - doexe "${BUILD_DIR}/plugins/wm-actions/libwm-actions.so" - exeinto "/usr/$(get_libdir)/wayfire/grid" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-physical" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/move-physical" - doexe "${BUILD_DIR}/plugins/single_plugins/libmove.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-preview" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-cycle" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/oswitch-physical-r24" - doexe "${BUILD_DIR}/plugins/single_plugins/liboswitch.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-move-r27" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - # Legacy activation paths remain valid, now containing ordinary Grid only. - exeinto "/usr/$(get_libdir)/wayfire/grid-occupancy-r54" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-occupancy-r52" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-monitor-r42" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/move-snap-r27" - doexe "${BUILD_DIR}/plugins/single_plugins/libmove.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-gap-r67" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/move-gap-r67" - doexe "${BUILD_DIR}/plugins/single_plugins/libmove.so" - exeinto "/usr/$(get_libdir)/wayfire/move-redraw-r28" - doexe "${BUILD_DIR}/plugins/single_plugins/libmove.so" - exeinto "/usr/$(get_libdir)/wayfire/wm-actions-desktop-r30" - doexe "${BUILD_DIR}/plugins/wm-actions/libwm-actions.so" - exeinto "/usr/$(get_libdir)/wayfire/vswitch-held-r31" - doexe "${BUILD_DIR}/plugins/vswitch/libvswitch.so" - exeinto "/usr/$(get_libdir)/wayfire/vswitch-held-r37" - doexe "${BUILD_DIR}/plugins/vswitch/libvswitch.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-r38" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-r39" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-r40" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-r41" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-nebula-r58" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-hyperspace-r57" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-warp-rest-r56" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-backdrop-r55" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-warp-r53" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-centered-entry-r51" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-center-r50" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-square-r49" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-wheel-r48" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-direct-r47" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-rest-r46" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-tilt-r45" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-stack-r44" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-overview-r43" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/vswitch-held-r39" - doexe "${BUILD_DIR}/plugins/vswitch/libvswitch.so" - dobin "${T}"/wayfire-session - - insinto "/usr/share/wayland-sessions/" - insopts -m644 - doins wayfire.desktop - doins "${T}"/wayfire-session.desktop - - insinto "/usr/share/wayfire/" - doins wayfire.ini - - insinto "/etc" - doins "${FILESDIR}"/wayfire.env - - strip-lto-bytecode -} diff --git a/gui-wm/wayfire/wayfire-0.11.0-r68.ebuild b/gui-wm/wayfire/wayfire-0.11.0-r68.ebuild deleted file mode 100644 index c7c3a2f2584d..000000000000 --- a/gui-wm/wayfire/wayfire-0.11.0-r68.ebuild +++ /dev/null @@ -1,309 +0,0 @@ -# Copyright 2019-2026 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -EAPI=8 - -inherit dot-a meson toolchain-funcs - -DESCRIPTION="compiz like 3D wayland compositor" -HOMEPAGE="https://github.com/WayfireWM/wayfire" - -if [[ ${PV} == 9999 ]]; then - inherit git-r3 - EGIT_REPO_URI="https://github.com/WayfireWM/${PN}.git" - SLOT="0/0.12" - - IUSE="vulkan" - - # vulkan support is experimental and uses a bundled fork of wlroots - # keep it only in the live ebuild for now - RDEPEND="vulkan? ( media-libs/vulkan-loader )" - DEPEND="vulkan? ( dev-util/vulkan-headers )" - COMMON_DEPEND=" - !vulkan? ( - gui-libs/wlroots:0.20[drm(+),libinput(+),x11-backend,X?] - ) - vulkan? ( - dev-libs/libliftoff - media-libs/lcms:2 - media-libs/libdisplay-info:= - media-libs/mesa - sys-auth/seatd:= - x11-libs/libdrm - x11-libs/xcb-util-errors - x11-libs/xcb-util-renderutil - x11-libs/xcb-util-wm - ) - " -else - SRC_URI="https://github.com/WayfireWM/${PN}/releases/download/v${PV}/${P}.tar.xz" - KEYWORDS="~amd64 ~arm64 ~riscv" - SLOT="0/$(ver_cut 1-2)" - - COMMON_DEPEND="gui-libs/wlroots:0.20[drm(+),libinput(+),x11-backend,X?]" -fi - -LICENSE="MIT" -IUSE+=" X +dbus +gles3 nls openmp test" -RESTRICT="!test? ( test )" - -# bundled wlroots has the following dependency string according to included headers. -# wlroots[drm,gles2-renderer,libinput,x11-backend?,X?] -# enable x11-backend with X and vice versa -COMMON_DEPEND+=" - dev-cpp/nlohmann_json - dev-libs/glib:2 - dev-libs/libevdev - dev-libs/libinput:= - dev-libs/wayland - dev-libs/yyjson:= - >=dev-libs/wayland-protocols-1.12 - gui-libs/wf-config:${SLOT} - media-libs/glm - media-libs/libglvnd - media-libs/libjpeg-turbo:= - media-libs/libpng:= - virtual/libudev:= - x11-libs/cairo - x11-libs/libxkbcommon - x11-libs/pango - x11-libs/pixman - dbus? ( sys-apps/dbus ) - X? ( x11-libs/libxcb:= ) -" - -RDEPEND+=" - >=dev-lang/python-3.11 - ${COMMON_DEPEND} - x11-misc/xkeyboard-config -" -DEPEND+=" - ${COMMON_DEPEND} - test? ( dev-cpp/doctest ) -" -BDEPEND=" - dev-util/wayland-scanner - virtual/pkgconfig - openmp? ( - || ( - sys-devel/gcc[openmp] - llvm-runtimes/clang-runtime[openmp] - ) - ) - nls? ( - sys-devel/gettext - ) -" - -PATCHES=( - "${FILESDIR}"/${P}-transparent-cube.patch - "${FILESDIR}"/${P}-rotate-layer-shell.patch - "${FILESDIR}"/${P}-screensaver-layer.patch - "${FILESDIR}"/${P}-desktop-sampling.patch - "${FILESDIR}"/${P}-cube-damage-stacking.patch - "${FILESDIR}"/${P}-cube-panel-overlay.patch - "${FILESDIR}"/${P}-stitched-outputs.patch - "${FILESDIR}"/${P}-virtual-output-polish.patch - "${FILESDIR}"/${P}-wcm-displays.patch - "${FILESDIR}"/${P}-window-scope.patch - "${FILESDIR}"/${P}-float-on-top.patch - "${FILESDIR}"/${P}-physical-tiling.patch - "${FILESDIR}"/${P}-cube-key-repeat.patch - "${FILESDIR}"/${P}-cube-repeat-minimum-20.patch - "${FILESDIR}"/${P}-scope-toggle.patch - "${FILESDIR}"/${P}-dialog-family-stacking.patch - "${FILESDIR}"/${P}-physical-grid.patch - "${FILESDIR}"/${P}-grid-preview.patch - "${FILESDIR}"/${P}-grid-cycle.patch - "${FILESDIR}"/${P}-physical-output-focus.patch - "${FILESDIR}"/${P}-oswitch-local-symbols.patch - "${FILESDIR}"/${P}-oswitch-wrap.patch - "${FILESDIR}"/${P}-popup-physical-scope.patch - "${FILESDIR}"/${P}-move-snapping.patch - "${FILESDIR}"/${P}-move-held-redraw.patch - "${FILESDIR}"/${P}-workspace-backgrounds.patch - "${FILESDIR}"/${P}-show-desktop-workspace.patch - "${FILESDIR}"/${P}-vswitch-held-target.patch - "${FILESDIR}"/${P}-decoration-window-menu.patch - "${FILESDIR}"/${P}-layer-move-old-damage.patch - "${FILESDIR}"/${P}-workspace-grid-events.patch - "${FILESDIR}"/${P}-r35-fullscreen-subpasses.patch - "${FILESDIR}"/${P}-r36-cube-fixed-top.patch - "${FILESDIR}"/${P}-r37-cube-carried-visibility.patch - "${FILESDIR}"/${P}-r38-cube-workspace-lifecycle.patch - "${FILESDIR}"/${P}-r39-cube-workspace-rows.patch - "${FILESDIR}"/${P}-r40-cube-row-translation.patch - "${FILESDIR}"/${P}-r41-cube-complete-row-translation.patch - "${FILESDIR}"/${P}-r42-grid-monitor-window-move.patch - "${FILESDIR}"/${P}-r43-cube-mouse-overview.patch - "${FILESDIR}"/${P}-r44-cube-rigid-stack.patch - "${FILESDIR}"/${P}-r45-cube-stack-tilt.patch - "${FILESDIR}"/${P}-r46-cube-resting-faces.patch - "${FILESDIR}"/${P}-r47-cube-direct-drag.patch - "${FILESDIR}"/${P}-r48-cube-wheel-zoom.patch - "${FILESDIR}"/${P}-r49-cube-square-footprint.patch - "${FILESDIR}"/${P}-r50-cube-center-pivot.patch - "${FILESDIR}"/${P}-r51-cube-centered-entry.patch - "${FILESDIR}"/${P}-r53-cube-warp.patch - "${FILESDIR}"/${P}-r55-cube-backdrop-opacity.patch - "${FILESDIR}"/${P}-r56-cube-warp-rest.patch - "${FILESDIR}"/${P}-r57-cube-hyperspace.patch - "${FILESDIR}"/${P}-r58-cube-nebula.patch - "${FILESDIR}"/${P}-r59-ambient-background.patch - "${FILESDIR}"/${P}-r60-ambient-package-split.patch - "${FILESDIR}"/${P}-r65-output-transforms.patch - "${FILESDIR}"/${P}-drag-escape.patch - "${FILESDIR}"/${P}-r67-grid-gap.patch - "${FILESDIR}"/${P}-r68-ipc-control.patch -) - -# SCREENSAVER changes scene layer indices. Rebuild plugins against the matching -# ABI; wf-config above retains its upstream 0.11 subslot. -SLOT="${SLOT}.screensaver1" - -pkg_pretend() { - [[ ${MERGE_TYPE} != binary ]] && use openmp && tc-check-openmp -} - -pkg_setup() { - [[ ${MERGE_TYPE} != binary ]] && use openmp && tc-check-openmp -} - -src_prepare() { - default - - local dbusrunsession=$(usev dbus dbus-run-session) - sed -e "s:@EPREFIX@:${EPREFIX}:g" -e "s:@DBUS_RUN_SESSION@:${dbusrunsession}:" \ - "${FILESDIR}"/wayfire-session-2 > "${T}"/wayfire-session || die - sed -e "s:@EPREFIX@:${EPREFIX}:" \ - "${FILESDIR}"/wayfire-session.desktop > "${T}"/wayfire-session.desktop || die -} - -src_configure() { - lto-guarantee-fat - - local emesonargs=( - $(meson_feature test tests) - $(meson_feature X xwayland) - $(meson_use gles3 enable_gles32) - $(meson_feature nls build_locales) - $(meson_use openmp enable_openmp) - -Duse_system_wfconfig=enabled - ) - - if [[ ${PV} == 9999 ]]; then - emesonargs+=( - $(meson_feature !vulkan use_system_wlroots) - $(meson_use vulkan vulkan_effects) - ) - else - emesonargs+=( - -Duse_system_wlroots=enabled - -Dvulkan_effects=false - ) - fi - - meson_src_configure -} - -src_install() { - meson_src_install - - exeinto "/usr/$(get_libdir)/wayfire/ipc-control-r68" - doexe "${BUILD_DIR}/plugins/ipc-rules/libipc-rules.so" - doexe "${BUILD_DIR}/plugins/wm-actions/libwm-actions.so" - - exeinto "/usr/$(get_libdir)/wayfire/cube-ambient-r59" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - - # A distinct packaged path permits upgrading a resident cached plugin. - exeinto "/usr/$(get_libdir)/wayfire/wm-actions" - doexe "${BUILD_DIR}/plugins/wm-actions/libwm-actions.so" - exeinto "/usr/$(get_libdir)/wayfire/grid" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-physical" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/move-physical" - doexe "${BUILD_DIR}/plugins/single_plugins/libmove.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-preview" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-cycle" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/oswitch-physical-r24" - doexe "${BUILD_DIR}/plugins/single_plugins/liboswitch.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-move-r27" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - # Legacy activation paths remain valid, now containing ordinary Grid only. - exeinto "/usr/$(get_libdir)/wayfire/grid-occupancy-r54" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-occupancy-r52" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-monitor-r42" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/move-snap-r27" - doexe "${BUILD_DIR}/plugins/single_plugins/libmove.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-gap-r67" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/move-gap-r67" - doexe "${BUILD_DIR}/plugins/single_plugins/libmove.so" - exeinto "/usr/$(get_libdir)/wayfire/move-redraw-r28" - doexe "${BUILD_DIR}/plugins/single_plugins/libmove.so" - exeinto "/usr/$(get_libdir)/wayfire/wm-actions-desktop-r30" - doexe "${BUILD_DIR}/plugins/wm-actions/libwm-actions.so" - exeinto "/usr/$(get_libdir)/wayfire/vswitch-held-r31" - doexe "${BUILD_DIR}/plugins/vswitch/libvswitch.so" - exeinto "/usr/$(get_libdir)/wayfire/vswitch-held-r37" - doexe "${BUILD_DIR}/plugins/vswitch/libvswitch.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-r38" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-r39" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-r40" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-r41" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-nebula-r58" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-hyperspace-r57" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-warp-rest-r56" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-backdrop-r55" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-warp-r53" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-centered-entry-r51" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-center-r50" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-square-r49" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-wheel-r48" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-direct-r47" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-rest-r46" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-tilt-r45" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-stack-r44" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-overview-r43" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/vswitch-held-r39" - doexe "${BUILD_DIR}/plugins/vswitch/libvswitch.so" - dobin "${T}"/wayfire-session - - insinto "/usr/share/wayland-sessions/" - insopts -m644 - doins wayfire.desktop - doins "${T}"/wayfire-session.desktop - - insinto "/usr/share/wayfire/" - doins wayfire.ini - - insinto "/etc" - doins "${FILESDIR}"/wayfire.env - - strip-lto-bytecode -} diff --git a/gui-wm/wayfire/wayfire-0.11.0-r69.ebuild b/gui-wm/wayfire/wayfire-0.11.0-r69.ebuild deleted file mode 100644 index b1300c773ea2..000000000000 --- a/gui-wm/wayfire/wayfire-0.11.0-r69.ebuild +++ /dev/null @@ -1,321 +0,0 @@ -# Copyright 2019-2026 Gentoo Authors -# Distributed under the terms of the GNU General Public License v2 - -EAPI=8 - -inherit dot-a meson toolchain-funcs - -DESCRIPTION="compiz like 3D wayland compositor" -HOMEPAGE="https://github.com/WayfireWM/wayfire" - -if [[ ${PV} == 9999 ]]; then - inherit git-r3 - EGIT_REPO_URI="https://github.com/WayfireWM/${PN}.git" - SLOT="0/0.12" - - IUSE="vulkan" - - # vulkan support is experimental and uses a bundled fork of wlroots - # keep it only in the live ebuild for now - RDEPEND="vulkan? ( media-libs/vulkan-loader )" - DEPEND="vulkan? ( dev-util/vulkan-headers )" - COMMON_DEPEND=" - !vulkan? ( - gui-libs/wlroots:0.20[drm(+),libinput(+),x11-backend,X?] - ) - vulkan? ( - dev-libs/libliftoff - media-libs/lcms:2 - media-libs/libdisplay-info:= - media-libs/mesa - sys-auth/seatd:= - x11-libs/libdrm - x11-libs/xcb-util-errors - x11-libs/xcb-util-renderutil - x11-libs/xcb-util-wm - ) - " -else - SRC_URI="https://github.com/WayfireWM/${PN}/releases/download/v${PV}/${P}.tar.xz" - KEYWORDS="~amd64 ~arm64 ~riscv" - SLOT="0/$(ver_cut 1-2)" - - COMMON_DEPEND="gui-libs/wlroots:0.20[drm(+),libinput(+),x11-backend,X?]" -fi - -LICENSE="MIT" -IUSE+=" X +dbus +gles3 nls openmp test" -RESTRICT="!test? ( test )" - -# bundled wlroots has the following dependency string according to included headers. -# wlroots[drm,gles2-renderer,libinput,x11-backend?,X?] -# enable x11-backend with X and vice versa -COMMON_DEPEND+=" - dev-cpp/nlohmann_json - dev-libs/glib:2 - dev-libs/libevdev - dev-libs/libinput:= - dev-libs/wayland - dev-libs/yyjson:= - >=dev-libs/wayland-protocols-1.12 - gui-libs/wf-config:${SLOT} - media-libs/glm - media-libs/libglvnd - media-libs/libjpeg-turbo:= - media-libs/libpng:= - virtual/libudev:= - x11-libs/cairo - x11-libs/libxkbcommon - x11-libs/pango - x11-libs/pixman - dbus? ( sys-apps/dbus ) - X? ( x11-libs/libxcb:= ) -" - -RDEPEND+=" - >=dev-lang/python-3.11 - ${COMMON_DEPEND} - x11-misc/xkeyboard-config -" -DEPEND+=" - ${COMMON_DEPEND} - test? ( dev-cpp/doctest ) -" -BDEPEND=" - dev-util/wayland-scanner - virtual/pkgconfig - openmp? ( - || ( - sys-devel/gcc[openmp] - llvm-runtimes/clang-runtime[openmp] - ) - ) - nls? ( - sys-devel/gettext - ) -" - -PATCHES=( - "${FILESDIR}"/${P}-transparent-cube.patch - "${FILESDIR}"/${P}-rotate-layer-shell.patch - "${FILESDIR}"/${P}-screensaver-layer.patch - "${FILESDIR}"/${P}-desktop-sampling.patch - "${FILESDIR}"/${P}-cube-damage-stacking.patch - "${FILESDIR}"/${P}-cube-panel-overlay.patch - "${FILESDIR}"/${P}-stitched-outputs.patch - "${FILESDIR}"/${P}-virtual-output-polish.patch - "${FILESDIR}"/${P}-wcm-displays.patch - "${FILESDIR}"/${P}-window-scope.patch - "${FILESDIR}"/${P}-float-on-top.patch - "${FILESDIR}"/${P}-physical-tiling.patch - "${FILESDIR}"/${P}-cube-key-repeat.patch - "${FILESDIR}"/${P}-cube-repeat-minimum-20.patch - "${FILESDIR}"/${P}-scope-toggle.patch - "${FILESDIR}"/${P}-dialog-family-stacking.patch - "${FILESDIR}"/${P}-physical-grid.patch - "${FILESDIR}"/${P}-grid-preview.patch - "${FILESDIR}"/${P}-grid-cycle.patch - "${FILESDIR}"/${P}-physical-output-focus.patch - "${FILESDIR}"/${P}-oswitch-local-symbols.patch - "${FILESDIR}"/${P}-oswitch-wrap.patch - "${FILESDIR}"/${P}-popup-physical-scope.patch - "${FILESDIR}"/${P}-move-snapping.patch - "${FILESDIR}"/${P}-move-held-redraw.patch - "${FILESDIR}"/${P}-workspace-backgrounds.patch - "${FILESDIR}"/${P}-show-desktop-workspace.patch - "${FILESDIR}"/${P}-vswitch-held-target.patch - "${FILESDIR}"/${P}-decoration-window-menu.patch - "${FILESDIR}"/${P}-layer-move-old-damage.patch - "${FILESDIR}"/${P}-workspace-grid-events.patch - "${FILESDIR}"/${P}-r35-fullscreen-subpasses.patch - "${FILESDIR}"/${P}-r36-cube-fixed-top.patch - "${FILESDIR}"/${P}-r37-cube-carried-visibility.patch - "${FILESDIR}"/${P}-r38-cube-workspace-lifecycle.patch - "${FILESDIR}"/${P}-r39-cube-workspace-rows.patch - "${FILESDIR}"/${P}-r40-cube-row-translation.patch - "${FILESDIR}"/${P}-r41-cube-complete-row-translation.patch - "${FILESDIR}"/${P}-r42-grid-monitor-window-move.patch - "${FILESDIR}"/${P}-r43-cube-mouse-overview.patch - "${FILESDIR}"/${P}-r44-cube-rigid-stack.patch - "${FILESDIR}"/${P}-r45-cube-stack-tilt.patch - "${FILESDIR}"/${P}-r46-cube-resting-faces.patch - "${FILESDIR}"/${P}-r47-cube-direct-drag.patch - "${FILESDIR}"/${P}-r48-cube-wheel-zoom.patch - "${FILESDIR}"/${P}-r49-cube-square-footprint.patch - "${FILESDIR}"/${P}-r50-cube-center-pivot.patch - "${FILESDIR}"/${P}-r51-cube-centered-entry.patch - "${FILESDIR}"/${P}-r53-cube-warp.patch - "${FILESDIR}"/${P}-r55-cube-backdrop-opacity.patch - "${FILESDIR}"/${P}-r56-cube-warp-rest.patch - "${FILESDIR}"/${P}-r57-cube-hyperspace.patch - "${FILESDIR}"/${P}-r58-cube-nebula.patch - "${FILESDIR}"/${P}-r59-ambient-background.patch - "${FILESDIR}"/${P}-r60-ambient-package-split.patch - "${FILESDIR}"/${P}-r65-output-transforms.patch - "${FILESDIR}"/${P}-drag-escape.patch - "${FILESDIR}"/${P}-r67-grid-gap.patch - "${FILESDIR}"/${P}-r68-ipc-control.patch - "${FILESDIR}"/${P}-ipc-backpressure.patch -) - -# The local patches change the plugin ABI (scene layer indices, class layouts). -# Tie the subslot to WAYFIRE_API_ABI_VERSION so := consumers rebuild whenever a -# patch bumps it; src_prepare refuses to build if the two drift apart. -# wf-config above retains its upstream 0.11 subslot. -WAYFIRE_ABI="20260918" -SLOT="${SLOT}.abi${WAYFIRE_ABI}" - -pkg_pretend() { - [[ ${MERGE_TYPE} != binary ]] && use openmp && tc-check-openmp -} - -pkg_setup() { - [[ ${MERGE_TYPE} != binary ]] && use openmp && tc-check-openmp -} - -src_prepare() { - default - - local abi - abi=$(sed -n "s/^#define WAYFIRE_API_ABI_VERSION_MACRO \([0-9']*\)$/\1/p" \ - src/api/wayfire/plugin.hpp) || die - abi=${abi//\'/} - if [[ ${abi} != "${WAYFIRE_ABI}" ]]; then - die "WAYFIRE_API_ABI_VERSION is '${abi}' but the subslot says '${WAYFIRE_ABI}'; update WAYFIRE_ABI" - fi - - local dbusrunsession=$(usev dbus dbus-run-session) - sed -e "s:@EPREFIX@:${EPREFIX}:g" -e "s:@DBUS_RUN_SESSION@:${dbusrunsession}:" \ - "${FILESDIR}"/wayfire-session-2 > "${T}"/wayfire-session || die - sed -e "s:@EPREFIX@:${EPREFIX}:" \ - "${FILESDIR}"/wayfire-session.desktop > "${T}"/wayfire-session.desktop || die -} - -src_configure() { - lto-guarantee-fat - - local emesonargs=( - $(meson_feature test tests) - $(meson_feature X xwayland) - $(meson_use gles3 enable_gles32) - $(meson_feature nls build_locales) - $(meson_use openmp enable_openmp) - -Duse_system_wfconfig=enabled - ) - - if [[ ${PV} == 9999 ]]; then - emesonargs+=( - $(meson_feature !vulkan use_system_wlroots) - $(meson_use vulkan vulkan_effects) - ) - else - emesonargs+=( - -Duse_system_wlroots=enabled - -Dvulkan_effects=false - ) - fi - - meson_src_configure -} - -src_install() { - meson_src_install - - exeinto "/usr/$(get_libdir)/wayfire/ipc-control-r68" - doexe "${BUILD_DIR}/plugins/ipc-rules/libipc-rules.so" - doexe "${BUILD_DIR}/plugins/wm-actions/libwm-actions.so" - - exeinto "/usr/$(get_libdir)/wayfire/cube-ambient-r59" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - - # A distinct packaged path permits upgrading a resident cached plugin. - exeinto "/usr/$(get_libdir)/wayfire/wm-actions" - doexe "${BUILD_DIR}/plugins/wm-actions/libwm-actions.so" - exeinto "/usr/$(get_libdir)/wayfire/grid" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-physical" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/move-physical" - doexe "${BUILD_DIR}/plugins/single_plugins/libmove.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-preview" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-cycle" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/oswitch-physical-r24" - doexe "${BUILD_DIR}/plugins/single_plugins/liboswitch.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-move-r27" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - # Legacy activation paths remain valid, now containing ordinary Grid only. - exeinto "/usr/$(get_libdir)/wayfire/grid-occupancy-r54" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-occupancy-r52" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-monitor-r42" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/move-snap-r27" - doexe "${BUILD_DIR}/plugins/single_plugins/libmove.so" - exeinto "/usr/$(get_libdir)/wayfire/grid-gap-r67" - doexe "${BUILD_DIR}/plugins/grid/libgrid.so" - exeinto "/usr/$(get_libdir)/wayfire/move-gap-r67" - doexe "${BUILD_DIR}/plugins/single_plugins/libmove.so" - exeinto "/usr/$(get_libdir)/wayfire/move-redraw-r28" - doexe "${BUILD_DIR}/plugins/single_plugins/libmove.so" - exeinto "/usr/$(get_libdir)/wayfire/wm-actions-desktop-r30" - doexe "${BUILD_DIR}/plugins/wm-actions/libwm-actions.so" - exeinto "/usr/$(get_libdir)/wayfire/vswitch-held-r31" - doexe "${BUILD_DIR}/plugins/vswitch/libvswitch.so" - exeinto "/usr/$(get_libdir)/wayfire/vswitch-held-r37" - doexe "${BUILD_DIR}/plugins/vswitch/libvswitch.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-r38" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-r39" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-r40" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-r41" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-nebula-r58" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-hyperspace-r57" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-warp-rest-r56" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-backdrop-r55" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-warp-r53" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-centered-entry-r51" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-center-r50" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-square-r49" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-wheel-r48" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-direct-r47" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-rest-r46" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-tilt-r45" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-stack-r44" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/cube-overview-r43" - doexe "${BUILD_DIR}/plugins/cube/libcube.so" - exeinto "/usr/$(get_libdir)/wayfire/vswitch-held-r39" - doexe "${BUILD_DIR}/plugins/vswitch/libvswitch.so" - dobin "${T}"/wayfire-session - - insinto "/usr/share/wayland-sessions/" - insopts -m644 - doins wayfire.desktop - doins "${T}"/wayfire-session.desktop - - insinto "/usr/share/wayfire/" - doins wayfire.ini - - insinto "/etc" - doins "${FILESDIR}"/wayfire.env - - strip-lto-bytecode -} |
