From 77edacc9d441131cc3ab0ce54bdb05896b99d57c Mon Sep 17 00:00:00 2001 From: Toast Date: Fri, 17 Jan 2025 22:21:12 +0100 Subject: [PATCH 1/3] Roles/kde: add kwin and plasma-workspace patches --- roles/kde/patches/kwin-pr6985.patch | 41 + .../kde/patches/plasma_workspace-pr4965.patch | 915 ++++++++++++++++++ roles/kde/plasma.nix | 1 + roles/kde/programs/kwin.nix | 1 + 4 files changed, 958 insertions(+) create mode 100644 roles/kde/patches/kwin-pr6985.patch create mode 100644 roles/kde/patches/plasma_workspace-pr4965.patch diff --git a/roles/kde/patches/kwin-pr6985.patch b/roles/kde/patches/kwin-pr6985.patch new file mode 100644 index 0000000..95d196d --- /dev/null +++ b/roles/kde/patches/kwin-pr6985.patch @@ -0,0 +1,41 @@ +From 45a5d8844b36404334301f5da6e75f1a345e0c80 Mon Sep 17 00:00:00 2001 +From: Xaver Hugl +Date: Fri, 10 Jan 2025 13:45:30 +0000 +Subject: [PATCH] plugins/screencast: call ItemRenderer::begin/endFrame + +The OpenGL renderer references the explicit sync release points for client buffers +during rendering, and releases them in endFrame. If endFrame never gets called though +(for example because we're doing direct scanout) then the release points never get +signaled, and the client very quickly runs out of buffers to use and freezes. + +BUG: 495287 + + +(cherry picked from commit b1031ea63eaa8c9bf5c70157d1b6bf8eb0f5a74a) + +Co-authored-by: Xaver Hugl +--- + src/plugins/screencast/windowscreencastsource.cpp | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/plugins/screencast/windowscreencastsource.cpp b/src/plugins/screencast/windowscreencastsource.cpp +index 24ef92aad7b..b396eed46f9 100644 +--- a/src/plugins/screencast/windowscreencastsource.cpp ++++ b/src/plugins/screencast/windowscreencastsource.cpp +@@ -75,11 +75,11 @@ void WindowScreenCastSource::render(GLFramebuffer *target) + RenderTarget renderTarget(target); + RenderViewport viewport(m_window->clientGeometry(), 1, renderTarget); + +- GLFramebuffer::pushFramebuffer(target); ++ Compositor::self()->scene()->renderer()->beginFrame(renderTarget, viewport); + glClearColor(0.0, 0.0, 0.0, 0.0); + glClear(GL_COLOR_BUFFER_BIT); + Compositor::self()->scene()->renderer()->renderItem(renderTarget, viewport, m_window->windowItem(), Scene::PAINT_WINDOW_TRANSFORMED, infiniteRegion(), WindowPaintData{}); +- GLFramebuffer::popFramebuffer(); ++ Compositor::self()->scene()->renderer()->endFrame(); + } + + std::chrono::nanoseconds WindowScreenCastSource::clock() const +-- +GitLab + diff --git a/roles/kde/patches/plasma_workspace-pr4965.patch b/roles/kde/patches/plasma_workspace-pr4965.patch new file mode 100644 index 0000000..f677504 --- /dev/null +++ b/roles/kde/patches/plasma_workspace-pr4965.patch @@ -0,0 +1,915 @@ +diff --git a/appiumtests/applets/CMakeLists.txt b/appiumtests/applets/CMakeLists.txt +index 19229541aa..6715dd1f8f 100644 +--- a/appiumtests/applets/CMakeLists.txt ++++ b/appiumtests/applets/CMakeLists.txt +@@ -59,7 +59,7 @@ add_test( + NAME notificationstest + COMMAND selenium-webdriver-at-spi-run ${CMAKE_CURRENT_SOURCE_DIR}/notificationstest.py --failfast + ) +-set_tests_properties(notificationstest PROPERTIES TIMEOUT 120) ++set_tests_properties(notificationstest PROPERTIES TIMEOUT 120 ENVIRONMENT "KACTIVITYMANAGERD_PATH=${KDE_INSTALL_FULL_LIBEXECDIR}/kactivitymanagerd;USE_CUSTOM_BUS=1") + + add_test( + NAME digitalclocktest +diff --git a/appiumtests/applets/kicker/favoritetest.py b/appiumtests/applets/kicker/favoritetest.py +new file mode 100755 +index 0000000000..50613c3db1 +--- /dev/null ++++ b/appiumtests/applets/kicker/favoritetest.py +@@ -0,0 +1,162 @@ ++#!/usr/bin/env python3 ++ ++# SPDX-License-Identifier: BSD-3-Clause ++# SPDX-FileCopyrightText: 2022-2023 Harald Sitter ++ ++import logging ++import os ++import shutil ++import subprocess ++import sys ++import tempfile ++import time ++import unittest ++from typing import Final ++ ++from gi.repository import Gio, GLib ++ ++sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir, "utils")) ++from GLibMainLoopThread import GLibMainLoopThread ++ ++KDE_VERSION: Final = 6 ++KACTIVITYMANAGERD_SERVICE_NAME: Final = "org.kde.ActivityManager" ++KACTIVITYMANAGERD_PATH: Final = os.getenv("KACTIVITYMANAGERD_PATH", "/usr/libexec/kactivitymanagerd") ++QMLTEST_EXEC: Final = os.getenv("QMLTEST_EXEC", "/usr/bin/qmltestrunner6") ++ ++ ++def name_has_owner(session_bus: Gio.DBusConnection, name: str) -> bool: ++ """ ++ Whether the given name is available on session bus ++ """ ++ message: Gio.DBusMessage = Gio.DBusMessage.new_method_call("org.freedesktop.DBus", "/", "org.freedesktop.DBus", "NameHasOwner") ++ message.set_body(GLib.Variant("(s)", [name])) ++ reply, _ = session_bus.send_message_with_reply_sync(message, Gio.DBusSendMessageFlags.NONE, 1000) ++ return reply and reply.get_signature() == 'b' and reply.get_body().get_child_value(0).get_boolean() ++ ++ ++def build_ksycoca() -> None: ++ subprocess.check_call([f"kbuildsycoca{KDE_VERSION}"], stdout=sys.stderr, stderr=sys.stderr, env=os.environ) ++ ++ ++def start_kactivitymanagerd() -> subprocess.Popen: ++ session_bus: Gio.DBusConnection = Gio.bus_get_sync(Gio.BusType.SESSION) ++ assert not name_has_owner(session_bus, KACTIVITYMANAGERD_SERVICE_NAME) ++ ++ os.makedirs(os.path.join(GLib.get_user_config_dir(), "menus")) ++ shutil.copy(os.path.join(os.path.dirname(os.path.abspath(__file__)), "applications.menu"), os.path.join(GLib.get_user_config_dir(), "menus")) ++ ++ kactivitymanagerd = subprocess.Popen([KACTIVITYMANAGERD_PATH], stdout=sys.stderr, stderr=sys.stderr, env=os.environ) ++ kactivitymanagerd_started: bool = False ++ for _ in range(10): ++ if name_has_owner(session_bus, KACTIVITYMANAGERD_SERVICE_NAME): ++ kactivitymanagerd_started = True ++ break ++ logging.info("waiting for kactivitymanagerd to appear on the DBus session") ++ time.sleep(1) ++ assert kactivitymanagerd_started ++ ++ build_ksycoca() ++ ++ return kactivitymanagerd ++ ++ ++class TestDBusInterface: ++ """ ++ D-Bus interface for org.kde.kickertest ++ """ ++ ++ BUS_NAME: Final = "org.kde.kickertest" ++ OBJECT_PATH: Final = "/test" ++ INTERFACE_NAME: Final = "org.kde.kickertest" ++ ++ connection: Gio.DBusConnection ++ ++ def __init__(self) -> None: ++ self.reg_id: int = 0 ++ self.owner_id: int = Gio.bus_own_name(Gio.BusType.SESSION, self.BUS_NAME, Gio.BusNameOwnerFlags.NONE, self.on_bus_acquired, None, None) ++ assert self.owner_id > 0 ++ ++ def on_bus_acquired(self, connection: Gio.DBusConnection, name: str, *args) -> None: ++ """ ++ The interface is ready, now register objects. ++ """ ++ self.connection = connection ++ introspection_data = Gio.DBusNodeInfo.new_for_xml(""" ++ ++ ++ ++ ++ ++ ++ ++""") ++ self.reg_id = connection.register_object(self.OBJECT_PATH, introspection_data.interfaces[0], self.handle_method_call, None, None) ++ assert self.reg_id > 0 ++ logging.info("interface registered") ++ ++ def handle_method_call(self, connection: Gio.DBusConnection, sender: str, object_path: str, interface_name: str, method_name: str, parameters: GLib.Variant, invocation: Gio.DBusMethodInvocation) -> None: ++ logging.info("method call %s", method_name) ++ ++ if method_name == "DeleteAndRebuildDatabase1": ++ os.remove(KickerTest.desktop_entry_1) ++ build_ksycoca() ++ invocation.return_value(None) ++ elif method_name == "DeleteAndRebuildDatabase2": ++ os.remove(KickerTest.desktop_entry_2) ++ build_ksycoca() ++ invocation.return_value(None) ++ ++ ++class KickerTest(unittest.TestCase): ++ kactivitymanagerd: subprocess.Popen ++ loop_thread: GLibMainLoopThread ++ dbus_interface: TestDBusInterface ++ ++ temp_dir: tempfile.TemporaryDirectory ++ desktop_entry_1: str ++ desktop_entry_2: str ++ ++ @classmethod ++ def setUpClass(cls) -> None: ++ # Prepare desktop files ++ # 1 ++ os.makedirs(os.path.join(GLib.get_user_data_dir(), "applications")) ++ shutil.copy(os.path.join(os.path.dirname(os.path.abspath(__file__)), "kickertest.desktop"), os.path.join(GLib.get_user_data_dir(), "applications")) ++ cls.desktop_entry_1 = os.path.join(GLib.get_user_data_dir(), "applications", "kickertest.desktop") ++ # 2 ++ cls.temp_dir = tempfile.TemporaryDirectory() ++ os.makedirs(os.path.join(cls.temp_dir.name, "applications")) ++ shutil.copy(os.path.join(os.path.dirname(os.path.abspath(__file__)), "kickertest.desktop"), os.path.join(cls.temp_dir.name, "applications")) ++ cls.desktop_entry_2 = os.path.join(cls.temp_dir.name, "applications", "kickertest.desktop") ++ ++ os.environ["LC_ALL"] = "en_US.UTF-8" ++ os.environ["QT_LOGGING_RULES"] = "org.kde.plasma.kicker.debug=true;kf.coreaddons.kdirwatch.debug=true" ++ os.environ["XDG_DATA_DIRS"] = os.environ["XDG_DATA_DIRS"] + ":" + cls.temp_dir.name ++ ++ cls.kactivitymanagerd = start_kactivitymanagerd() ++ ++ cls.loop_thread = GLibMainLoopThread() ++ cls.loop_thread.start() ++ cls.dbus_interface = TestDBusInterface() ++ ++ @classmethod ++ def tearDownClass(cls) -> None: ++ cls.loop_thread.quit() ++ cls.kactivitymanagerd.kill() ++ cls.kactivitymanagerd.wait(10) ++ ++ def test_qml(self) -> None: ++ """ ++ 1. Add an entry to Favorites ++ 2. Remove the entry from Favorites ++ 3. Hide invalid entries automatically and don't crash when there are multiple entries with the same desktop name ++ """ ++ with subprocess.Popen([QMLTEST_EXEC, "-input", os.path.join(os.path.dirname(os.path.abspath(__file__)), 'favoritetest.qml')], stdout=sys.stderr, stderr=sys.stderr) as process: ++ self.assertEqual(process.wait(60), 0) ++ ++ ++if __name__ == '__main__': ++ assert "USE_CUSTOM_BUS" in os.environ ++ logging.getLogger().setLevel(logging.INFO) ++ unittest.main() +diff --git a/appiumtests/applets/notificationstest.py b/appiumtests/applets/notificationstest.py +index 8917121a94..cd34a32905 100755 +--- a/appiumtests/applets/notificationstest.py ++++ b/appiumtests/applets/notificationstest.py +@@ -5,6 +5,7 @@ + + import base64 + import os ++import shutil + import subprocess + import tempfile + import time +@@ -15,6 +16,7 @@ import gi + from appium import webdriver + from appium.options.common.base import AppiumOptions + from appium.webdriver.common.appiumby import AppiumBy ++from selenium.common.exceptions import (NoSuchElementException, WebDriverException) + from selenium.webdriver.support import expected_conditions as EC + from selenium.webdriver.support.ui import WebDriverWait + +@@ -22,6 +24,8 @@ gi.require_version('Gdk', '4.0') + gi.require_version('GdkPixbuf', '2.0') + from gi.repository import Gdk, GdkPixbuf, Gio, GLib + ++from kicker.favoritetest import start_kactivitymanagerd ++ + WIDGET_ID: Final = "org.kde.plasma.notifications" + KDE_VERSION: Final = 6 + +@@ -48,17 +52,25 @@ class NotificationsTest(unittest.TestCase): + """ + + driver: webdriver.Remote ++ kactivitymanagerd: subprocess.Popen + + @classmethod + def setUpClass(cls) -> None: + """ + Opens the widget and initialize the webdriver + """ ++ # Make history work ++ cls.kactivitymanagerd = start_kactivitymanagerd() ++ ++ os.makedirs(os.path.join(GLib.get_user_data_dir(), "knotifications6")) ++ shutil.copy(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, os.pardir, "libnotificationmanager", "libnotificationmanager.notifyrc"), os.path.join(GLib.get_user_data_dir(), "knotifications6")) ++ + options = AppiumOptions() + options.set_capability("app", f"plasmawindowed -p org.kde.plasma.nano {WIDGET_ID}") + options.set_capability("timeouts", {'implicit': 10000}) + options.set_capability("environ", { + "LC_ALL": "en_US.UTF-8", ++ "QT_LOGGING_RULES": "kf.notification*.debug=true;org.kde.plasma.notificationmanager.debug=true", + }) + cls.driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', options=options) + +@@ -75,6 +87,8 @@ class NotificationsTest(unittest.TestCase): + Make sure to terminate the driver again, lest it dangles. + """ + subprocess.check_call([f"kquitapp{KDE_VERSION}", "plasmawindowed"]) ++ cls.kactivitymanagerd.kill() ++ cls.kactivitymanagerd.wait(10) + for _ in range(10): + try: + subprocess.check_call(["pidof", "plasmawindowed"]) +@@ -83,6 +97,15 @@ class NotificationsTest(unittest.TestCase): + time.sleep(1) + cls.driver.quit() + ++ def close_notifications(self) -> None: ++ wait = WebDriverWait(self.driver, 5) ++ for button in self.driver.find_elements(AppiumBy.XPATH, "//button[@name='Close']"): ++ try: ++ button.click() ++ wait.until_not(lambda _: button.is_displayed()) ++ except WebDriverException: ++ pass ++ + def test_0_open(self) -> None: + """ + Tests the widget can be opened +@@ -106,6 +129,10 @@ class NotificationsTest(unittest.TestCase): + + wait = WebDriverWait(self.driver, 5) + wait.until(EC.presence_of_element_located((AppiumBy.NAME, summary))) ++<<<<<<< HEAD ++======= ++ self.close_notifications() ++>>>>>>> 5145d877d8 (applets/notifications: suppress inhibited notifications after "Do not disturb" is off) + + def take_screenshot(self) -> str: + with tempfile.TemporaryDirectory() as temp_dir: +@@ -123,6 +150,7 @@ class NotificationsTest(unittest.TestCase): + partial_pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, 16, 16) + colors = (0xff0000ff, 0x00ff00ff, 0x0000ffff) + for color in colors: ++ logging.info(f"Testing color: {color}") + pixbuf.fill(color) + send_notification({ + "app_name": "Appium Test", +@@ -145,7 +173,29 @@ class NotificationsTest(unittest.TestCase): + wait.until(EC.presence_of_element_located((AppiumBy.NAME, summary + str(color)))) + partial_pixbuf.fill(color) + partial_image = base64.b64encode(Gdk.Texture.new_for_pixbuf(partial_pixbuf).save_to_png_bytes().get_data()).decode() ++<<<<<<< HEAD + self.driver.find_image_occurrence(self.take_screenshot(), partial_image) ++======= ++ try: ++ self.driver.find_image_occurrence(self.take_screenshot(), partial_image) ++ except WebDriverException: # Popup animation ++ self.driver.find_image_occurrence(self.take_screenshot(), partial_image) ++ self.close_notifications() ++ ++ def test_2_notification_with_explicit_timeout(self) -> None: ++ """ ++ Sends notifications with expire_timeout ++ """ ++ summary = "expire_timeout" ++ send_notification({ ++ "app_name": "Appium Test", ++ "summary": summary, ++ "body": "Will it disappear automatically?", ++ "timeout": 2000, ++ }) ++ element = self.driver.find_element(AppiumBy.NAME, summary) ++ WebDriverWait(self.driver, 5).until_not(lambda _: element.is_displayed()) ++>>>>>>> 5145d877d8 (applets/notifications: suppress inhibited notifications after "Do not disturb" is off) + + def test_3_accessible_description_html_to_plaintext(self) -> None: + """ +@@ -157,6 +207,202 @@ class NotificationsTest(unittest.TestCase): + }) + wait = WebDriverWait(self.driver, 5) + wait.until(EC.presence_of_element_located(("description", "biublinkwww.example.org from Appium Test"))) ++<<<<<<< HEAD ++======= ++ self.close_notifications() ++ ++ def test_4_actions(self) -> None: ++ """ ++ When the "actions" key is set, a notification can provide actions. ++ """ ++ loop = GLib.MainLoop() ++ activation_token = False ++ params_1: list[Any] = [] ++ action_invoked = False ++ params_2: list[Any] = [] ++ notification_closed = False ++ params_3: list[Any] = [] ++ ++ def notification_signal_handler(d_bus_proxy: Gio.DBusProxy, sender_name: str, signal_name: str, parameters: GLib.Variant) -> None: ++ nonlocal params_2, params_3, params_1, activation_token, action_invoked, notification_closed ++ logging.info(f"received signal {signal_name}") ++ match signal_name: ++ case "ActivationToken": ++ params_1 = parameters.unpack() ++ activation_token = True ++ case "ActionInvoked": ++ params_2 = parameters.unpack() ++ action_invoked = True ++ case "NotificationClosed": ++ params_3 = parameters.unpack() ++ notification_closed = True ++ loop.quit() ++ ++ connection_id = self.notification_proxy.connect("g-signal", notification_signal_handler) ++ self.addCleanup(lambda: self.notification_proxy.disconnect(connection_id)) ++ ++ notification_id = send_notification({ ++ "app_name": "Appium Test", ++ "body": "A notification with actions", ++ "actions": ["action1", "FooAction", "action2", "BarAction"], ++ }) ++ self.driver.find_element(AppiumBy.NAME, "BarAction") ++ element = self.driver.find_element(AppiumBy.NAME, "FooAction") ++ element.click() ++ loop.run() ++ self.assertTrue(activation_token) ++ self.assertEqual(params_1[0], notification_id) ++ self.assertTrue(action_invoked) ++ self.assertEqual(params_2[0], notification_id) ++ self.assertEqual(params_2[1], "action1") ++ self.assertTrue(notification_closed) ++ self.assertEqual(params_3[0], notification_id) ++ self.assertEqual(params_3[1], 3) # reason: Revoked ++ self.assertFalse(element.is_displayed()) ++ ++ def test_5_inline_reply(self) -> None: ++ """ ++ When the action list has "inline-reply", the notification popup will contain a text field and a reply button. ++ """ ++ loop = GLib.MainLoop() ++ notification_replied = False ++ params: list[Any] = [] # id, text ++ ++ def notification_signal_handler(d_bus_proxy: Gio.DBusProxy, sender_name: str, signal_name: str, parameters: GLib.Variant) -> None: ++ nonlocal params, notification_replied ++ logging.info(f"received signal {signal_name}") ++ if signal_name == "NotificationReplied": ++ params = parameters.unpack() ++ notification_replied = True ++ loop.quit() ++ ++ connection_id = self.notification_proxy.connect("g-signal", notification_signal_handler) ++ self.addCleanup(lambda: self.notification_proxy.disconnect(connection_id)) ++ ++ # When there is only one action and it is a reply action, show text field right away ++ notification_id = send_notification({ ++ "app_name": "Appium Test", ++ "body": "A notification with actions 1", ++ "actions": ["inline-reply", ""], # Use the default label ++ }) ++ reply_text = "this is a reply" ++ self.driver.find_element(AppiumBy.NAME, "begin reply").click() ++ self.driver.find_element(AppiumBy.NAME, "Type a reply…").send_keys(reply_text) ++ element = self.driver.find_element(AppiumBy.NAME, "Send") ++ element.click() ++ loop.run() ++ self.assertTrue(notification_replied) ++ self.assertEqual(params[0], notification_id) ++ self.assertEqual(params[1], reply_text) ++ self.assertFalse(element.is_displayed()) ++ ++ notification_replied = False ++ notification_id = send_notification({ ++ "app_name": "Appium Test", ++ "body": "A notification with actions 2", ++ "actions": ["inline-reply", ""], ++ "hints": { ++ "x-kde-reply-submit-button-text": GLib.Variant("s", "Reeply"), # Use a custom label ++ "x-kde-reply-placeholder-text": GLib.Variant("s", "A placeholder"), # Use a custom placeholder ++ }, ++ }) ++ reply_text = "this is another reply" ++ self.driver.find_element(AppiumBy.NAME, "begin reply").click() ++ self.driver.find_element(AppiumBy.NAME, "A placeholder").send_keys(reply_text) ++ element = self.driver.find_element(AppiumBy.NAME, "Reeply") ++ element.click() ++ loop.run() ++ self.assertTrue(notification_replied) ++ self.assertEqual(params[0], notification_id) ++ self.assertEqual(params[1], reply_text) ++ self.assertFalse(element.is_displayed()) ++ ++ notification_replied = False ++ notification_id = send_notification({ ++ "app_name": "Appium Test", ++ "body": "A notification with actions 3", ++ "actions": ["inline-reply", "Replyy", "foo", "Foo", "bar", "Bar"], # Click to show the text field ++ }) ++ self.driver.find_element(AppiumBy.NAME, "Foo") ++ self.driver.find_element(AppiumBy.NAME, "Bar") ++ element = self.driver.find_element(AppiumBy.NAME, "Replyy") ++ element.click() ++ reply_text = "Click Replyy to reply" ++ self.driver.find_element(AppiumBy.NAME, "Type a reply…").send_keys(reply_text) ++ self.assertFalse(element.is_displayed()) ++ element = self.driver.find_element(AppiumBy.NAME, "Send") ++ element.click() ++ loop.run() ++ self.assertTrue(notification_replied) ++ self.assertEqual(params[0], notification_id) ++ self.assertEqual(params[1], reply_text) ++ ++ def test_6_thumbnail(self) -> None: ++ """ ++ When a notification has "x-kde-urls" hint, a thumbnail will be shown for the first url in the list ++ """ ++ with tempfile.TemporaryDirectory() as temp_dir: ++ pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, 256, 256) ++ colors = (0xff0000ff, 0x00ff00ff, 0x0000ffff) ++ for color in colors: ++ pixbuf.fill(color) ++ pixbuf.savev(os.path.join(temp_dir, f"{str(color)}.png"), "png") ++ ++ url_list = [f"file://{os.path.join(temp_dir, path)}" for path in os.listdir(temp_dir)] ++ url_list.sort() ++ send_notification({ ++ "app_name": "Appium Test", ++ "body": "Thumbnail", ++ "hints": { ++ "x-kde-urls": GLib.Variant("as", url_list), ++ }, ++ "timeout": 10 * 1000, ++ }) ++ ++ self.driver.find_element(AppiumBy.NAME, "More Options…") ++ ++ partial_pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, 100, 100) ++ partial_pixbuf.fill(colors[1]) # Green is the first item ++ partial_image = base64.b64encode(Gdk.Texture.new_for_pixbuf(partial_pixbuf).save_to_png_bytes().get_data()).decode() ++ ++ def match_image(driver) -> bool: ++ try: ++ self.driver.find_image_occurrence(self.take_screenshot(), partial_image) ++ return True ++ except WebDriverException: ++ return False ++ ++ WebDriverWait(self.driver, 10).until(match_image) ++ self.close_notifications() ++ ++ def test_7_do_not_disturb(self) -> None: ++ """ ++ Suppress inhibited notifications after "Do not disturb" is turned off, and show a summary for unread inhibited notifications. ++ """ ++ self.driver.find_element(AppiumBy.NAME, "Do not disturb").click() ++ dnd_button = self.driver.find_element(AppiumBy.XPATH, "//*[@name='Do not disturb' and contains(@states, 'checked')]") ++ ++ summary = "Do not disturb me" ++ for i in range(2): ++ send_notification({ ++ "app_name": "Appium Test", ++ "summary": summary + str(i), ++ "hints": { ++ "desktop-entry": GLib.Variant("s", "org.kde.plasmashell"), ++ }, ++ "timeout": 60 * 1000, ++ }) ++ title = self.driver.find_element(AppiumBy.XPATH, f"//heading[starts-with(@name, '{summary}') and contains(@accessibility-id, 'FullRepresentation')]") ++ self.assertRaises(NoSuchElementException, self.driver.find_element, AppiumBy.XPATH, f"//notification[starts-with(@name, '{summary}')]") ++ ++ dnd_button.click() ++ self.driver.find_element(AppiumBy.XPATH, "//notification[@name='Unread Notifications' and @description='2 notifications were received while Do Not Disturb was active. from Notification Manager']") ++ self.driver.find_element(AppiumBy.XPATH, "//button[@name='Close' and contains(@accessibility-id, 'NotificationPopup')]").click() ++ ++ # Notifications can only be cleared after they are expired, otherwise they will stay in the list ++ self.driver.find_element(AppiumBy.NAME, "Clear All Notifications").click() ++ WebDriverWait(self.driver, 5).until_not(lambda _: title.is_displayed()) ++>>>>>>> 5145d877d8 (applets/notifications: suppress inhibited notifications after "Do not disturb" is off) + + + if __name__ == '__main__': +diff --git a/applets/notifications/package/contents/ui/FullRepresentation.qml b/applets/notifications/package/contents/ui/FullRepresentation.qml +index f949dad46a..33838599f6 100644 +--- a/applets/notifications/package/contents/ui/FullRepresentation.qml ++++ b/applets/notifications/package/contents/ui/FullRepresentation.qml +@@ -71,6 +71,14 @@ PlasmaExtras.Representation { + checkable: true + checked: Globals.inhibited + ++ Accessible.onPressAction: if (Globals.inhibited) { ++ Globals.revokeInhibitions(); ++ } else { ++ let date = new Date(); ++ date.setFullYear(date.getFullYear() + 1); ++ notificationSettings.notificationsInhibitedUntil = date; ++ notificationSettings.save(); ++ } + KeyNavigation.down: list + KeyNavigation.tab: list + +diff --git a/applets/notifications/package/contents/ui/global/Globals.qml b/applets/notifications/package/contents/ui/global/Globals.qml +index c46c32921a..2238653eff 100644 +--- a/applets/notifications/package/contents/ui/global/Globals.qml ++++ b/applets/notifications/package/contents/ui/global/Globals.qml +@@ -34,6 +34,10 @@ QtObject { + property bool inhibited: false + + onInhibitedChanged: { ++ if (!inhibited) { ++ popupNotificationsModel.showInhibitionSummary(); ++ } ++ + var pa = pulseAudio.item; + if (!pa) { + return; +@@ -405,6 +409,7 @@ QtObject { + limit: plasmoid ? (Math.ceil(globals.screenRect.height / (Kirigami.Units.gridUnit * 4))) : 0 + showExpired: false + showDismissed: false ++ showAddedDuringInhibition: false + blacklistedDesktopEntries: notificationSettings.popupBlacklistedApplications + blacklistedNotifyRcNames: notificationSettings.popupBlacklistedServices + whitelistedDesktopEntries: globals.inhibited ? notificationSettings.doNotDisturbPopupWhitelistedApplications : [] +@@ -613,9 +618,13 @@ QtObject { + onKillJobClicked: popupNotificationsModel.killJob(popupNotificationsModel.index(index, 0)) + + // popup width is fixed +- onHeightChanged: positionPopups() ++ onHeightChanged: globals.positionPopups() + + Component.onCompleted: { ++ if (globals.inhibited) { ++ model.wasAddedDuringInhibition = false; // Don't count already shown notifications ++ } ++ + if (model.type === NotificationManager.Notifications.NotificationType && model.desktopEntry) { + // Register apps that were seen spawning a popup so they can be configured later + // Apps with notifyrc can already be configured anyway +diff --git a/libnotificationmanager/CMakeLists.txt b/libnotificationmanager/CMakeLists.txt +index d04d8a4a24..5c48653ede 100644 +--- a/libnotificationmanager/CMakeLists.txt ++++ b/libnotificationmanager/CMakeLists.txt +@@ -84,6 +84,7 @@ target_link_libraries(notificationmanager + KF6::I18n + KF6::WindowSystem + KF6::ItemModels # KDescendantsProxyModel ++ KF6::Notifications # Inhibition summary + KF6::KIOFileWidgets + Plasma::Plasma + KF6::Screen +@@ -135,3 +136,6 @@ install(EXPORT notificationmanagerLibraryTargets + + install(FILES plasmanotifyrc + DESTINATION ${KDE_INSTALL_CONFDIR}) ++ ++install(FILES libnotificationmanager.notifyrc ++ DESTINATION ${KDE_INSTALL_KNOTIFYRCDIR}) +diff --git a/libnotificationmanager/abstractnotificationsmodel.cpp b/libnotificationmanager/abstractnotificationsmodel.cpp +index 8bd55bc26c..8307a1e8f7 100644 +--- a/libnotificationmanager/abstractnotificationsmodel.cpp ++++ b/libnotificationmanager/abstractnotificationsmodel.cpp +@@ -104,6 +104,7 @@ void AbstractNotificationsModel::Private::onNotificationReplaced(uint replacedId + newNotification.setExpired(oldNotification.expired()); + newNotification.setDismissed(oldNotification.dismissed()); + newNotification.setRead(oldNotification.read()); ++ newNotification.setWasAddedDuringInhibition(Server::self().inhibited()); + + notifications[row] = newNotification; + const QModelIndex idx = q->index(row, 0); +@@ -378,6 +379,9 @@ QVariant AbstractNotificationsModel::data(const QModelIndex &index, int role) co + case Notifications::TransientRole: + return notification.transient(); + ++ case Notifications::WasAddedDuringInhibitionRole: ++ return notification.wasAddedDuringInhibition(); ++ + case Notifications::HasReplyActionRole: + return notification.hasReplyAction(); + case Notifications::ReplyActionLabelRole: +@@ -416,6 +420,12 @@ bool AbstractNotificationsModel::setData(const QModelIndex &index, const QVarian + dirty = true; + } + break; ++ case Notifications::WasAddedDuringInhibitionRole: ++ if (bool v = value.toBool(); v != notification.wasAddedDuringInhibition()) { ++ notification.setWasAddedDuringInhibition(v); ++ dirty = true; ++ } ++ break; + } + + if (dirty) { +@@ -471,7 +481,7 @@ void AbstractNotificationsModel::clear(Notifications::ClearFlags flags) + for (int i = 0; i < d->notifications.count(); ++i) { + const Notification ¬ification = d->notifications.at(i); + +- if (flags.testFlag(Notifications::ClearExpired) && notification.expired()) { ++ if (flags.testFlag(Notifications::ClearExpired) && (notification.expired() || notification.wasAddedDuringInhibition())) { + close(notification.id()); + } + } +diff --git a/libnotificationmanager/libnotificationmanager.notifyrc b/libnotificationmanager/libnotificationmanager.notifyrc +new file mode 100644 +index 0000000000..79304e62bc +--- /dev/null ++++ b/libnotificationmanager/libnotificationmanager.notifyrc +@@ -0,0 +1,11 @@ ++# SPDX-License-Identifier: CC0-1.0 ++# SPDX-FileCopyrightText: None ++ ++[Global] ++Name=Notification Manager ++IconName=preferences-desktop-notification-bell ++ ++[Event/inhibitionSummary] ++Name=Summary for unread inhibited notifications ++Action=Popup ++Urgency=Low +diff --git a/libnotificationmanager/notification.cpp b/libnotificationmanager/notification.cpp +index 276611310c..320c837617 100644 +--- a/libnotificationmanager/notification.cpp ++++ b/libnotificationmanager/notification.cpp +@@ -826,3 +826,13 @@ void Notification::processHints(const QVariantMap &hints) + { + d->processHints(hints); + } ++ ++bool Notification::wasAddedDuringInhibition() const ++{ ++ return d->wasAddedDuringInhibition; ++} ++ ++void Notification::setWasAddedDuringInhibition(bool value) ++{ ++ d->wasAddedDuringInhibition = value; ++} +diff --git a/libnotificationmanager/notification.h b/libnotificationmanager/notification.h +index 9f04a4e200..8613534fd2 100644 +--- a/libnotificationmanager/notification.h ++++ b/libnotificationmanager/notification.h +@@ -131,6 +131,9 @@ public: + + void processHints(const QVariantMap &hints); + ++ bool wasAddedDuringInhibition() const; ++ void setWasAddedDuringInhibition(bool value); ++ + private: + friend class NotificationsModel; + friend class AbstractNotificationsModel; +diff --git a/libnotificationmanager/notification_p.h b/libnotificationmanager/notification_p.h +index 6cae23c21e..923bde7882 100644 +--- a/libnotificationmanager/notification_p.h ++++ b/libnotificationmanager/notification_p.h +@@ -96,6 +96,8 @@ public: + + bool resident = false; + bool transient = false; ++ ++ bool wasAddedDuringInhibition = false; + }; + + } // namespace NotificationManager +diff --git a/libnotificationmanager/notificationfilterproxymodel.cpp b/libnotificationmanager/notificationfilterproxymodel.cpp +index 98a32b6645..bb573713af 100644 +--- a/libnotificationmanager/notificationfilterproxymodel.cpp ++++ b/libnotificationmanager/notificationfilterproxymodel.cpp +@@ -58,6 +58,20 @@ void NotificationFilterProxyModel::setShowDismissed(bool show) + } + } + ++bool NotificationFilterProxyModel::showAddedDuringInhibition() const ++{ ++ return m_showDismissed; ++} ++ ++void NotificationFilterProxyModel::setShowAddedDuringInhibition(bool show) ++{ ++ if (m_showAddedDuringInhibition != show) { ++ m_showAddedDuringInhibition = show; ++ invalidateFilter(); ++ Q_EMIT showAddedDuringInhibitionChanged(); ++ } ++} ++ + QStringList NotificationFilterProxyModel::blacklistedDesktopEntries() const + { + return m_blacklistedDesktopEntries; +@@ -177,5 +191,9 @@ bool NotificationFilterProxyModel::filterAcceptsRow(int source_row, const QModel + } + } + ++ if (!m_showAddedDuringInhibition && sourceIdx.data(Notifications::WasAddedDuringInhibitionRole).toBool()) { ++ return false; ++ } ++ + return true; + } +diff --git a/libnotificationmanager/notificationfilterproxymodel_p.h b/libnotificationmanager/notificationfilterproxymodel_p.h +index 4029320e8e..af04a9fac1 100644 +--- a/libnotificationmanager/notificationfilterproxymodel_p.h ++++ b/libnotificationmanager/notificationfilterproxymodel_p.h +@@ -30,6 +30,9 @@ public: + bool showDismissed() const; + void setShowDismissed(bool show); + ++ bool showAddedDuringInhibition() const; ++ void setShowAddedDuringInhibition(bool show); ++ + QStringList blacklistedDesktopEntries() const; + void setBlackListedDesktopEntries(const QStringList &blacklist); + +@@ -46,6 +49,7 @@ Q_SIGNALS: + void urgenciesChanged(); + void showExpiredChanged(); + void showDismissedChanged(); ++ void showAddedDuringInhibitionChanged(); + void blacklistedDesktopEntriesChanged(); + void blacklistedNotifyRcNamesChanged(); + void whitelistedDesktopEntriesChanged(); +@@ -58,6 +62,7 @@ private: + Notifications::Urgencies m_urgencies = Notifications::LowUrgency | Notifications::NormalUrgency | Notifications::CriticalUrgency; + bool m_showDismissed = false; + bool m_showExpired = false; ++ bool m_showAddedDuringInhibition = true; + + QStringList m_blacklistedDesktopEntries; + QStringList m_blacklistedNotifyRcNames; +diff --git a/libnotificationmanager/notifications.cpp b/libnotificationmanager/notifications.cpp +index 9b3e18018b..3d5662f926 100644 +--- a/libnotificationmanager/notifications.cpp ++++ b/libnotificationmanager/notifications.cpp +@@ -12,6 +12,8 @@ + #include + + #include ++#include ++#include + + #include "limitedrowcountproxymodel_p.h" + #include "notificationfilterproxymodel_p.h" +@@ -30,6 +32,7 @@ + + #include "debug.h" + ++using namespace Qt::StringLiterals; + using namespace NotificationManager; + + class Q_DECL_HIDDEN Notifications::Private +@@ -166,6 +169,7 @@ void Notifications::Private::initProxyModels() + connect(filterModel, &NotificationFilterProxyModel::urgenciesChanged, q, &Notifications::urgenciesChanged); + connect(filterModel, &NotificationFilterProxyModel::showExpiredChanged, q, &Notifications::showExpiredChanged); + connect(filterModel, &NotificationFilterProxyModel::showDismissedChanged, q, &Notifications::showDismissedChanged); ++ connect(filterModel, &NotificationFilterProxyModel::showAddedDuringInhibitionChanged, q, &Notifications::showAddedDuringInhibitionChanged); + connect(filterModel, &NotificationFilterProxyModel::blacklistedDesktopEntriesChanged, q, &Notifications::blacklistedDesktopEntriesChanged); + connect(filterModel, &NotificationFilterProxyModel::blacklistedNotifyRcNamesChanged, q, &Notifications::blacklistedNotifyRcNamesChanged); + +@@ -245,7 +249,7 @@ void Notifications::Private::updateCount() + for (int i = 0; i < filterModel->rowCount(); ++i) { + const QModelIndex idx = filterModel->index(i, 0); + +- if (idx.data(Notifications::ExpiredRole).toBool()) { ++ if (idx.data(Notifications::ExpiredRole).toBool() || idx.data(Notifications::WasAddedDuringInhibitionRole).toBool()) { + ++expired; + } else { + ++active; +@@ -477,6 +481,16 @@ void Notifications::setShowDismissed(bool show) + d->filterModel->setShowDismissed(show); + } + ++bool Notifications::showAddedDuringInhibition() const ++{ ++ return d->filterModel->showAddedDuringInhibition(); ++} ++ ++void Notifications::setShowAddedDuringInhibition(bool show) ++{ ++ d->filterModel->setShowAddedDuringInhibition(show); ++} ++ + QStringList Notifications::blacklistedDesktopEntries() const + { + return d->filterModel->blacklistedDesktopEntries(); +@@ -812,6 +826,28 @@ void Notifications::collapseAllGroups() + } + } + ++void Notifications::showInhibitionSummary() ++{ ++ int inhibited = 0; ++ for (int i = 0, count = d->notificationsAndJobsModel->rowCount(); i < count; ++i) { ++ const QModelIndex idx = d->notificationsAndJobsModel->index(i, 0); ++ if (!idx.data(Notifications::ReadRole).toBool() && idx.data(Notifications::WasAddedDuringInhibitionRole).toBool()) { ++ ++inhibited; ++ } ++ } ++ ++ if (!inhibited) { ++ return; ++ } ++ ++ KNotification::event(u"inhibitionSummary"_s, ++ i18nc("@title", "Unread Notifications"), ++ i18nc("@info", "%1 notifications were received while Do Not Disturb was active.", QString::number(inhibited)), ++ u"preferences-desktop-notification-bell"_s, ++ KNotification::CloseOnTimeout, ++ u"libnotificationmanager"_s); ++} ++ + QVariant Notifications::data(const QModelIndex &index, int role) const + { + return QSortFilterProxyModel::data(index, role); +diff --git a/libnotificationmanager/notifications.h b/libnotificationmanager/notifications.h +index edb898988f..ef500b0c7b 100644 +--- a/libnotificationmanager/notifications.h ++++ b/libnotificationmanager/notifications.h +@@ -61,6 +61,15 @@ class NOTIFICATIONMANAGER_EXPORT Notifications : public QSortFilterProxyModel, p + */ + Q_PROPERTY(bool showDismissed READ showDismissed WRITE setShowDismissed NOTIFY showDismissedChanged) + ++ /** ++ * Whether to show notifications added during inhibition. ++ * ++ * If set to @c false, notifications are suppressed even after leaving "Do not disturb" mode. ++ * ++ * Default is @c true. ++ */ ++ Q_PROPERTY(bool showAddedDuringInhibition READ showAddedDuringInhibition WRITE setShowAddedDuringInhibition NOTIFY showAddedDuringInhibitionChanged) ++ + /** + * A list of desktop entries for which no notifications should be shown. + * +@@ -285,6 +294,8 @@ public: + ///< notification in a certain way, or group notifications of similar types. @since 5.21 + ResidentRole, ///< Whether the notification should keep its actions even when they were invoked. @since 5.22 + TransientRole, ///< Whether the notification is transient and should not be kept in history. @since 5.22 ++ ++ WasAddedDuringInhibitionRole, ///< Whether the notification was added while inhibition was active. @since 6.3 + }; + Q_ENUM(Roles) + +@@ -371,6 +382,9 @@ public: + bool showDismissed() const; + void setShowDismissed(bool show); + ++ bool showAddedDuringInhibition() const; ++ void setShowAddedDuringInhibition(bool show); ++ + QStringList blacklistedDesktopEntries() const; + void setBlacklistedDesktopEntries(const QStringList &blacklist); + +@@ -529,6 +543,11 @@ public: + + Q_INVOKABLE void collapseAllGroups(); + ++ /** ++ * Shows a notification to report the number of unread inhibited notifications. ++ */ ++ Q_INVOKABLE void showInhibitionSummary(); ++ + QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; +@@ -541,6 +560,7 @@ Q_SIGNALS: + void limitChanged(); + void showExpiredChanged(); + void showDismissedChanged(); ++ void showAddedDuringInhibitionChanged(); + void blacklistedDesktopEntriesChanged(); + void blacklistedNotifyRcNamesChanged(); + void whitelistedDesktopEntriesChanged(); +diff --git a/libnotificationmanager/server_p.cpp b/libnotificationmanager/server_p.cpp +index 84fe37afa9..66cd621033 100644 +--- a/libnotificationmanager/server_p.cpp ++++ b/libnotificationmanager/server_p.cpp +@@ -164,6 +164,7 @@ uint ServerPrivate::Notify(const QString &app_name, + notification.setActions(actions); + + notification.setTimeout(timeout); ++ notification.setWasAddedDuringInhibition(m_inhibited); + + // might override some of the things we set above (like application name) + notification.d->processHints(hints); diff --git a/roles/kde/plasma.nix b/roles/kde/plasma.nix index 8339715..1f0f5db 100644 --- a/roles/kde/plasma.nix +++ b/roles/kde/plasma.nix @@ -54,6 +54,7 @@ in { kPrev.plasma-workspace.patches ++ [ ./patches/plasma_workspace-pr4883.patch + ./patches/plasma_workspace-pr4965.patch ]; }; plasma-desktop = kPrev.plasma-desktop.overrideAttrs { diff --git a/roles/kde/programs/kwin.nix b/roles/kde/programs/kwin.nix index e7dd2b7..f6b176b 100644 --- a/roles/kde/programs/kwin.nix +++ b/roles/kde/programs/kwin.nix @@ -11,6 +11,7 @@ ../patches/kwin-pr6406.patch ../patches/kwin-pr6878.patch ../patches/kwin-pr6844.patch + ../patches/kwin-pr6985.patch ]; }; } From 2f970835b257943bc550dec6f1304c57ecc2db40 Mon Sep 17 00:00:00 2001 From: Toast Date: Sat, 18 Jan 2025 00:27:52 +0100 Subject: [PATCH 2/3] Flake: remove unneeded nixpkgs patch --- flake.nix | 4 +-- .../fix-endlessh-dns-resolution.patch | 31 ------------------- 2 files changed, 1 insertion(+), 34 deletions(-) delete mode 100644 nixpkgs-patches/fix-endlessh-dns-resolution.patch diff --git a/flake.nix b/flake.nix index f05c44b..3bdb7f1 100644 --- a/flake.nix +++ b/flake.nix @@ -105,9 +105,7 @@ nixpkgs-patched = nixpkgs-raw.legacyPackages.x86_64-linux.applyPatches { name = "patched-nixpkgs"; src = nixpkgs-raw; - patches = [ - ./nixpkgs-patches/fix-endlessh-dns-resolution.patch - ]; + patches = []; }; # https://discourse.nixos.org/t/proper-way-of-applying-patch-to-system-managed-via-flake/21073/26 nixpkgs-unstable = (import "${nixpkgs-unstable-patched}/flake.nix").outputs {self = inputs.nixpkgs-unstable-raw;}; diff --git a/nixpkgs-patches/fix-endlessh-dns-resolution.patch b/nixpkgs-patches/fix-endlessh-dns-resolution.patch deleted file mode 100644 index dc55d97..0000000 --- a/nixpkgs-patches/fix-endlessh-dns-resolution.patch +++ /dev/null @@ -1,31 +0,0 @@ -From 99778e1bb590c984d91f39322e57d59220402c59 Mon Sep 17 00:00:00 2001 -From: azahi -Date: Wed, 8 Jan 2025 13:47:09 +0300 -Subject: [PATCH] nixos/endlessh-go: fix DNS resolution - -As suggested by toast003[1]. - -[1]: https://github.com/shizunge/endlessh-go/discussions/127#discussioncomment-11760912 ---- - nixos/modules/services/security/endlessh-go.nix | 8 +++++++- - 1 file changed, 7 insertions(+), 1 deletion(-) - -diff --git a/nixos/modules/services/security/endlessh-go.nix b/nixos/modules/services/security/endlessh-go.nix -index b8b51acc81d0e..5c69d412a7d3c 100644 ---- a/nixos/modules/services/security/endlessh-go.nix -+++ b/nixos/modules/services/security/endlessh-go.nix -@@ -110,7 +110,13 @@ in - ); - DynamicUser = true; - RootDirectory = rootDirectory; -- BindReadOnlyPaths = [ builtins.storeDir ]; -+ BindReadOnlyPaths = [ -+ builtins.storeDir -+ "-/etc/hosts" -+ "-/etc/localtime" -+ "-/etc/nsswitch.conf" -+ "-/etc/resolv.conf" -+ ]; - InaccessiblePaths = [ "-+${rootDirectory}" ]; - RuntimeDirectory = baseNameOf rootDirectory; - RuntimeDirectoryMode = "700"; From 02fcfb5008996666022231d5358179e5167aafd1 Mon Sep 17 00:00:00 2001 From: Toast Date: Sat, 18 Jan 2025 00:28:22 +0100 Subject: [PATCH 3/3] Flake: update lock file --- flake.lock | 144 ++++++++++++++++++++++++++--------------------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/flake.lock b/flake.lock index 40c00a4..5345bb4 100644 --- a/flake.lock +++ b/flake.lock @@ -10,11 +10,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1723293904, - "narHash": "sha256-b+uqzj+Wa6xgMS9aNbX4I+sXeb5biPDi39VgvSFqFvU=", + "lastModified": 1736955230, + "narHash": "sha256-uenf8fv2eG5bKM8C/UvFaiJMZ4IpUFaQxk9OH5t/1gA=", "owner": "ryantm", "repo": "agenix", - "rev": "f6291c5935fdc4e0bef208cfc0dcab7e3f7a1c41", + "rev": "e600439ec4c273cf11e06fe4d9d906fb98fa097c", "type": "github" }, "original": { @@ -34,11 +34,11 @@ "nuscht-search": "nuscht-search" }, "locked": { - "lastModified": 1735895235, - "narHash": "sha256-MtsfkMikkPjnZUMqsqXQ29cHzlRD2lMe27jXua28cHU=", + "lastModified": 1736957255, + "narHash": "sha256-qZZ/K5XheRMjCNYgle90QESuys0PIFJNPJJswMJ0GEA=", "owner": "catppuccin", "repo": "nix", - "rev": "b326f48f17023fc0060590ba299d55f7da8350a5", + "rev": "f06fcadf9a61b6581b392e72f230fa6783fe36e4", "type": "github" }, "original": { @@ -79,12 +79,12 @@ }, "catppuccin-v1_2": { "locked": { - "lastModified": 1734728407, - "narHash": "sha256-Let3uJo4YDyfqbqaw66dpZxhJB2TrDyZWSFd5rpPLJA=", - "rev": "23ee86dbf4ed347878115a78971d43025362fab1", - "revCount": 341, + "lastModified": 1734734291, + "narHash": "sha256-CFX4diEQHKvZYjnhf7TLg20m3ge1O4vqgplsk/Kuaek=", + "rev": "1e4c3803b8da874ff75224ec8512cb173036bbd8", + "revCount": 344, "type": "tarball", - "url": "https://api.flakehub.com/f/pinned/catppuccin/nix/1.2.0/0193e5e0-33b7-7149-a362-bfe56b20f64e/source.tar.gz" + "url": "https://api.flakehub.com/f/pinned/catppuccin/nix/1.2.1/0193e646-1107-7f69-a402-f2a3988ecf1d/source.tar.gz" }, "original": { "type": "tarball", @@ -94,11 +94,11 @@ "eza-themes": { "flake": false, "locked": { - "lastModified": 1735244910, - "narHash": "sha256-sfQLMjtAcYTLAA664v6CaYlkoBCPtykhzSQCrcMEA2c=", + "lastModified": 1736468386, + "narHash": "sha256-K9k+jE27kK8PlN6BbTfMLhCagnWCc6CIO1lkqvWl9fE=", "owner": "eza-community", "repo": "eza-themes", - "rev": "39c1a6640ceaaa4b59f354e1cb229c292a199e68", + "rev": "266feb8d373ac201bd28d7522e4e65cb2865f082", "type": "github" }, "original": { @@ -241,11 +241,11 @@ ] }, "locked": { - "lastModified": 1734366194, - "narHash": "sha256-vykpJ1xsdkv0j8WOVXrRFHUAdp9NXHpxdnn1F4pYgSw=", + "lastModified": 1736373539, + "narHash": "sha256-dinzAqCjenWDxuy+MqUQq0I4zUSfaCvN9rzuCmgMZJY=", "owner": "nix-community", "repo": "home-manager", - "rev": "80b0fdf483c5d1cb75aaad909bd390d48673857f", + "rev": "bd65bc3cde04c16755955630b344bc9e35272c56", "type": "github" }, "original": { @@ -262,11 +262,11 @@ ] }, "locked": { - "lastModified": 1735900408, - "narHash": "sha256-U+oZBQ3f5fF2hHsupKQH4ihgTKLHgcJh6jEmKDg+W10=", + "lastModified": 1737120639, + "narHash": "sha256-p5e/45V41YD3tMELuiNIoVCa25/w4nhOTm0B9MtdHFI=", "owner": "nix-community", "repo": "home-manager", - "rev": "1c8d4c8d592e8fab4cff4397db5529ec6f078cf9", + "rev": "a0046af169ce7b1da503974e1b22c48ef4d71887", "type": "github" }, "original": { @@ -283,11 +283,11 @@ ] }, "locked": { - "lastModified": 1734622215, - "narHash": "sha256-OOfI0XhSJGHblfdNDhfnn8QnZxng63rWk9eeJ2tCbiI=", + "lastModified": 1736508663, + "narHash": "sha256-ZOaGwa+WnB7Zn3YXimqjmIugAnHePdXCmNu+AHkq808=", "owner": "nix-community", "repo": "home-manager", - "rev": "1395379a7a36e40f2a76e7b9936cc52950baa1be", + "rev": "2532b500c3ed2b8940e831039dcec5a5ea093afc", "type": "github" }, "original": { @@ -303,11 +303,11 @@ ] }, "locked": { - "lastModified": 1735344290, - "narHash": "sha256-oJDtWPH1oJT34RJK1FSWjwX4qcGOBRkcNQPD0EbSfNM=", + "lastModified": 1736373539, + "narHash": "sha256-dinzAqCjenWDxuy+MqUQq0I4zUSfaCvN9rzuCmgMZJY=", "owner": "nix-community", "repo": "home-manager", - "rev": "613691f285dad87694c2ba1c9e6298d04736292d", + "rev": "bd65bc3cde04c16755955630b344bc9e35272c56", "type": "github" }, "original": { @@ -353,11 +353,11 @@ ] }, "locked": { - "lastModified": 1735330405, - "narHash": "sha256-MhXgu1oymyjhhZGY9yewNonJknNAjilzMGPY1FfMR7s=", + "lastModified": 1737126697, + "narHash": "sha256-k1YhjONkiKBHzbjNy4ZsjysBac5UJSolCVq9cTKLeKM=", "owner": "Jovian-Experiments", "repo": "Jovian-NixOS", - "rev": "a86d9cf841eff8b33a05d2bf25788abd8e018dbd", + "rev": "27a0ddac1a14e10ba98530f59db728951495f2ce", "type": "github" }, "original": { @@ -369,11 +369,11 @@ "lix": { "flake": false, "locked": { - "lastModified": 1735572323, - "narHash": "sha256-Wjt+PK15IdaOUoI0sgsRzaNMxKQGSROLe9sOd44+fwM=", - "rev": "5c7ea4f446de58aa64f78087bb4ec26b9c4111aa", + "lastModified": 1737086202, + "narHash": "sha256-33tcNERQt1R7tr/W7pvQj8R6NJktyTOKMIfY16RHLK8=", + "rev": "0d14c2b67a407825e3427bf079b31de069d9653a", "type": "tarball", - "url": "https://git.lix.systems/api/v1/repos/lix-project/lix/archive/5c7ea4f446de58aa64f78087bb4ec26b9c4111aa.tar.gz?rev=5c7ea4f446de58aa64f78087bb4ec26b9c4111aa" + "url": "https://git.lix.systems/api/v1/repos/lix-project/lix/archive/0d14c2b67a407825e3427bf079b31de069d9653a.tar.gz?rev=0d14c2b67a407825e3427bf079b31de069d9653a" }, "original": { "type": "tarball", @@ -428,11 +428,11 @@ }, "nix-flatpak": { "locked": { - "lastModified": 1735913600, - "narHash": "sha256-370z+WLVnD7LrN/SvTCZxPl/XPTshS5NS2dHN4iyK6o=", + "lastModified": 1736952876, + "narHash": "sha256-dJXuLP2CBkIG333L+Rb3e1D0oXHYbl0MgmKPGuvFuAI=", "owner": "gmodena", "repo": "nix-flatpak", - "rev": "78ed84ff81e8d8510926e7165d508bcacef49ff1", + "rev": "b6966d5fa96b0fae99a4da0b5bdfbb0a75f5c058", "type": "github" }, "original": { @@ -466,11 +466,11 @@ }, "nix-impermanence": { "locked": { - "lastModified": 1734945620, - "narHash": "sha256-olIfsfJK4/GFmPH8mXMmBDAkzVQ1TWJmeGT3wBGfQPY=", + "lastModified": 1736688610, + "narHash": "sha256-1Zl9xahw399UiZSJ9Vxs1W4WRFjO1SsNdVZQD4nghz0=", "owner": "nix-community", "repo": "impermanence", - "rev": "d000479f4f41390ff7cf9204979660ad5dd16176", + "rev": "c64bed13b562fc3bb454b48773d4155023ac31b7", "type": "github" }, "original": { @@ -486,11 +486,11 @@ ] }, "locked": { - "lastModified": 1735443188, - "narHash": "sha256-AydPpRBh8+NOkrLylG7vTsHrGO2b5L7XkMEL5HlzcA8=", + "lastModified": 1736652904, + "narHash": "sha256-8uolHABgroXqzs03QdulHp8H9e5kWQZnnhcda1MKbBM=", "owner": "Mic92", "repo": "nix-index-database", - "rev": "55ab1e1df5daf2476e6b826b69a82862dcbd7544", + "rev": "271e5bd7c57e1f001693799518b10a02d1123b12", "type": "github" }, "original": { @@ -506,11 +506,11 @@ ] }, "locked": { - "lastModified": 1735443188, - "narHash": "sha256-AydPpRBh8+NOkrLylG7vTsHrGO2b5L7XkMEL5HlzcA8=", + "lastModified": 1736652904, + "narHash": "sha256-8uolHABgroXqzs03QdulHp8H9e5kWQZnnhcda1MKbBM=", "owner": "Mic92", "repo": "nix-index-database", - "rev": "55ab1e1df5daf2476e6b826b69a82862dcbd7544", + "rev": "271e5bd7c57e1f001693799518b10a02d1123b12", "type": "github" }, "original": { @@ -521,11 +521,11 @@ }, "nixos-hardware": { "locked": { - "lastModified": 1735388221, - "narHash": "sha256-e5IOgjQf0SZcFCEV/gMGrsI0gCJyqOKShBQU0iiM3Kg=", + "lastModified": 1736978406, + "narHash": "sha256-oMr3PVIQ8XPDI8/x6BHxsWEPBRU98Pam6KGVwUh8MPk=", "owner": "NixOS", "repo": "nixos-hardware", - "rev": "7c674c6734f61157e321db595dbfcd8523e04e19", + "rev": "b678606690027913f3434dea3864e712b862dde5", "type": "github" }, "original": { @@ -537,11 +537,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1734424634, - "narHash": "sha256-cHar1vqHOOyC7f1+tVycPoWTfKIaqkoe1Q6TnKzuti4=", + "lastModified": 1736012469, + "narHash": "sha256-/qlNWm/IEVVH7GfgAIyP6EsVZI6zjAx1cV5zNyrs+rI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d3c42f187194c26d9f0309a8ecc469d6c878ce33", + "rev": "8f3e1f807051e32d8c95cd12b9b421623850a34d", "type": "github" }, "original": { @@ -553,11 +553,11 @@ }, "nixpkgs-raw": { "locked": { - "lastModified": 1735669367, - "narHash": "sha256-tfYRbFhMOnYaM4ippqqid3BaLOXoFNdImrfBfCp4zn0=", + "lastModified": 1736916166, + "narHash": "sha256-puPDoVKxkuNmYIGMpMQiK8bEjaACcCksolsG36gdaNQ=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "edf04b75c13c2ac0e54df5ec5c543e300f76f1c9", + "rev": "e24b4c09e963677b1beea49d411cd315a024ad3a", "type": "github" }, "original": { @@ -568,11 +568,11 @@ }, "nixpkgs-stable": { "locked": { - "lastModified": 1734600368, - "narHash": "sha256-nbG9TijTMcfr+au7ZVbKpAhMJzzE2nQBYmRvSdXUD8g=", + "lastModified": 1736061677, + "narHash": "sha256-DjkQPnkAfd7eB522PwnkGhOMuT9QVCZspDpJJYyOj60=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b47fd6fa00c6afca88b8ee46cfdb00e104f50bca", + "rev": "cbd8ec4de4469333c82ff40d057350c30e9f7d36", "type": "github" }, "original": { @@ -584,11 +584,11 @@ }, "nixpkgs-unstable-raw": { "locked": { - "lastModified": 1735834308, - "narHash": "sha256-dklw3AXr3OGO4/XT1Tu3Xz9n/we8GctZZ75ZWVqAVhk=", + "lastModified": 1737062831, + "narHash": "sha256-Tbk1MZbtV2s5aG+iM99U8FqwxU/YNArMcWAv6clcsBc=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "6df24922a1400241dae323af55f30e4318a6ca65", + "rev": "5df43628fdf08d642be8ba5b3625a6c70731c19c", "type": "github" }, "original": { @@ -599,11 +599,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1735471104, - "narHash": "sha256-0q9NGQySwDQc7RhAV2ukfnu7Gxa5/ybJ2ANT8DQrQrs=", + "lastModified": 1736883708, + "narHash": "sha256-uQ+NQ0/xYU0N1CnXsa2zghgNaOPxWpMJXSUJJ9W7140=", "owner": "nixos", "repo": "nixpkgs", - "rev": "88195a94f390381c6afcdaa933c2f6ff93959cb4", + "rev": "eb62e6aa39ea67e0b8018ba8ea077efe65807dc8", "type": "github" }, "original": { @@ -620,11 +620,11 @@ "treefmt-nix": "treefmt-nix" }, "locked": { - "lastModified": 1735920791, - "narHash": "sha256-DPP/ioDJ283YMxU+U+URmTPECHLrFRUcXG2pbKA/MNY=", + "lastModified": 1737147292, + "narHash": "sha256-y5ALxyA2en4I81mwdeJgatg0u/SWi0LMAv+uakJ9czE=", "owner": "nix-community", "repo": "NUR", - "rev": "3af14b22ef7fa1eeb169840d9ee3010a43ed61ea", + "rev": "a55bbc7cf3f8772ef6274a32520ce117f32f9d0e", "type": "github" }, "original": { @@ -643,11 +643,11 @@ ] }, "locked": { - "lastModified": 1733773348, - "narHash": "sha256-Y47y+LesOCkJaLvj+dI/Oa6FAKj/T9sKVKDXLNsViPw=", + "lastModified": 1735854821, + "narHash": "sha256-Iv59gMDZajNfezTO0Fw6LHE7uKAShxbvMidmZREit7c=", "owner": "NuschtOS", "repo": "search", - "rev": "3051be7f403bff1d1d380e4612f0c70675b44fc9", + "rev": "836908e3bddd837ae0f13e215dd48767aee355f0", "type": "github" }, "original": { @@ -666,11 +666,11 @@ ] }, "locked": { - "lastModified": 1735049224, - "narHash": "sha256-fWUd9kyXdepphJ7cCzOsuSo7l0kbFCkUqfgKqZyFZzE=", + "lastModified": 1736549395, + "narHash": "sha256-XzwkB62Tt5UYoL1jXiHzgk/qz2fUpGHExcSIbyGTtI0=", "owner": "nix-community", "repo": "plasma-manager", - "rev": "d16bbded0ae452bc088489e7dca3ef58d8d1830b", + "rev": "a53af7f1514ef4cce8620a9d6a50f238cdedec8b", "type": "github" }, "original": {