# SPDX-License-Identifier: LGPL-2.1-or-later #--------------------------------------------------------------- # winrt (Windows) module for a standalone python-3.6 AirPlay Service-Discovery Bluetooth LE beacon for UxPlay # (c) F. Duncanh, March 2026 # Import WinRT APIs (see https://pypi.org/project/winrt-Windows.Foundation.Collections/) try: import winrt.windows.foundation.collections except ImportError: print(f"ImportError from winrt-Windows.Foundation.Collections") print(f"Install with 'pip install winrt-Windows.Foundation'") print(f"and with 'pip install winrt-Windows.Foundation.Collections'") print(f'You may need to use pip option "--break-system-packages" (disregard the warning)') raise SystemExit(1) try: import winrt.windows.devices.bluetooth.advertisement as ble_adv except ImportError: print(f"ImportError from winrt-Windows.Devices.Bluetooth.Advertisement") print(f"Install with 'pip install winrt-Windows.Devices.Bluetooth.Advertisement'") print(f'You may need to use pip option "--break-system-packages" (disregard the warning)') raise SystemExit(1) try: import winrt.windows.storage.streams as streams except ImportError: print(f"ImportError from winrt-Windows.Storage.Streams") print(f"Install with 'pip install winrt-Windows.Storage.Streams'") print(f'You may need to use pip option "--break-system-packages" (disregard the warning)') raise SystemExit(1) import os import asyncio import ipaddress from typing import Literal from typing import Optional #global variables publisher = None advertised_port = None advertised_address = None quiet = False def on_status_changed(sender, args): global publisher if not quiet: print(f"Publisher status change to: {args.status.name}") if args.status.name == "ABORTED": print(f'Publisher was aborted after starting: perhaps no Bluetooth interface is available?') print(f'Stopping') os._exit(1) if args.status.name == "STOPPED": publisher = None def create_airplay_service_discovery_advertisement_publisher(ipv4_str, port): global publisher global advertised_port global advertised_address assert port > 0 assert port <= 65535 mfg_data = bytearray([0x09, 0x08, 0x13, 0x30]) # Apple Data Unit type 9 (Airplay), length 8, flags 0001 0011, seed 30 ipv4_address = ipaddress.ip_address(ipv4_str) ipv4 = bytearray(ipv4_address.packed) mfg_data.extend(ipv4) port_bytes = port.to_bytes(2, 'big') mfg_data.extend(port_bytes) writer = streams.DataWriter() writer.write_bytes(mfg_data) manufacturer_data = ble_adv.BluetoothLEManufacturerData() manufacturer_data.company_id = 0x004C #Apple manufacturer_data.data = writer.detach_buffer() advertisement = ble_adv.BluetoothLEAdvertisement() advertisement.manufacturer_data.append(manufacturer_data) publisher = ble_adv.BluetoothLEAdvertisementPublisher(advertisement) advertised_port = port advertised_address = ipv4_str publisher.add_status_changed(on_status_changed) async def publish_advertisement(): global advertised_port global advertised_address try: publisher.start() if not quiet: print(f"AirPlay Service_Discovery Advertisement ({advertised_address}:{advertised_port}) registered") except Exception as e: print(f"Failed to start Publisher: {e}") print(f"Publisher Status: {publisher.status.name}") advertised_address = None advertised_port = None def setup_beacon(ipv4_str: str, port:int , advmin: Literal[None], advmax :Literal[None], index :Literal[None]) ->bool: global quiet quiet = False if port == 1: #fake port used for testing print(f'beacon test') quiet = True if (advmin is not None) or (advmax is not None) or (index is not None): raise ValueError('uxplay_beacon_module_winrt: advmin, advmax, index were not all None') create_airplay_service_discovery_advertisement_publisher(ipv4_str, port) return True def beacon_on() -> Optional[int]: global publisher global advertised_port try: asyncio.run(publish_advertisement()) except Exception as e: print(f"Failed to start publisher: {e}") publisher = None #advertised_port is set to None if publish_advertisement failed return advertised_port def beacon_off(): global advertised_port global advertised_address publisher.stop() advertised_port = None advertised_address = None print(f'loaded uxplay_beacon_module_winrt')