PcBeaconAgent is a remote control solution for Windows PCs. A lightweight agent runs on the PC and exposes a local Web API + SignalR hub over the LAN. An Android client discovers the agent via UDP broadcast, pairs via a one-time PIN, and then can:
- Switch audio output devices β list playback devices, change the default output, without touching the Windows sound settings.
- Control displays β list connected monitors, disable a specific display, restore the original topology. The server correctly handles primary-display promotion and clone/extend modes.
- Monitor online status β the client sees which PCs are reachable in real time, and control buttons disable automatically when a device goes offline.
The server is a self-contained .NET 10 single-file executable. The Android client is a .NET MAUI app. All traffic is plain HTTP (LAN-only); TLS is planned for a future release.
The PcBeaconAgent.Server.Cli is a console host that runs the Web API
and SignalR hub. The PcBeaconAgent.Server.Tray is a WPF tray host
that runs the same business logic and adds a system tray icon, a PIN
popup with countdown, and balloon notifications. Both share the same
Server.Core β pick the one that fits your deployment:
- Server.Cli β interactive console host for debugging and scripted use. Detailed documentation: docs/server-cli.md.
- Server.Tray β interactive desktop sessions; auto-starts on user login (planned), shows the PIN in a popup next to the taskbar. Detailed documentation: docs/server-tray.md.
For background or scripted execution, the CLI host can suppress terminal output:
./PcBeaconAgent.Server.Cli.exe --no-console
# or:
./PcBeaconAgent.Server.Cli.exe --silent
See docs/server-cli.md for the full CLI reference (appsettings.json schema, log file location, interactive-only notes).
The solution is a monorepo with five projects arranged in a strict
layering. The dependency arrows never point from server to client β
both shared libraries depend on Contracts, never on each other.
Contracts (DTOs, ProjectJsonContext β no dependencies)
β β
β β
Client.Core Server.Core
β β
β β
Android Server.Cli β’ Server.Tray
PcBeaconAgent.Contractsβ wire contracts only (DTOs,BeaconDevice,ProjectJsonContext). No dependencies beyond the BCL. Referenced by bothClient.CoreandServer.Core.PcBeaconAgent.Client.Coreβ client-side business logic and UI support types (ManagedDevice,SignalService,AudioServiceClient,DisplayServiceClient,PairingServiceClient,BeaconClient). ReferencesContracts.PcBeaconAgent.Server.Coreβ server-side business logic (BeaconServiceHub,BeaconServer,BeaconServerIdentity,PairingService,DisplayController,AudioController). Holds theFrameworkReference Microsoft.AspNetCore.Appand the Windows-only NuGet packages (WindowsDisplayAPI,AudioSwitcher). ReferencesContracts.PcBeaconAgent.Server.Cliβ the console host (composition root, HTTP endpoint mapping,BeaconBackgroundService). Interactive-only β the controllers require a desktop session, so it cannot run as a Windows Service. ReferencesServer.Core. See docs/server-cli.md.PcBeaconAgent.Server.Trayβ the WPF tray host. Runs the sameServer.Corebusiness logic and adds a system tray icon, a PIN popup with countdown, and balloon notifications viaINotificationService. Suitable for interactive desktop sessions. ReferencesServer.Core. See docs/server-tray.md.PcBeaconAgent.Client.Androidβ the .NET MAUI Android client. ReferencesClient.Core.
Data Flow & Security: The architecture follows a clear "Discovery-to-RPC" lifecycle to maintain security while ensuring network flexibility.
- UDP Beacon (Discovery): The scanner identifies network devices
via UDP broadcast. At this stage, only raw
IP:Portmetadata is available. This phase is unauthenticated. - SignalR RPC (Request-Response): Once the user initiates a
connection ("Remember"), the client establishes a persistent SignalR
connection. Instead of event-based push patterns, the system uses
authenticated RPC calls (
InvokeAsync) to fetch device metadata. This ensures deterministic state management and tight control over data exposure. - Storage Sync & Indexing: Device identities are persisted via
IDeviceStorageService. The client maintains a thread-safe index of stored API keys, allowing for granular management and preventing data corruption during concurrent pairing operations. - Identity Tracking: The
BeaconDevicemodel utilizes robustEquals/GetHashCodeoverrides to ensure consistent identity tracking even if the network configuration changes.
The system utilizes a secure, PIN-based pairing mechanism to ensure that only authorized clients can access device metadata or control signals. The pairing process is designed with the "Principle of Least Privilege":
- No Secret Exposure: Discovery over UDP carries only
IP:Port. API keys are never broadcasted. - Isolated Trust: Each client stores an IP-scoped key. Compromising one device does not grant access to others.
- Non-Persistent Discovery: Listing devices does not require credentials; authentication is only triggered when a user explicitly initiates a connection ("Remember").
- Key Lifecycle Management: The client maintains a local index of stored API keys, allowing for granular "Forget" operations. This ensures that when a device is removed, its associated credentials are fully purged from the local Android Keystore, preventing "orphaned" trust entries.
For a technical deep-dive into the state machine, failure modes, and cryptographic storage isolation, refer to the PIN Pairing Algorithm Reference.
- Single-File Executable: The service compiles into a single, self-contained
.exefile. - Trimmed: All unused code is automatically removed during compilation to optimize production binary size.
- Self-Contained: The .NET 10 runtime is packed inside the executable, meaning no external .NET SDK installation is required on the target machine.
The project utilizes automated deployment pipelines configured via GitHub Actions. A single release.v.* tag publishes both the server (Cli + Tray) and the Android client in one unified release.
To trigger a release, push a tag matching the naming convention below. The version number X.Y.Z must follow the versioning rules defined in CONTRIBUTING.md.
| Component | Tag Pattern | Target Workflow | Release Name Example |
|---|---|---|---|
| All (Server + Client) | release.v.X.Y.Z |
publish-all.yml |
Release X.Y.Z |
π‘ Branch & Tag Isolation Note: Git tags point directly to a specific commit, completely independent of branches. You can safely create and push release tags from development branches (e.g.,
devel). GitHub Actions will check out and compile the exact commit historical snapshot bound to that tag, provided that the corresponding workflow.ymlfile exists within that commit.
β οΈ Important: The version stringX.Y.Zmust follow strict semantic versioning numbers (e.g.,2.0.0). Do not add extra prefixes or suffixes, otherwise the tag parsing engine in the pipeline will fail.
-
Commit and push all your changes to the remote branch (e.g.,
devel). -
Create and push the release tag:
git tag release.v.2.0.0 git push origin release.v.2.0.0 -
Navigate to the Actions tab of your GitHub repository to monitor the live build logs.
- Pipeline Output: Both hosts compile to standalone, native Windows
x64 single-file executables, each packed inside its own
.ziparchive. - Compilation Flags:
Server.Cliuses trimming (-p:PublishTrimmed=true);Server.Trayis published without trimming β WPF XAML bindings trim unreliably. - Metadata Extraction: The pipeline strips the
release.v.prefix and injects the rawX.Y.Zvalue into both executables'VersionandAssemblyVersionproperties.
-
Pipeline Output: A standalone
.apkpackage. -
Version Code Calculation: Android requires a monotonically increasing integer for its
versionCode. The pipeline computes this from the tag:VersionCode = (Major Γ 1,000,000) + (Minor Γ 1,000) + BuildExample: Tag
release.v.2.0.0results in:versionCode=2,000,000versionName=2.0.0
We follow the Conventional Commits specification to maintain a clean project history and enable automated changelog generation.
- Before contributing, please review the CONTRIBUTING.md file for details on commit message formats, types, and scopes.
- For coding conventions (naming, logging, DI, async, JSON), see the Coding Guidelines.
- For the project roadmap and planned features, see the Roadmap.
- For test structure and what is covered, see Testing.