File indexing completed on 2024-12-15 05:06:15
0001 #!/usr/bin/env python3 0002 0003 # SPDX-FileCopyrightText: 2023 Fushan Wen <qydwhotmail@gmail.com> 0004 # SPDX-License-Identifier: GPL-2.0-or-later 0005 0006 # pylint: disable=line-too-long 0007 0008 # For FreeBSD CI which only has python 3.9 0009 from __future__ import annotations 0010 0011 import json 0012 import os 0013 import subprocess 0014 import sys 0015 import time 0016 import unittest 0017 from typing import Final 0018 0019 assert "appiumtests" in os.getcwd(), "Make sure the current directory is appiumtests" 0020 sys.path.append(os.getcwd()) # for appiumtests.utils 0021 0022 from gi.repository import Gio, GLib 0023 from utils.GLibMainLoopThread import GLibMainLoopThread 0024 from utils.mediaplayer import (Mpris2, read_base_properties, read_player_metadata, read_player_properties) 0025 0026 KDE_VERSION: Final = 6 0027 SERVICE_NAME: Final = "org.freedesktop.DBus" 0028 ROOT_OBJECT_PATH: Final = "/" 0029 INTERFACE_NAME: Final = SERVICE_NAME 0030 KGLOBALACCELD_PATH: str = "" 0031 0032 0033 def name_has_owner(session_bus: Gio.DBusConnection, name: str) -> bool: 0034 """ 0035 Whether the given name is available on session bus 0036 """ 0037 message: Gio.DBusMessage = Gio.DBusMessage.new_method_call(SERVICE_NAME, ROOT_OBJECT_PATH, INTERFACE_NAME, "NameHasOwner") 0038 message.set_body(GLib.Variant("(s)", [name])) 0039 reply, _ = session_bus.send_message_with_reply_sync(message, Gio.DBusSendMessageFlags.NONE, 1000) 0040 return reply and reply.get_signature() == 'b' and reply.get_body().get_child_value(0).get_boolean() 0041 0042 0043 class MediaKeysTest(unittest.TestCase): 0044 """ 0045 Tests for the media global shortcuts 0046 0047 @see https://bugs.kde.org/show_bug.cgi?id=474531 0048 """ 0049 0050 loop_thread: GLibMainLoopThread 0051 mpris_interface: Mpris2 0052 kded: subprocess.Popen | None = None 0053 kglobalacceld: subprocess.Popen | None = None 0054 0055 @classmethod 0056 def setUpClass(cls) -> None: 0057 cls.loop_thread = GLibMainLoopThread() 0058 cls.loop_thread.start() 0059 0060 # Doc: https://lazka.github.io/pgi-docs/Gio-2.0/classes/DBusConnection.html 0061 session_bus: Gio.DBusConnection = Gio.bus_get_sync(Gio.BusType.SESSION) 0062 0063 # Start KGlobalAccel daemon service 0064 if not name_has_owner(session_bus, "org.kde.kglobalaccel"): 0065 cls.kglobalacceld = subprocess.Popen([KGLOBALACCELD_PATH]) 0066 0067 if not name_has_owner(session_bus, f"org.kde.kded{KDE_VERSION}"): 0068 cls.kded = subprocess.Popen([f"kded{KDE_VERSION}"]) 0069 kded_started: bool = False 0070 for _ in range(10): 0071 if name_has_owner(session_bus, f"org.kde.kded{KDE_VERSION}"): 0072 kded_started = True 0073 break 0074 print(f"waiting for kded{KDE_VERSION} to appear on the dbus session") 0075 time.sleep(1) 0076 cls.assertTrue(kded_started, "kded is not started") 0077 0078 cls.assertTrue(name_has_owner(session_bus, "org.kde.kglobalaccel"), "kglobalacceld is not started") 0079 0080 kded_reply: GLib.Variant = session_bus.call_sync(f"org.kde.kded{KDE_VERSION}", "/kded", f"org.kde.kded{KDE_VERSION}", "loadModule", GLib.Variant("(s)", ["mprisservice"]), GLib.VariantType("(b)"), Gio.DBusSendMessageFlags.NONE, 1000) 0081 cls.assertTrue(kded_reply.get_child_value(0).get_boolean(), "mprisservice module is not loaded") 0082 0083 json_path: str = os.path.join(os.getcwd(), "resources/player_a.json") 0084 with open(json_path, "r", encoding="utf-8") as f: 0085 json_dict: dict[str, list | dict] = json.load(f) 0086 metadata: list[dict[str, GLib.Variant]] = read_player_metadata(json_dict) 0087 base_properties: dict[str, GLib.Variant] = read_base_properties(json_dict) 0088 current_index: int = 1 0089 player_properties: dict[str, GLib.Variant] = read_player_properties(json_dict, metadata[current_index]) 0090 cls.mpris_interface = Mpris2(metadata, base_properties, player_properties, current_index) 0091 cls.mpris_interface.registered_event.wait(timeout=10) 0092 time.sleep(1) # Make sure kded receives the player 0093 0094 @classmethod 0095 def tearDownClass(cls) -> None: 0096 cls.mpris_interface.quit() 0097 cls.loop_thread.quit() 0098 if cls.kded: 0099 cls.kded.terminate() 0100 if cls.kglobalacceld: 0101 cls.kglobalacceld.terminate() 0102 0103 def setUp(self) -> None: 0104 pass 0105 0106 def tearDown(self) -> None: 0107 pass 0108 0109 def test_1_playpause(self) -> None: 0110 """ 0111 Global shortcut for "Play/Pause media playback" 0112 """ 0113 self.assertEqual(self.mpris_interface.player_properties["PlaybackStatus"].get_string(), "Stopped") 0114 subprocess.check_call(["xdotool", "key", "XF86AudioPlay"]) 0115 self.assertTrue(self.mpris_interface.playback_status_set_event.wait(timeout=10)) 0116 self.mpris_interface.playback_status_set_event.clear() 0117 self.assertEqual(self.mpris_interface.player_properties["PlaybackStatus"].get_string(), "Playing") 0118 subprocess.check_call(["xdotool", "key", "XF86AudioPlay"]) 0119 self.assertTrue(self.mpris_interface.playback_status_set_event.wait(timeout=10)) 0120 self.mpris_interface.playback_status_set_event.clear() 0121 self.assertEqual(self.mpris_interface.player_properties["PlaybackStatus"].get_string(), "Paused") 0122 0123 def test_2_next(self) -> None: 0124 """ 0125 Global shortcut for "Media playback next" 0126 """ 0127 subprocess.check_call(["xdotool", "key", "XF86AudioNext"]) 0128 self.assertTrue(self.mpris_interface.metadata_updated_event.wait(10)) 0129 self.mpris_interface.metadata_updated_event.clear() 0130 self.assertEqual(self.mpris_interface.player_properties["Metadata"]["xesam:title"], "Konqi's Favorite") 0131 0132 # Press again to test canNext 0133 subprocess.check_call(["xdotool", "key", "XF86AudioNext"]) 0134 time.sleep(0.5) 0135 self.assertEqual(self.mpris_interface.player_properties["Metadata"]["xesam:title"], "Konqi's Favorite") 0136 0137 def test_3_previous(self) -> None: 0138 """ 0139 Global shortcut for "Media playback previous" 0140 """ 0141 subprocess.check_call(["xdotool", "key", "XF86AudioPrev"]) 0142 self.assertTrue(self.mpris_interface.metadata_updated_event.wait(10)) 0143 self.mpris_interface.metadata_updated_event.clear() 0144 self.assertEqual(self.mpris_interface.player_properties["Metadata"]["xesam:title"], "Konqi ❤️️ Katie") 0145 0146 subprocess.check_call(["xdotool", "key", "XF86AudioPrev"]) 0147 self.assertTrue(self.mpris_interface.metadata_updated_event.wait(10)) 0148 self.mpris_interface.metadata_updated_event.clear() 0149 self.assertEqual(self.mpris_interface.player_properties["Metadata"]["xesam:title"], "Katie's Favorite") 0150 0151 # Press again to test canPrevious 0152 subprocess.check_call(["xdotool", "key", "XF86AudioPrev"]) 0153 time.sleep(0.5) 0154 self.assertEqual(self.mpris_interface.player_properties["Metadata"]["xesam:title"], "Katie's Favorite") 0155 0156 def test_4_unload_mprisservice(self) -> None: 0157 """ 0158 Unload mprisservice to make sure the default keyboard shortcuts don't take effect 0159 """ 0160 if self.kded is None and "KDECI_BUILD" not in os.environ: 0161 self.skipTest(f"kded{KDE_VERSION} is not run by this test") 0162 0163 session_bus: Gio.DBusConnection = Gio.bus_get_sync(Gio.BusType.SESSION) 0164 kded_reply: GLib.Variant = session_bus.call_sync(f"org.kde.kded{KDE_VERSION}", "/kded", f"org.kde.kded{KDE_VERSION}", "unloadModule", GLib.Variant("(s)", ["mprisservice"]), GLib.VariantType("(b)"), Gio.DBusSendMessageFlags.NONE, 1000) 0165 self.assertTrue(kded_reply.get_child_value(0).get_boolean(), "mprisservice module is not loaded") 0166 0167 last_playback_status: str = self.mpris_interface.player_properties["PlaybackStatus"].get_string() 0168 subprocess.check_call(["xdotool", "key", "XF86AudioPlay"]) 0169 time.sleep(0.5) 0170 self.assertEqual(self.mpris_interface.player_properties["PlaybackStatus"].get_string(), last_playback_status) 0171 0172 last_xesam_title: str = self.mpris_interface.player_properties["Metadata"]["xesam:title"] 0173 subprocess.check_call(["xdotool", "key", "XF86AudioNext"]) 0174 time.sleep(0.5) 0175 self.assertEqual(self.mpris_interface.player_properties["Metadata"]["xesam:title"], last_xesam_title) 0176 0177 0178 if __name__ == '__main__': 0179 assert len(sys.argv) >= 2, "kglobalacceld is not provided" 0180 KGLOBALACCELD_PATH = sys.argv.pop() 0181 unittest.main()