├── .assets ├── appicon-tile.pdn ├── appicon-tile.png ├── appicon.icns ├── appicon.ico ├── appicon.png ├── appicon.svg ├── favicon.ico └── screenshots │ ├── desktop_details-row.png │ ├── desktop_remote-control.png │ ├── desktop_terminal.png │ ├── desktop_windows-sessions.png │ └── mobile_windows-sessions.png ├── .azure-pipelines ├── default.yml └── relay-server.yml ├── .build └── Build.ps1 ├── .devcontainer └── devcontainer.json ├── .dockerignore ├── .editorconfig ├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── media │ ├── controlr-logo.png │ └── controlr1.jpg ├── pull_request_template.md └── workflows │ ├── README.md │ ├── build-and-deploy.yml │ └── relay-server.yml ├── .gitignore ├── .gitmodules ├── .idea └── .idea.ControlR │ └── .idea │ ├── .gitignore │ ├── codeStyles │ └── codeStyleConfig.xml │ ├── encodings.xml │ ├── indexLayout.xml │ └── vcs.xml ├── .run ├── Agent (Run).run.xml ├── Server (https).run.xml ├── Server + Agent + Docker.run.xml ├── Server + Docker.run.xml └── compose (dev).run.xml ├── .vscode ├── PSScriptAnalyzerSettings.psd1 ├── compound-tasks.json ├── launch.json └── tasks.json ├── ControlR.Agent.Common ├── ControlR.Agent.Common.csproj ├── Interfaces │ ├── IAgentInstaller.cs │ ├── IDeviceDataGenerator.cs │ ├── IElevationChecker.cs │ ├── IPowerControl.cs │ ├── IStreamerLauncher.cs │ ├── IStreamerUpdater.cs │ └── ITerminalSession.cs ├── IpcDtos │ ├── DesktopRequestDto.cs │ ├── DesktopResponseDto.cs │ └── ShutdownRequestDto.cs ├── Models │ ├── DeviceModel.cs │ ├── StartupMode.cs │ └── StreamingSession.cs ├── Options │ └── InstanceOptions.cs ├── Properties │ └── AssemblyInfo.cs ├── Services │ ├── AgentHeartbeatTimer.cs │ ├── AgentHubClient.cs │ ├── AgentHubConnection.cs │ ├── AgentUpdater.cs │ ├── Base │ │ ├── AgentInstallerBase.cs │ │ └── DeviceDataGeneratorBase.cs │ ├── CpuUtilizationSampler.cs │ ├── DtoHandler.cs │ ├── Fakes │ │ ├── StreamerLauncherFake.cs │ │ └── StreamerUpdaterFake.cs │ ├── HubConnectionInitializer.cs │ ├── Linux │ │ ├── AgentInstallerLinux.cs │ │ ├── DeviceDataGeneratorLinux.cs │ │ ├── ElevationCheckerLinux.cs │ │ └── PowerControlLinux.cs │ ├── Mac │ │ ├── AgentInstallerMac.cs │ │ ├── DeviceDataGeneratorMac.cs │ │ ├── ElevationCheckerMac.cs │ │ └── PowerControlMac.cs │ ├── SettingsProvider.cs │ ├── TerminalSession.cs │ ├── TerminalStore.cs │ └── Windows │ │ ├── AgentInstallerWindows.cs │ │ ├── CpuUtilizationSamplerWin.cs │ │ ├── DesktopEchoer.cs │ │ ├── DeviceDataGeneratorWin.cs │ │ ├── ElevationCheckerWin.cs │ │ ├── PowerControlWindows.cs │ │ ├── RegistryAccessor.cs │ │ ├── StreamerLauncherWindows.cs │ │ ├── StreamerUpdaterWindows.cs │ │ ├── StreamingSessionCache.cs │ │ └── StreamingSessionWatcher.cs ├── Startup │ ├── HostBuilderExtensions.cs │ └── PathConstants.cs └── Usings.cs ├── ControlR.Agent ├── ControlR.Agent.csproj ├── Program.cs ├── Properties │ ├── PublishProfiles │ │ ├── linux-x64.pubxml │ │ ├── osx-arm64.pubxml │ │ ├── osx-x64.pubxml │ │ └── win-x86.pubxml │ └── launchSettings.json ├── Startup │ └── CommandProvider.cs ├── Usings.cs ├── appicon.ico ├── appsettings.Development.json └── appsettings.json ├── ControlR.Streamer ├── ControlR.Streamer.csproj ├── Extensions │ ├── BitmapExtensions.cs │ ├── DxExtensions.cs │ ├── GraphicsExtensions.cs │ └── IServiceCollectionExtensions.cs ├── Helpers │ ├── DisplayEnumHelper.cs │ ├── Disposer.cs │ ├── DxOutputGenerator.cs │ ├── DxTextureHelper.cs │ └── TryHelper.cs ├── Messages │ ├── CursorChangedMessage.cs │ ├── DisplayMetricsChangedMessage.cs │ ├── DisplaySettingsChangedMessage.cs │ ├── SessionEndReasonsEx.cs │ ├── SessionSwitchReasonEx.cs │ ├── WindowsSessionEndingMessage.cs │ └── WindowsSessionSwitchedMessage.cs ├── Models │ ├── CaptureResult.cs │ ├── DisplayInfo.cs │ └── DxOutput.cs ├── Options │ └── StartupOptions.cs ├── PathConstants.cs ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ └── PublishProfiles │ │ └── win-x86.pubxml ├── Services │ ├── BitmapUtility.cs │ ├── CaptureMetrics.cs │ ├── ClipboardManager.cs │ ├── CursorWatcher.cs │ ├── DesktopCapturer.cs │ ├── DtoHandler.cs │ ├── InputDesktopReporter.cs │ ├── InputSimulatorWindows.cs │ ├── ScreenGrabber.cs │ ├── StreamerStreamingClient.cs │ ├── SystemEventHandler.cs │ └── Toaster.cs ├── Usings.cs ├── appicon.ico ├── appsettings.Development.json └── appsettings.json ├── ControlR.Web.AppHost ├── ControlR.Web.AppHost.csproj ├── Program.cs ├── Properties │ └── launchSettings.json ├── appsettings.Development.json └── appsettings.json ├── ControlR.Web.Client ├── Authz │ ├── Policies │ │ └── RequireServerAdministratorPolicy.cs │ ├── RoleNames.cs │ ├── UserClaim.cs │ ├── UserClaimTypes.cs │ └── UserInfo.cs ├── ClientRoutes.cs ├── Components │ ├── ContentWindows │ │ ├── DeviceContentHarness.razor │ │ ├── DeviceContentHarness.razor.css │ │ ├── DeviceContentInstance.cs │ │ ├── DeviceContentWindow.razor │ │ ├── DeviceContentWindow.razor.cs │ │ └── DeviceContentWindow.razor.css │ ├── Dashboard.razor │ ├── Dashboard.razor.cs │ ├── Dashboard.razor.css │ ├── Dialogs │ │ ├── EditDeviceDialog.razor │ │ ├── EditDeviceDialog.razor.css │ │ ├── PromptDialog.razor │ │ └── WindowsSessionSelectDialog.razor │ ├── JsInteropableComponent.cs │ ├── Layout │ │ ├── MainLayout.razor │ │ └── NavMenu.razor │ ├── Pages │ │ ├── About.razor │ │ ├── About.razor.css │ │ ├── Deploy.razor │ │ ├── Deploy.razor.cs │ │ ├── Home.razor │ │ ├── Invite.razor │ │ ├── Invite.razor.css │ │ ├── InviteConfirmation.razor │ │ ├── Permissions.razor │ │ ├── Permissions.razor.cs │ │ ├── ServerStats.razor │ │ └── Settings.razor │ ├── Permissions │ │ ├── DevicesList.razor │ │ ├── DevicesListToggled.razor │ │ ├── DevicesTabContent.razor │ │ ├── DevicesTabContent.razor.cs │ │ ├── TagsSelector.razor │ │ ├── TagsTabContent.razor │ │ ├── TagsTabContent.razor.cs │ │ ├── UsersTabContent.razor │ │ └── UsersTabContent.razor.cs │ ├── RemoteDisplays │ │ ├── ClipboardMenu.razor │ │ ├── DisplaysMenu.razor │ │ ├── RemoteDisplay.razor │ │ ├── RemoteDisplay.razor.cs │ │ ├── RemoteDisplay.razor.css │ │ ├── RemoteDisplay.razor.js │ │ └── ViewModeMenu.razor │ ├── Shared │ │ ├── RadialGauge.razor │ │ └── RadialGauge.razor.css │ ├── Terminal.razor │ ├── Terminal.razor.cs │ ├── Terminal.razor.css │ ├── ViewportAwareComponent.cs │ └── Welcome.razor ├── ControlR.Web.Client.csproj ├── DataValidation │ └── EqualToAttribute.cs ├── Enums │ ├── ControlMode.cs │ ├── ViewMode.cs │ └── WindowState.cs ├── Extensions │ ├── AuthenticationStateProviderExtensions.cs │ ├── ClaimsPrincipalExtensions.cs │ ├── DialogServiceExtensions.cs │ ├── IMessengerExtensions.cs │ ├── IServiceCollectionExtensions.cs │ └── MessageSeverityExtensions.cs ├── Models │ ├── Messages │ │ ├── DesktopChangedMessage.cs │ │ ├── DeviceContentWindowStateMessage.cs │ │ ├── HubConnectionStateChangedMessage.cs │ │ └── ToastMessage.cs │ └── RemoteControlSession.cs ├── Program.cs ├── RedirectToLogin.razor ├── Routes.razor ├── Services │ ├── BusyCounter.cs │ ├── ClipboardManager.cs │ ├── DeviceContentWindowStore.cs │ ├── JsInterop.cs │ ├── PersistentAuthenticationStateProvider.cs │ ├── ScreenWake.cs │ ├── Settings.cs │ ├── Stores │ │ ├── DeviceStore.cs │ │ ├── InviteStore.cs │ │ ├── RoleStore.cs │ │ ├── StoreBase.cs │ │ ├── TagStore.cs │ │ └── UserStore.cs │ ├── ViewerHubClient.cs │ ├── ViewerHubConnection.cs │ └── ViewerStreamingClient.cs ├── Usings.cs ├── ViewModels │ ├── DeviceViewModel.cs │ ├── RoleViewModel.cs │ └── TagViewModel.cs ├── _Imports.razor └── wwwroot │ ├── appsettings.Development.json │ └── appsettings.json ├── ControlR.Web.Server ├── Api │ ├── DeviceTagsController.cs │ ├── DevicesController.cs │ ├── InstallerKeysController.cs │ ├── InvitesController.cs │ ├── LogoutController.cs │ ├── RolesController.cs │ ├── ServerSettingsController.cs │ ├── TagsController.cs │ ├── UserPreferencesController.cs │ ├── UserRolesController.cs │ ├── UserTagsController.cs │ ├── UsersController.cs │ └── VersionController.cs ├── Authz │ ├── AuthorizationPolicyBuilderExtensions.cs │ ├── Policies │ │ └── DeviceAccessByDeviceResourcePolicy.cs │ ├── Roles │ │ └── RoleFactory.cs │ ├── ServiceProviderAsyncRequirement.cs │ ├── ServiceProviderAsyncRequirementHandler.cs │ ├── ServiceProviderRequirement.cs │ └── ServiceProviderRequirementHandler.cs ├── Components │ ├── Account │ │ ├── IdentityComponentsEndpointRouteBuilderExtensions.cs │ │ ├── IdentityEmailSender.cs │ │ ├── IdentityRedirectManager.cs │ │ ├── IdentityUserAccessor.cs │ │ ├── Pages │ │ │ ├── AccessDenied.razor │ │ │ ├── ConfirmEmail.razor │ │ │ ├── ConfirmEmailChange.razor │ │ │ ├── ExternalLogin.razor │ │ │ ├── ForgotPassword.razor │ │ │ ├── ForgotPasswordConfirmation.razor │ │ │ ├── InvalidPasswordReset.razor │ │ │ ├── InvalidUser.razor │ │ │ ├── Lockout.razor │ │ │ ├── Login.razor │ │ │ ├── LoginWith2fa.razor │ │ │ ├── LoginWithRecoveryCode.razor │ │ │ ├── Manage │ │ │ │ ├── ChangePassword.razor │ │ │ │ ├── DeletePersonalData.razor │ │ │ │ ├── Disable2fa.razor │ │ │ │ ├── Email.razor │ │ │ │ ├── EnableAuthenticator.razor │ │ │ │ ├── ExternalLogins.razor │ │ │ │ ├── GenerateRecoveryCodes.razor │ │ │ │ ├── Index.razor │ │ │ │ ├── PersonalData.razor │ │ │ │ ├── ResetAuthenticator.razor │ │ │ │ ├── SetPassword.razor │ │ │ │ ├── TwoFactorAuthentication.razor │ │ │ │ └── _Imports.razor │ │ │ ├── Register.razor │ │ │ ├── RegisterConfirmation.razor │ │ │ ├── ResendEmailConfirmation.razor │ │ │ ├── ResetPassword.razor │ │ │ ├── ResetPasswordConfirmation.razor │ │ │ └── _Imports.razor │ │ ├── PersistingRevalidatingAuthenticationStateProvider.cs │ │ └── Shared │ │ │ ├── AccountLayout.razor │ │ │ ├── ExternalLoginPicker.razor │ │ │ ├── ManageLayout.razor │ │ │ ├── ManageNavMenu.razor │ │ │ ├── ShowRecoveryCodes.razor │ │ │ └── StatusMessage.razor │ ├── App.razor │ ├── Pages │ │ └── Error.razor │ └── _Imports.razor ├── ControlR.Web.Server.csproj ├── Converters │ └── PostgresDateTimeOffsetConverter.cs ├── Data │ ├── AppDb.cs │ ├── Configuration │ │ ├── ClaimsDbContextOptions.cs │ │ ├── ClaimsDbContextOptionsExtension.cs │ │ └── DbContextOptionsBuilderExtensions.cs │ ├── Entities │ │ ├── AppRole.cs │ │ ├── AppUser.cs │ │ ├── Bases │ │ │ ├── EntityBase.cs │ │ │ └── TenantEntityBase.cs │ │ ├── Device.cs │ │ ├── Tag.cs │ │ ├── Tenant.cs │ │ ├── TenantInvite.cs │ │ └── UserPreference.cs │ └── Migrations │ │ ├── 20241113035049_Initial.Designer.cs │ │ ├── 20241113035049_Initial.cs │ │ ├── 20241116201658_Add_Invites.Designer.cs │ │ ├── 20241116201658_Add_Invites.cs │ │ ├── 20241130193555_Add_DataProtectionKeys.Designer.cs │ │ ├── 20241130193555_Add_DataProtectionKeys.cs │ │ ├── 20241207195022_Add_TagNameLength.Designer.cs │ │ ├── 20241207195022_Add_TagNameLength.cs │ │ ├── 20241212010510_Add_UserIsOnline.Designer.cs │ │ ├── 20241212010510_Add_UserIsOnline.cs │ │ ├── 20250103164738_Add_AgentInstaller.Designer.cs │ │ ├── 20250103164738_Add_AgentInstaller.cs │ │ └── AppDbModelSnapshot.cs ├── Dockerfile ├── Extensions │ ├── EntityToDtoExtensions.cs │ ├── HttpExtensions.cs │ ├── OutputCacheExtensions.cs │ └── PropertyValuesExtensions.cs ├── Helpers │ ├── CoordinateHelper.cs │ └── StreamSignaler.cs ├── Hubs │ ├── AgentHub.cs │ ├── HubGroupNames.cs │ ├── HubWithItems.cs │ └── ViewerHub.cs ├── Interfaces │ └── ICoordinate.cs ├── Middleware │ ├── ContentHashHeaderMiddleware.cs │ └── DeviceGridOutputCachePolicy.cs ├── Models │ ├── AgentInstallerKey.cs │ ├── Coordinate.cs │ └── ExternalWebSocketHost.cs ├── Options │ └── AppOptions.cs ├── Program.cs ├── Properties │ ├── launchSettings.json │ ├── serviceDependencies.json │ └── serviceDependencies.local.json ├── RateLimiting │ └── RateLimitPolicyNames.cs ├── ResultObjects │ └── CreateUserResult.cs ├── Services │ ├── AgentInstallerKeyManager.cs │ ├── DeviceManager.cs │ ├── EmailSender.cs │ ├── IDeviceGridCacheService.cs │ ├── ServerStatsProvider.cs │ ├── StreamStore.cs │ ├── UserCreator.cs │ └── UserRegistrationProvider.cs ├── Startup │ ├── HttpClientConfigurer.cs │ ├── IHostExtensions.cs │ └── WebApplicationBuilderExtensions.cs ├── Usings.cs ├── appsettings.Development.json ├── appsettings.json └── wwwroot │ ├── app.css │ ├── downloads │ └── AgentVersion.txt │ ├── favicon.ico │ ├── icon-192.png │ ├── icon-512.png │ ├── images │ ├── sign-in-microsoft.png │ └── sign-in-microsoft.svg │ ├── interop.js │ └── manifest.webmanifest ├── ControlR.Web.ServiceDefaults ├── ControlR.Web.ServiceDefaults.csproj ├── Extensions.cs └── ServiceNames.cs ├── ControlR.Web.WebSocketRelay ├── .config │ └── dotnet-tools.json ├── ControlR.Web.WebSocketRelay.csproj ├── ControlR.Web.WebSocketRelay.http ├── Dockerfile ├── Dtos │ └── StatusOkDto.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Serialization │ └── AppJsonSerializerContext.cs ├── appsettings.Development.json └── appsettings.json ├── ControlR.sln ├── ControlR.sln.DotSettings ├── ControlR.slnLaunch ├── Directory.Build.props ├── LICENSE.txt ├── Libraries ├── ControlR.Libraries.Clients │ ├── ControlR.Libraries.Clients.csproj │ ├── Extensions │ │ └── IMessengerExtensions.cs │ ├── Messages │ │ ├── DtoReceivedMessage.cs │ │ ├── EventMessageKind.cs │ │ └── ValueMessage.cs │ ├── Services │ │ ├── HubConnectionBase.cs │ │ └── StreamingClient.cs │ └── Usings.cs ├── ControlR.Libraries.DevicesCommon │ ├── ControlR.Libraries.DevicesCommon.csproj │ ├── Extensions │ │ └── SerilogHostExtensions.cs │ ├── NativeMethods.txt │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Services │ │ ├── FileLogger.cs │ │ ├── FileLoggerProvider.cs │ │ ├── FileSystem.cs │ │ ├── HostLifetimeEventResponder.cs │ │ ├── ProcessManager.cs │ │ └── WakeOnLanService.cs │ └── Usings.cs ├── ControlR.Libraries.DevicesNative │ ├── ControlR.Libraries.DevicesNative.csproj │ ├── Linux │ │ └── LIbc.cs │ ├── NativeMethods.txt │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Services │ │ ├── Win32InteropFake.cs │ │ └── Windows │ │ │ └── Win32Interop.cs │ └── Windows │ │ ├── MemoryStatusEx.cs │ │ └── WtsApi32.cs ├── ControlR.Libraries.Shared │ ├── Collections │ │ ├── ConcurrentHashSet.cs │ │ └── ConcurrentList.cs │ ├── Constants │ │ ├── AppConstants.cs │ │ ├── HttpConstants.cs │ │ ├── UserPreferenceNames.cs │ │ └── Validators.cs │ ├── ControlR.Libraries.Shared.csproj │ ├── Converters │ │ └── TimeSpanJsonConverter.cs │ ├── Dtos │ │ ├── DtoType.cs │ │ ├── DtoWrapper.cs │ │ ├── HubDtos │ │ │ ├── CloseStreamingSessionRequestDto.cs │ │ │ ├── CloseTerminalRequestDto.cs │ │ │ ├── DisplayDto.cs │ │ │ ├── GetWindowsSessionsDto.cs │ │ │ ├── InvokeCtrlAltDelRequestDto.cs │ │ │ ├── IpApiResponse.cs │ │ │ ├── PowerStateChangeDto.cs │ │ │ ├── RefreshDeviceInfoRequestDto.cs │ │ │ ├── ResetKeyboardStateDto.cs │ │ │ ├── ServerStatusDto.cs │ │ │ ├── TerminalInputDto.cs │ │ │ ├── TerminalOutputDto.cs │ │ │ ├── TerminalSessionRequest.cs │ │ │ ├── TerminalSessionRequestResult.cs │ │ │ ├── TriggerAgentUpdateDto.cs │ │ │ └── WakeDeviceDto.cs │ │ ├── Interfaces │ │ │ └── IHasPrimaryKey.cs │ │ ├── ServerApi │ │ │ ├── AcceptInvitationRequestDto.cs │ │ │ ├── AcceptInvitationResponseDto.cs │ │ │ ├── CreateDeviceRequestDto.cs │ │ │ ├── CreateInstallerKeyRequestDto.cs │ │ │ ├── CreateInstallerKeyResponseDto.cs │ │ │ ├── DeviceDto.cs │ │ │ ├── DeviceSearchRequestDto.cs │ │ │ ├── DeviceSearchResponseDto.cs │ │ │ ├── DeviceTagAddRequestDto.cs │ │ │ ├── EntityBaseRecordDto.cs │ │ │ ├── InstallerKeyType.cs │ │ │ ├── RoleResponseDto.cs │ │ │ ├── ServerSettingsDto.cs │ │ │ ├── TagCreateRequestDto.cs │ │ │ ├── TagRenameRequestDto.cs │ │ │ ├── TagResponseDto.cs │ │ │ ├── TenantInviteRequestDto.cs │ │ │ ├── TenantInviteResponseDto.cs │ │ │ ├── UserPreferenceRequestDto.cs │ │ │ ├── UserPreferenceResponseDto.cs │ │ │ ├── UserResponseDto.cs │ │ │ ├── UserRoleAddRequest.cs │ │ │ └── UserTagAddRequestDto.cs │ │ └── StreamerDtos │ │ │ ├── AckDto.cs │ │ │ ├── ChangeDisplaysDto.cs │ │ │ ├── ClipboardTextDto.cs │ │ │ ├── CursorChangedDto.cs │ │ │ ├── DesktopChangedDto.cs │ │ │ ├── DisplayDataDto.cs │ │ │ ├── KeyEventDto.cs │ │ │ ├── MouseButtonEventDto.cs │ │ │ ├── MouseClickDto.cs │ │ │ ├── MovePointerDto.cs │ │ │ ├── RequestClipboardTextDto.cs │ │ │ ├── ScreenRegionDto.cs │ │ │ ├── StreamerDownloadProgressDto.cs │ │ │ ├── StreamerSessionRequestDto.cs │ │ │ ├── TypeTextDto.cs │ │ │ ├── WheelScrollDto.cs │ │ │ ├── WindowsSessionEndingDto.cs │ │ │ └── WindowsSessionSwitchedDto.cs │ ├── Enums │ │ ├── ConnectionType.cs │ │ ├── MessageSeverity.cs │ │ ├── PowerStateChangeType.cs │ │ ├── RuntimeId.cs │ │ ├── SystemPlatform.cs │ │ ├── TagType.cs │ │ ├── TerminalOutputKind.cs │ │ ├── TerminalSessionKind.cs │ │ └── WindowsCursor.cs │ ├── Exceptions │ │ ├── ClientConnectionNotFoundException.cs │ │ ├── DtoTypeMismatchException.cs │ │ └── ProcessStatusException.cs │ ├── Extensions │ │ ├── CancellationTokenExtensions.cs │ │ ├── CollectionExtensions.cs │ │ ├── DateTimeExtensions.cs │ │ ├── GenericExtensions.cs │ │ ├── ILoggerExtensions.cs │ │ ├── IServiceCollectionExtensions.cs │ │ ├── ListExtensions.cs │ │ ├── ProcessExtensions.cs │ │ ├── StringExtensions.cs │ │ ├── TaskExtensions.cs │ │ └── UriExtensions.cs │ ├── Helpers │ │ ├── AppendableStream.cs │ │ ├── Debouncer.cs │ │ ├── DeterministicGuid.cs │ │ ├── DisposeHelper.cs │ │ ├── Guard.cs │ │ ├── JsonValueFilter.cs │ │ ├── MathHelper.cs │ │ ├── NoopDisposable.cs │ │ ├── RandomGenerator.cs │ │ └── RateLimiter.cs │ ├── Hubs │ │ ├── IAgentHub.cs │ │ └── IViewerHub.cs │ ├── IO │ │ ├── ReactiveFileStream.cs │ │ └── RedirectableStream.cs │ ├── Interfaces │ │ └── HubClients │ │ │ ├── IAgentHubClient.cs │ │ │ ├── IDesktopHubClient.cs │ │ │ ├── IHubClient.cs │ │ │ └── IViewerHubClient.cs │ ├── Models │ │ ├── AgentAppOptions.cs │ │ ├── AgentAppSettings.cs │ │ ├── Drive.cs │ │ ├── ToastInfo.cs │ │ └── WindowsSession.cs │ ├── Primitives │ │ ├── AutoResetEventAsync.cs │ │ ├── CallbackDisposable.cs │ │ ├── CallbackDisposableAsync.cs │ │ ├── Closable.cs │ │ ├── DisposableValue.cs │ │ ├── FunnelLock.cs │ │ ├── ManualResetEventAsync.cs │ │ └── Result.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Services │ │ ├── Buffers │ │ │ ├── EphemeralBuffer.cs │ │ │ └── MemoryProvider.cs │ │ ├── Delayer.cs │ │ ├── Http │ │ │ ├── ControlrApi.cs │ │ │ ├── DownloadsApi.cs │ │ │ ├── IpApi.cs │ │ │ └── WsRelayApi.cs │ │ ├── LazyDi.cs │ │ ├── Retryer.cs │ │ └── SystemEnvironment.cs │ └── Usings.cs ├── ControlR.Libraries.Signalr.Client │ ├── ControlR.Libraries.Signalr.Client.csproj │ ├── Diagnostics │ │ └── DefaultActivitySource.cs │ ├── Exceptions │ │ └── DynamicObjectGenerationException.cs │ ├── Extensions │ │ └── ServiceCollectionExtensions.cs │ ├── HubConnection.cs │ └── Internals │ │ ├── HubProxyGenerator.cs │ │ ├── IInvocationHandler.cs │ │ └── ProxyInvocationHandler.cs └── ControlR.Libraries.WebSocketRelay.Common │ ├── ControlR.Libraries.WebSocketRelay.Common.csproj │ ├── Extensions │ └── Extensions.cs │ ├── Helpers │ └── DisposeHelper.cs │ ├── Middleware │ └── WebSocketRelayMiddleware.cs │ ├── Sessions │ ├── SessionSignaler.cs │ └── SessionStore.cs │ ├── Usings.cs │ └── appicon.png ├── README.md ├── Tests ├── ControlR.Agent.LoadTester │ ├── ControlR.Agent.LoadTester.csproj │ ├── FakeAgentUpdater.cs │ ├── FakeAppHostLifetime.cs │ ├── FakeCpuUtilizationSampler.cs │ ├── FakeSettingsProvider.cs │ ├── FakeStreamerLauncher.cs │ ├── FakeStreamerUpdater.cs │ ├── Helpers │ │ ├── ArgsParser.cs │ │ ├── ConnectionHelpers.cs │ │ └── ServiceCollectionExtensions.cs │ ├── Program.cs │ ├── Properties │ │ ├── AssemblyInfo.cs │ │ ├── PublishProfiles │ │ │ └── FolderProfile.pubxml │ │ └── launchSettings.json │ ├── TestAgentHubClient.cs │ └── TestAgentRetryPolicy.cs ├── ControlR.Libraries.DevicesCommon.Tests │ ├── ControlR.Libraries.DevicesCommon.Tests.csproj │ └── Usings.cs ├── ControlR.Libraries.ScreenCapture.Benchmarks │ ├── CaptureTests.cs │ ├── ControlR.Libraries.ScreenCapture.Benchmarks.csproj │ └── Program.cs ├── ControlR.Streamer.Tests │ ├── ControlR.Streamer.Tests.csproj │ └── DesktopCapturerTests.cs ├── ControlR.Tests.TestingUtilities │ ├── ControlR.Tests.TestingUtilities.csproj │ ├── FakeHostApplicationLifetime.cs │ ├── OptionsMonitorWrapper.cs │ ├── ServiceCollectionExtensions.cs │ ├── XunitLogger.cs │ └── XunitLoggerProvider.cs └── ControlR.Web.Server.Tests │ ├── AgentInstallerKeyManagerTests.cs │ ├── ControlR.Web.Server.Tests.csproj │ ├── DeviceGridCachePolicyTests.cs │ ├── DeviceGridOutputCacheTests.cs │ ├── DeviceManagerTests.cs │ ├── DevicesControllerTests.cs │ ├── Helpers │ ├── ControllerExtensions.cs │ ├── EntityFrameworkQueryHelper.cs │ ├── TestApp.cs │ ├── TestAppBuilder.cs │ ├── TestAppExtensions.cs │ └── TestDevicesController.cs │ └── OutputCacheExtensionsTests.cs └── docker-compose ├── .dockerignore ├── .env ├── docker-compose.dcproj ├── docker-compose.override.yml ├── docker-compose.yml └── launchSettings.json /.assets/appicon-tile.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbound/ControlR/79e29a6c09b6fbbd08ff37bd7e6b9eca3a9d00b3/.assets/appicon-tile.pdn -------------------------------------------------------------------------------- /.assets/appicon-tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbound/ControlR/79e29a6c09b6fbbd08ff37bd7e6b9eca3a9d00b3/.assets/appicon-tile.png -------------------------------------------------------------------------------- /.assets/appicon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbound/ControlR/79e29a6c09b6fbbd08ff37bd7e6b9eca3a9d00b3/.assets/appicon.icns -------------------------------------------------------------------------------- /.assets/appicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbound/ControlR/79e29a6c09b6fbbd08ff37bd7e6b9eca3a9d00b3/.assets/appicon.ico -------------------------------------------------------------------------------- /.assets/appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbound/ControlR/79e29a6c09b6fbbd08ff37bd7e6b9eca3a9d00b3/.assets/appicon.png -------------------------------------------------------------------------------- /.assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbound/ControlR/79e29a6c09b6fbbd08ff37bd7e6b9eca3a9d00b3/.assets/favicon.ico -------------------------------------------------------------------------------- /.assets/screenshots/desktop_details-row.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbound/ControlR/79e29a6c09b6fbbd08ff37bd7e6b9eca3a9d00b3/.assets/screenshots/desktop_details-row.png -------------------------------------------------------------------------------- /.assets/screenshots/desktop_remote-control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbound/ControlR/79e29a6c09b6fbbd08ff37bd7e6b9eca3a9d00b3/.assets/screenshots/desktop_remote-control.png -------------------------------------------------------------------------------- /.assets/screenshots/desktop_terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbound/ControlR/79e29a6c09b6fbbd08ff37bd7e6b9eca3a9d00b3/.assets/screenshots/desktop_terminal.png -------------------------------------------------------------------------------- /.assets/screenshots/desktop_windows-sessions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbound/ControlR/79e29a6c09b6fbbd08ff37bd7e6b9eca3a9d00b3/.assets/screenshots/desktop_windows-sessions.png -------------------------------------------------------------------------------- /.assets/screenshots/mobile_windows-sessions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbound/ControlR/79e29a6c09b6fbbd08ff37bd7e6b9eca3a9d00b3/.assets/screenshots/mobile_windows-sessions.png -------------------------------------------------------------------------------- /.azure-pipelines/relay-server.yml: -------------------------------------------------------------------------------- 1 | name: 1.1.$(Rev:r) 2 | trigger: 3 | branches: 4 | include: 5 | - main 6 | jobs: 7 | - job: Relay_Deploy 8 | displayName: Relay Server Build and Deploy 9 | pool: 10 | name: Default 11 | steps: 12 | - checkout: self 13 | fetchDepth: 1 14 | clean: true 15 | 16 | - task: DotNetCoreCLI@2 17 | inputs: 18 | command: "build" 19 | arguments: '--configuration Release -p:Version="$(Build.BuildNumber)"' 20 | projects: "ControlR.Web.WebSocketRelay/ControlR.Web.WebSocketRelay.csproj" 21 | 22 | - task: Docker@2 23 | displayName: Build and Push Docker 24 | inputs: 25 | containerRegistry: 428e5669-4949-4ad6-9e37-730f15b2cad8 26 | repository: translucency/controlr-relay 27 | buildContext: $(System.DefaultWorkingDirectory) 28 | tags: >- 29 | $(Build.BuildId) 30 | 31 | latest 32 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md 26 | !**/.gitignore 27 | !.git/HEAD 28 | !.git/config 29 | !.git/packed-refs 30 | !.git/refs/heads/** -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [bitbound] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE] - Title Here" 5 | labels: enhancement 6 | assignees: bitbound 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/media/controlr-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbound/ControlR/79e29a6c09b6fbbd08ff37bd7e6b9eca3a9d00b3/.github/media/controlr-logo.png -------------------------------------------------------------------------------- /.github/media/controlr1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbound/ControlR/79e29a6c09b6fbbd08ff37bd7e6b9eca3a9d00b3/.github/media/controlr1.jpg -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "wwwroot/novnc"] 2 | path = wwwroot/novnc 3 | url = https://github.com/novnc/noVNC 4 | [submodule "ControlR.RelayNode/wwwroot/novnc"] 5 | path = ControlR.RelayNode/wwwroot/novnc 6 | url = https://github.com/novnc/noVNC 7 | -------------------------------------------------------------------------------- /.idea/.idea.ControlR/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Rider ignored files 5 | /.idea.ControlR.iml 6 | /modules.xml 7 | /contentModel.xml 8 | /projectSettingsUpdater.xml 9 | # Editor-based HTTP Client requests 10 | /httpRequests/ 11 | # Datasource local storage ignored files 12 | /dataSources/ 13 | /dataSources.local.xml 14 | -------------------------------------------------------------------------------- /.idea/.idea.ControlR/.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/.idea.ControlR/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/.idea.ControlR/.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/.idea.ControlR/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.run/Agent (Run).run.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 17 | -------------------------------------------------------------------------------- /.run/Server (https).run.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 18 | -------------------------------------------------------------------------------- /.run/Server + Agent + Docker.run.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.run/Server + Docker.run.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.run/compose (dev).run.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 11 | 17 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /.vscode/PSScriptAnalyzerSettings.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | ExcludeRules = @('PSUseApprovedVerbs') 3 | } -------------------------------------------------------------------------------- /.vscode/compound-tasks.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbound/ControlR/79e29a6c09b6fbbd08ff37bd7e6b9eca3a9d00b3/.vscode/compound-tasks.json -------------------------------------------------------------------------------- /ControlR.Agent.Common/Interfaces/IAgentInstaller.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Agent.Common.Interfaces; 2 | 3 | public interface IAgentInstaller 4 | { 5 | Task Install(Uri? serverUri = null, Guid? tenantId = null, string? installerKey = null, Guid[]? tags = null); 6 | 7 | Task Uninstall(); 8 | } -------------------------------------------------------------------------------- /ControlR.Agent.Common/Interfaces/IDeviceDataGenerator.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Agent.Common.Models; 2 | 3 | namespace ControlR.Agent.Common.Interfaces; 4 | 5 | public interface IDeviceDataGenerator 6 | { 7 | Task CreateDevice(Guid deviceId); 8 | } -------------------------------------------------------------------------------- /ControlR.Agent.Common/Interfaces/IElevationChecker.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Agent.Common.Interfaces; 2 | 3 | public interface IElevationChecker 4 | { 5 | bool IsElevated(); 6 | } -------------------------------------------------------------------------------- /ControlR.Agent.Common/Interfaces/IPowerControl.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Enums; 2 | 3 | namespace ControlR.Agent.Common.Interfaces; 4 | internal interface IPowerControl 5 | { 6 | Task ChangeState(PowerStateChangeType type); 7 | } 8 | -------------------------------------------------------------------------------- /ControlR.Agent.Common/Interfaces/IStreamerLauncher.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Primitives; 2 | 3 | namespace ControlR.Agent.Common.Interfaces; 4 | internal interface IStreamerLauncher 5 | { 6 | Task CreateSession( 7 | Guid sessionId, 8 | Uri websocketUri, 9 | string viewerConnectionId, 10 | int targetWindowsSession = -1, 11 | bool notifyUserOnSessionStart = false, 12 | string? viewerName = null); 13 | } 14 | -------------------------------------------------------------------------------- /ControlR.Agent.Common/Interfaces/IStreamerUpdater.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Dtos.StreamerDtos; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace ControlR.Agent.Common.Interfaces; 5 | internal interface IStreamerUpdater : IHostedService 6 | { 7 | Task EnsureLatestVersion( 8 | StreamerSessionRequestDto requestDto, 9 | CancellationToken cancellationToken); 10 | 11 | Task EnsureLatestVersion( 12 | CancellationToken cancellationToken); 13 | } 14 | -------------------------------------------------------------------------------- /ControlR.Agent.Common/Interfaces/ITerminalSession.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Agent.Common.Interfaces; 2 | internal interface ITerminalSession 3 | { 4 | } 5 | -------------------------------------------------------------------------------- /ControlR.Agent.Common/IpcDtos/DesktopRequestDto.cs: -------------------------------------------------------------------------------- 1 | using MessagePack; 2 | namespace ControlR.Agent.Common.IpcDtos; 3 | 4 | [MessagePackObject] 5 | public record DesktopRequestDto(); -------------------------------------------------------------------------------- /ControlR.Agent.Common/IpcDtos/DesktopResponseDto.cs: -------------------------------------------------------------------------------- 1 | using MessagePack; 2 | 3 | namespace ControlR.Agent.Common.IpcDtos; 4 | 5 | [MessagePackObject] 6 | public record DesktopResponseDto([property: Key(0)] string DesktopName); -------------------------------------------------------------------------------- /ControlR.Agent.Common/IpcDtos/ShutdownRequestDto.cs: -------------------------------------------------------------------------------- 1 | using MessagePack; 2 | 3 | namespace ControlR.Agent.Common.IpcDtos; 4 | 5 | [MessagePackObject] 6 | public record ShutdownRequestDto(); -------------------------------------------------------------------------------- /ControlR.Agent.Common/Models/StartupMode.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Agent.Common.Models; 2 | internal enum StartupMode 3 | { 4 | None, 5 | Run, 6 | Install, 7 | Uninstall, 8 | EchoDesktop, 9 | } 10 | -------------------------------------------------------------------------------- /ControlR.Agent.Common/Models/StreamingSession.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using ControlR.Libraries.Shared.Helpers; 3 | 4 | namespace ControlR.Agent.Common.Models; 5 | 6 | internal class StreamingSession(string viewerConnectionId) : IDisposable 7 | { 8 | public string ViewerConnectionId { get; } = viewerConnectionId; 9 | public Process? StreamerProcess { get; set; } 10 | 11 | public void Dispose() 12 | { 13 | try 14 | { 15 | StreamerProcess?.Kill(); 16 | } 17 | catch 18 | { 19 | } 20 | 21 | DisposeHelper.DisposeAll(StreamerProcess); 22 | } 23 | } -------------------------------------------------------------------------------- /ControlR.Agent.Common/Options/InstanceOptions.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Agent.Common.Options; 2 | public class InstanceOptions 3 | { 4 | public const string SectionKey = "InstanceOptions"; 5 | private readonly string? _instanceId; 6 | 7 | public string? InstanceId 8 | { 9 | get => _instanceId; 10 | init => _instanceId = value?.SanitizeForFileSystem(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ControlR.Agent.Common/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("ControlR.Agent")] 4 | [assembly: InternalsVisibleTo("ControlR.Agent.LoadTester")] -------------------------------------------------------------------------------- /ControlR.Agent.Common/Services/Fakes/StreamerLauncherFake.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Agent.Common.Interfaces; 2 | using ControlR.Libraries.Shared.Extensions; 3 | using ControlR.Libraries.Shared.Primitives; 4 | 5 | namespace ControlR.Agent.Common.Services.Fakes; 6 | internal class StreamerLauncherFake : IStreamerLauncher 7 | { 8 | public Task CreateSession( 9 | Guid sessionId, 10 | Uri websocketUri, 11 | string viewerConnectionId, 12 | int targetWindowsSession = -1, 13 | bool notifyUserOnSessionStart = false, 14 | string? viewerName = null) 15 | { 16 | return Result.Fail("Platform not supported.").AsTaskResult(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ControlR.Agent.Common/Services/Fakes/StreamerUpdaterFake.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Agent.Common.Interfaces; 2 | using ControlR.Libraries.Shared.Dtos.StreamerDtos; 3 | using Microsoft.Extensions.Hosting; 4 | 5 | namespace ControlR.Agent.Common.Services.Fakes; 6 | 7 | internal class StreamerUpdaterFake(ILogger logger) : BackgroundService, IStreamerUpdater 8 | { 9 | public Task EnsureLatestVersion(StreamerSessionRequestDto requestDto, CancellationToken cancellationToken) 10 | { 11 | logger.LogWarning("Platform not supported for desktop streaming."); 12 | return Task.FromResult(false); 13 | } 14 | 15 | public Task EnsureLatestVersion(CancellationToken cancellationToken) 16 | { 17 | return Task.FromResult(true); 18 | } 19 | 20 | protected override Task ExecuteAsync(CancellationToken stoppingToken) 21 | { 22 | return Task.CompletedTask; 23 | } 24 | } -------------------------------------------------------------------------------- /ControlR.Agent.Common/Services/HubConnectionInitializer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Hosting; 2 | 3 | namespace ControlR.Agent.Common.Services; 4 | 5 | public class HubConnectionInitializer(IAgentHubConnection _agentHubConnection) : IHostedService 6 | { 7 | public async Task StartAsync(CancellationToken cancellationToken) 8 | { 9 | await _agentHubConnection.Connect(cancellationToken); 10 | } 11 | 12 | public async Task StopAsync(CancellationToken cancellationToken) 13 | { 14 | await _agentHubConnection.DisposeAsync(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ControlR.Agent.Common/Services/Linux/ElevationCheckerLinux.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Agent.Common.Interfaces; 2 | using ControlR.Libraries.DevicesNative.Linux; 3 | 4 | namespace ControlR.Agent.Common.Services.Linux; 5 | 6 | public class ElevationCheckerLinux : IElevationChecker 7 | { 8 | public static IElevationChecker Instance { get; } = new ElevationCheckerLinux(); 9 | 10 | public bool IsElevated() 11 | { 12 | return Libc.Geteuid() == 0; 13 | } 14 | } -------------------------------------------------------------------------------- /ControlR.Agent.Common/Services/Linux/PowerControlLinux.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Agent.Common.Interfaces; 2 | using ControlR.Libraries.Shared.Enums; 3 | 4 | namespace ControlR.Agent.Common.Services.Linux; 5 | internal class PowerControlLinux(IProcessManager processInvoker) : IPowerControl 6 | { 7 | private readonly IProcessManager _processInvoker = processInvoker; 8 | 9 | public Task ChangeState(PowerStateChangeType type) 10 | { 11 | switch (type) 12 | { 13 | case PowerStateChangeType.Restart: 14 | { 15 | _ = _processInvoker.Start("shutdown", "-r 0", true); 16 | } 17 | break; 18 | case PowerStateChangeType.Shutdown: 19 | { 20 | _ = _processInvoker.Start("shutdown", "-P 0", true); 21 | } 22 | break; 23 | default: 24 | break; 25 | } 26 | return Task.CompletedTask; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ControlR.Agent.Common/Services/Mac/ElevationCheckerMac.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Agent.Common.Interfaces; 2 | using ControlR.Libraries.DevicesNative.Linux; 3 | 4 | namespace ControlR.Agent.Common.Services.Mac; 5 | 6 | public class ElevationCheckerMac : IElevationChecker 7 | { 8 | public static IElevationChecker Instance { get; } = new ElevationCheckerMac(); 9 | 10 | public bool IsElevated() 11 | { 12 | return Libc.Geteuid() == 0; 13 | } 14 | } -------------------------------------------------------------------------------- /ControlR.Agent.Common/Services/Mac/PowerControlMac.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Agent.Common.Interfaces; 2 | using ControlR.Libraries.Shared.Enums; 3 | 4 | namespace ControlR.Agent.Common.Services.Mac; 5 | 6 | internal class PowerControlMac(IProcessManager processInvoker) : IPowerControl 7 | { 8 | private readonly IProcessManager _processInvoker = processInvoker; 9 | 10 | public Task ChangeState(PowerStateChangeType type) 11 | { 12 | switch (type) 13 | { 14 | case PowerStateChangeType.Restart: 15 | { 16 | _ = _processInvoker.Start("shutdown", "-r 0", true); 17 | } 18 | break; 19 | 20 | case PowerStateChangeType.Shutdown: 21 | { 22 | _ = _processInvoker.Start("shutdown", "-P 0", true); 23 | } 24 | break; 25 | 26 | default: 27 | break; 28 | } 29 | return Task.CompletedTask; 30 | } 31 | } -------------------------------------------------------------------------------- /ControlR.Agent.Common/Services/Windows/ElevationCheckerWin.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Agent.Common.Interfaces; 2 | using System.Runtime.Versioning; 3 | using System.Security.Principal; 4 | 5 | namespace ControlR.Agent.Common.Services.Windows; 6 | 7 | [SupportedOSPlatform("windows")] 8 | public class ElevationCheckerWin : IElevationChecker 9 | { 10 | public static IElevationChecker Instance { get; } = new ElevationCheckerWin(); 11 | 12 | public bool IsElevated() 13 | { 14 | var identity = WindowsIdentity.GetCurrent(); 15 | var principal = new WindowsPrincipal(identity); 16 | return principal.IsInRole(WindowsBuiltInRole.Administrator); 17 | } 18 | } -------------------------------------------------------------------------------- /ControlR.Agent.Common/Services/Windows/PowerControlWindows.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Agent.Common.Interfaces; 2 | using ControlR.Libraries.Shared.Enums; 3 | 4 | namespace ControlR.Agent.Common.Services.Windows; 5 | 6 | internal class PowerControlWindows(IProcessManager processInvoker) : IPowerControl 7 | { 8 | private readonly IProcessManager _processInvoker = processInvoker; 9 | 10 | public Task ChangeState(PowerStateChangeType type) 11 | { 12 | switch (type) 13 | { 14 | case PowerStateChangeType.Restart: 15 | { 16 | _ = _processInvoker.Start("shutdown.exe", "/g /t 0 /f", true); 17 | } 18 | break; 19 | 20 | case PowerStateChangeType.Shutdown: 21 | { 22 | _ = _processInvoker.Start("shutdown.exe", "/s /t 0 /f", true); 23 | } 24 | break; 25 | 26 | default: 27 | break; 28 | } 29 | return Task.CompletedTask; 30 | } 31 | } -------------------------------------------------------------------------------- /ControlR.Agent.Common/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Bitbound.SimpleMessenger; 2 | global using ControlR.Libraries.DevicesCommon.Services; 3 | global using ControlR.Libraries.Shared.Models; 4 | global using ControlR.Libraries.Shared.Services; 5 | global using ControlR.Libraries.Shared; 6 | global using ControlR.Libraries.Shared.Dtos; 7 | global using ControlR.Libraries.Clients.Messages; 8 | global using Microsoft.Extensions.Logging; 9 | global using ControlR.Libraries.Shared.Dtos.HubDtos; 10 | global using ControlR.Libraries.Shared.Enums; 11 | global using ControlR.Libraries.Shared.Extensions; 12 | global using ControlR.Libraries.Shared.Hubs; 13 | global using ControlR.Libraries.Shared.Primitives; 14 | global using ControlR.Libraries.Signalr.Client; -------------------------------------------------------------------------------- /ControlR.Agent/Program.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Agent.Startup; 2 | using System.CommandLine; 3 | using System.CommandLine.Parsing; 4 | 5 | var rootCommand = new RootCommand("Open-source remote control agent.") 6 | { 7 | CommandProvider.GetInstallCommand(args), 8 | CommandProvider.GetRunCommand(args), 9 | CommandProvider.GetUninstallCommand(args), 10 | CommandProvider.GetEchoDesktopCommand(args) 11 | }; 12 | 13 | return await rootCommand.InvokeAsync(args); -------------------------------------------------------------------------------- /ControlR.Agent/Properties/PublishProfiles/linux-x64.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | x64 9 | ..\ControlR.Web.Server\wwwroot\downloads\linux-x64 10 | FileSystem 11 | <_TargetId>Folder 12 | net9.0 13 | linux-x64 14 | true 15 | true 16 | false 17 | true 18 | 19 | -------------------------------------------------------------------------------- /ControlR.Agent/Properties/PublishProfiles/osx-arm64.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | x86 9 | ..\ControlR.Web.Server\wwwroot\downloads\osx-arm64 10 | FileSystem 11 | <_TargetId>Folder 12 | net9.0 13 | osx-arm64 14 | true 15 | true 16 | false 17 | true 18 | 19 | -------------------------------------------------------------------------------- /ControlR.Agent/Properties/PublishProfiles/osx-x64.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | x64 9 | ..\ControlR.Web.Server\wwwroot\downloads\osx-x64 10 | FileSystem 11 | <_TargetId>Folder 12 | net9.0 13 | osx-x64 14 | true 15 | true 16 | false 17 | true 18 | 19 | -------------------------------------------------------------------------------- /ControlR.Agent/Properties/PublishProfiles/win-x86.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | x86 9 | ..\ControlR.Web.Server\wwwroot\downloads\win-x86 10 | FileSystem 11 | <_TargetId>Folder 12 | net9.0 13 | win-x86 14 | true 15 | true 16 | false 17 | false 18 | true 19 | 20 | -------------------------------------------------------------------------------- /ControlR.Agent/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Run": { 4 | "commandName": "Project", 5 | "commandLineArgs": "run -i localhost", 6 | "environmentVariables": { 7 | "DOTNET_ENVIRONMENT": "Development", 8 | "AppOptions__ServerUri": "http://localhost:5120" 9 | } 10 | }, 11 | "Run (Native Debug)": { 12 | "commandName": "Project", 13 | "commandLineArgs": "run", 14 | "environmentVariables": { 15 | "DOTNET_ENVIRONMENT": "Development", 16 | "AppOptions__ServerUri": "http://localhost:5120" 17 | }, 18 | "nativeDebugging": true 19 | }, 20 | "Install": { 21 | "commandName": "Project", 22 | "commandLineArgs": "install -s http://localhost:5120 -i localhost_5120 -t 3a20b5ac-a2ff-48a3-a4c5-b44d1574a938 -g 509935ea-1387-466f-a74e-34312f34284a,4cc68f13-ad35-495b-9b45-d21a19dbabed", 23 | "environmentVariables": { 24 | "DOTNET_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /ControlR.Agent/Usings.cs: -------------------------------------------------------------------------------- 1 | global using Bitbound.SimpleMessenger; 2 | global using ControlR.Libraries.DevicesCommon.Services; 3 | global using ControlR.Libraries.Shared.Models; 4 | global using ControlR.Libraries.Shared.Services; 5 | global using ControlR.Libraries.Shared; 6 | global using ControlR.Libraries.Shared.Dtos; 7 | global using ControlR.Libraries.Clients.Messages; 8 | global using Microsoft.Extensions.Logging; -------------------------------------------------------------------------------- /ControlR.Agent/appicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbound/ControlR/79e29a6c09b6fbbd08ff37bd7e6b9eca3a9d00b3/ControlR.Agent/appicon.ico -------------------------------------------------------------------------------- /ControlR.Agent/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Serilog": { 3 | "MinimumLevel": { 4 | "Default": "Debug", 5 | "Override": { 6 | "Microsoft": "Warning", 7 | "System": "Warning" 8 | } 9 | } 10 | }, 11 | "AppOptions": { 12 | "DeviceId": "faa9d665-2653-467c-8b29-04e8edef9bb2", 13 | "ServerUri": "http://localhost:5120" 14 | }, 15 | "OTLP_ENDPOINT_URL": "https://localhost:21062" 16 | } -------------------------------------------------------------------------------- /ControlR.Agent/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Serilog": { 3 | "MinimumLevel": { 4 | "Default": "Information", 5 | "Override": { 6 | "Microsoft": "Warning", 7 | "System": "Warning" 8 | } 9 | } 10 | }, 11 | "AppOptions": { 12 | "ServerUri": "https://controlr.app" 13 | } 14 | } -------------------------------------------------------------------------------- /ControlR.Streamer/Extensions/BitmapExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using Windows.Graphics.Imaging; 3 | using System.Drawing.Imaging; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace ControlR.Streamer.Extensions; 7 | public static class BitmapExtensions 8 | { 9 | public static Rectangle ToRectangle(this Bitmap bitmap) 10 | { 11 | return new Rectangle(0, 0, bitmap.Width, bitmap.Height); 12 | } 13 | public static SoftwareBitmap ToSoftwareBitmap(this Bitmap bitmap) 14 | { 15 | var bd = bitmap.LockBits(bitmap.ToRectangle(), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); 16 | var sbBuffer = new byte[bd.Stride * bd.Height]; 17 | Marshal.Copy(bd.Scan0, sbBuffer, 0, sbBuffer.Length); 18 | bitmap.UnlockBits(bd); 19 | 20 | return new SoftwareBitmap(BitmapPixelFormat.Bgra8, bitmap.Width, bitmap.Height); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ControlR.Streamer/Extensions/GraphicsExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | 3 | namespace ControlR.Streamer.Extensions; 4 | 5 | public static class GraphicsExtensions 6 | { 7 | public static DisposableValue GetDisposableHdc(this Graphics graphics) 8 | { 9 | return new DisposableValue( 10 | value: graphics.GetHdc(), 11 | disposeCallback: graphics.ReleaseHdc); 12 | } 13 | } -------------------------------------------------------------------------------- /ControlR.Streamer/Extensions/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Streamer.Helpers; 2 | using ControlR.Streamer.Services; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace ControlR.Streamer.Extensions; 6 | 7 | public static class ServiceCollectionExtensions 8 | { 9 | /// 10 | /// Adds the following services with the specified lifetimes: 11 | /// 12 | /// 13 | /// as Singleton 14 | /// 15 | /// 16 | /// as Singleton 17 | /// 18 | /// 19 | /// 20 | /// 21 | /// 22 | public static IServiceCollection AddScreenCapturer(this IServiceCollection services) 23 | { 24 | return services 25 | .AddSingleton() 26 | .AddSingleton() 27 | .AddSingleton(); 28 | } 29 | } -------------------------------------------------------------------------------- /ControlR.Streamer/Helpers/Disposer.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Streamer.Helpers; 2 | public static class Disposer 3 | { 4 | public static void TryDispose(params IDisposable?[] disposables) 5 | { 6 | foreach (var disposable in disposables) 7 | { 8 | try 9 | { 10 | disposable?.Dispose(); 11 | } 12 | catch 13 | { 14 | // Intentionally ignored. 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ControlR.Streamer/Helpers/DxTextureHelper.cs: -------------------------------------------------------------------------------- 1 | using Windows.Win32.Graphics.Direct3D11; 2 | using Windows.Win32.Graphics.Dxgi.Common; 3 | 4 | internal static class DxTextureHelper 5 | { 6 | public static D3D11_TEXTURE2D_DESC Create2dTextureDescription(int width, int height) 7 | { 8 | return new D3D11_TEXTURE2D_DESC() 9 | { 10 | CPUAccessFlags = D3D11_CPU_ACCESS_FLAG.D3D11_CPU_ACCESS_READ, 11 | BindFlags = 0, 12 | Format = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM, 13 | Width = (uint)width, 14 | Height = (uint)height, 15 | MiscFlags = 0, 16 | MipLevels = 1, 17 | ArraySize = 1, 18 | SampleDesc = { Count = 1, Quality = 0 }, 19 | Usage = D3D11_USAGE.D3D11_USAGE_STAGING, 20 | }; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ControlR.Streamer/Helpers/TryHelper.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Streamer.Helpers; 2 | 3 | public static class TryHelper 4 | { 5 | public static void TryAll( 6 | params Action[] actions) 7 | { 8 | foreach (var action in actions) 9 | { 10 | try 11 | { 12 | action(); 13 | } 14 | catch 15 | { 16 | // Ignore. 17 | } 18 | } 19 | } 20 | 21 | public static void TryAll( 22 | Action onError, 23 | params Action[] actions) 24 | { 25 | foreach (var action in actions) 26 | { 27 | try 28 | { 29 | action(); 30 | } 31 | catch (Exception ex) 32 | { 33 | try 34 | { 35 | onError(ex); 36 | } 37 | catch 38 | { 39 | // Ignore. 40 | } 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /ControlR.Streamer/Messages/CursorChangedMessage.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Enums; 2 | 3 | namespace ControlR.Streamer.Messages; 4 | 5 | public record CursorChangedMessage(WindowsCursor Cursor); -------------------------------------------------------------------------------- /ControlR.Streamer/Messages/DisplayMetricsChangedMessage.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Streamer.Messages; 2 | public record DisplayMetricsChangedMessage(double Mbps, double Fps, double Ips, bool IsUsingGpu, int ImageQuality); -------------------------------------------------------------------------------- /ControlR.Streamer/Messages/DisplaySettingsChangedMessage.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Streamer.Messages; 2 | internal record DisplaySettingsChangedMessage(); 3 | -------------------------------------------------------------------------------- /ControlR.Streamer/Messages/SessionEndReasonsEx.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Streamer.Messages; 2 | 3 | public enum SessionEndReasonsEx 4 | { 5 | Logoff = 1, 6 | SystemShutdown = 2, 7 | SuspendMode = 3 8 | } 9 | -------------------------------------------------------------------------------- /ControlR.Streamer/Messages/SessionSwitchReasonEx.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Streamer.Messages; 2 | 3 | public enum SessionSwitchReasonEx 4 | { 5 | ConsoleConnect = 1, 6 | ConsoleDisconnect = 2, 7 | RemoteConnect = 3, 8 | RemoteDisconnect = 4, 9 | SessionLogon = 5, 10 | SessionLogoff = 6, 11 | SessionLock = 7, 12 | SessionUnlock = 8, 13 | SessionRemoteControl = 9 14 | } 15 | -------------------------------------------------------------------------------- /ControlR.Streamer/Messages/WindowsSessionEndingMessage.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Streamer.Messages; 2 | 3 | public record WindowsSessionEndingMessage(SessionEndReasonsEx Reason); -------------------------------------------------------------------------------- /ControlR.Streamer/Messages/WindowsSessionSwitchedMessage.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Streamer.Messages; 2 | 3 | public record WindowsSessionSwitchedMessage(SessionSwitchReasonEx Reason, int SessionId); -------------------------------------------------------------------------------- /ControlR.Streamer/Models/DisplayInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Numerics; 3 | 4 | namespace ControlR.Streamer.Models; 5 | 6 | public class DisplayInfo 7 | { 8 | public required string DeviceName { get; set; } 9 | public string DisplayName { get; set; } = string.Empty; 10 | public nint Hmon { get; set; } 11 | public bool IsPrimary { get; set; } 12 | public Rectangle MonitorArea { get; set; } 13 | public double ScaleFactor { get; set; } = 1; 14 | public Vector2 ScreenSize { get; set; } 15 | public Rectangle WorkArea { get; set; } 16 | } 17 | -------------------------------------------------------------------------------- /ControlR.Streamer/Options/StartupOptions.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Streamer.Options; 2 | 3 | public class StartupOptions 4 | { 5 | private Uri? _serverOrigin; 6 | private Uri? _webSocketUri; 7 | 8 | public bool NotifyUser { get; set; } 9 | 10 | public Uri ServerOrigin 11 | { 12 | get => _serverOrigin ?? throw new InvalidOperationException("ServerOrigin hasn't been set yet."); 13 | set => _serverOrigin = value; 14 | } 15 | 16 | public Guid SessionId { get; set; } 17 | 18 | public string? ViewerName { get; set; } 19 | 20 | public Uri WebSocketUri 21 | { 22 | get => _webSocketUri ?? throw new InvalidOperationException("WebSocketUri hasn't been set yet."); 23 | set => _webSocketUri = value; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ControlR.Streamer/PathConstants.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Streamer; 2 | 3 | internal static class PathConstants 4 | { 5 | public static string GetAppSettingsPath(string appDataFolder) 6 | { 7 | var dir = GetSettingsDirectory(appDataFolder); 8 | return Path.Combine(dir, "appsettings.json"); 9 | } 10 | 11 | public static string GetLogsPath(string appDataFolder) 12 | { 13 | var settingsDir = GetSettingsDirectory(appDataFolder); 14 | return Path.Combine(settingsDir, "Logs", "ControlR.Streamer", "LogFile.log"); 15 | } 16 | 17 | private static string GetSettingsDirectory(string appDataFolder) 18 | { 19 | var settingsDir = Path.Combine( 20 | Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), 21 | "ControlR"); 22 | 23 | if (SystemEnvironment.Instance.IsDebug) 24 | { 25 | settingsDir = Path.Combine(settingsDir, "Debug"); 26 | } 27 | 28 | settingsDir = Path.Combine(settingsDir, appDataFolder); 29 | 30 | return Directory.CreateDirectory(settingsDir).FullName; 31 | } 32 | } -------------------------------------------------------------------------------- /ControlR.Streamer/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("ControlR.Streamer.Tests")] 4 | [assembly: InternalsVisibleTo("ControlR.Libraries.ScreenCapture.Benchmarks")] -------------------------------------------------------------------------------- /ControlR.Streamer/Properties/PublishProfiles/win-x86.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\publish\ 10 | FileSystem 11 | <_TargetId>Folder 12 | net9.0-windows10.0.20348.0 13 | win-x86 14 | true 15 | false 16 | false 17 | false 18 | 19 | -------------------------------------------------------------------------------- /ControlR.Streamer/Usings.cs: -------------------------------------------------------------------------------- 1 | global using ControlR.Devices.Native.Services; 2 | global using ControlR.Libraries.DevicesCommon.Services; 3 | global using ControlR.Libraries.Shared.Extensions; 4 | global using ControlR.Streamer.Options; 5 | global using ControlR.Libraries.Shared.Services; 6 | global using ControlR.Streamer; 7 | global using ControlR.Libraries.Shared.Dtos; 8 | global using Microsoft.Extensions.Logging; 9 | global using ControlR.Libraries.Shared.Dtos.StreamerDtos; 10 | global using ControlR.Libraries.Shared.Primitives; 11 | global using ControlR.Libraries.Clients.Messages; -------------------------------------------------------------------------------- /ControlR.Streamer/appicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbound/ControlR/79e29a6c09b6fbbd08ff37bd7e6b9eca3a9d00b3/ControlR.Streamer/appicon.ico -------------------------------------------------------------------------------- /ControlR.Streamer/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Serilog": { 3 | "MinimumLevel": { 4 | "Default": "Debug", 5 | "Override": { 6 | "Microsoft": "Warning", 7 | "System": "Warning" 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ControlR.Streamer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Serilog": { 3 | "MinimumLevel": { 4 | "Default": "Information", 5 | "Override": { 6 | "Microsoft": "Warning", 7 | "System": "Warning" 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ControlR.Web.AppHost/ControlR.Web.AppHost.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | true 9 | 81fccb1a-eb24-4545-a8ac-430f71e5178b 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ControlR.Web.AppHost/Program.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Web.ServiceDefaults; 2 | 3 | var builder = DistributedApplication.CreateBuilder(args); 4 | 5 | var pgUser = builder.AddParameter("PgUser", true); 6 | var pgPassword = builder.AddParameter("PgPassword", true); 7 | 8 | var postgres = builder 9 | .AddPostgres(ServiceNames.Postgres, pgUser, pgPassword, port: 5432) 10 | .WithLifetime(ContainerLifetime.Persistent) 11 | .WithDataVolume("controlr-data") 12 | .ExcludeFromManifest(); 13 | 14 | var pgHost = postgres.GetEndpoint("tcp"); 15 | 16 | var web = builder 17 | .AddProject(ServiceNames.Controlr, launchProfileName: "https") 18 | .WithEnvironment("POSTGRES_USER", pgUser) 19 | .WithEnvironment("POSTGRES_PASSWORD", pgPassword) 20 | .WithEnvironment("ControlR_POSTGRES_HOST", pgHost) 21 | .WithReference(postgres) 22 | .WaitFor(postgres); 23 | 24 | var webEndpoint = web.GetEndpoint("http"); 25 | 26 | builder 27 | .AddProject(ServiceNames.ControlrAgent, "Run") 28 | .WithEnvironment("AppOptions__ServerUri", webEndpoint) 29 | .WaitFor(web) 30 | .ExcludeFromManifest(); 31 | 32 | builder.Build().Run(); 33 | -------------------------------------------------------------------------------- /ControlR.Web.AppHost/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "Parameters": { 9 | "PgUser": "postgres", 10 | "PgPassword": "password" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ControlR.Web.AppHost/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning", 6 | "Aspire.Hosting.Dcp": "Warning" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Authz/Policies/RequireServerAdministratorPolicy.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Client.Authz.Policies; 2 | 3 | public static class RequireServerAdministratorPolicy 4 | { 5 | public const string PolicyName = "RequireServerAdministratorPolicy"; 6 | public static AuthorizationPolicy Create() 7 | { 8 | return new AuthorizationPolicyBuilder() 9 | .RequireAuthenticatedUser() 10 | .RequireRole(RoleNames.ServerAdministrator) 11 | .Build(); 12 | } 13 | } -------------------------------------------------------------------------------- /ControlR.Web.Client/Authz/RoleNames.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Client.Authz; 2 | 3 | public static class RoleNames 4 | { 5 | public const string AgentInstaller = "Agent Installer"; 6 | public const string DeviceSuperUser = "Device Superuser"; 7 | public const string ServerAdministrator = "Server Administrator"; 8 | public const string TenantAdministrator = "Tenant Administrator"; 9 | } 10 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Authz/UserClaim.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Client.Authz; 2 | 3 | public class UserClaim 4 | { 5 | public required string Type { get; set; } 6 | public required string Value { get; set; } 7 | } 8 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Authz/UserClaimTypes.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Client.Authz; 2 | 3 | public static class UserClaimTypes 4 | { 5 | public const string UserId = "controlr:user:id"; 6 | public const string TenantId = "controlr:tenant:id"; 7 | } -------------------------------------------------------------------------------- /ControlR.Web.Client/Authz/UserInfo.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Client.Authz; 2 | 3 | // Add properties to this class and update the server and client AuthenticationStateProviders 4 | // to expose more information about the authenticated user to the client. 5 | public class UserInfo 6 | { 7 | public List Claims { get; set; } = []; 8 | public required string Email { get; set; } 9 | public List Roles { get; set; } = []; 10 | public required string UserId { get; set; } 11 | } -------------------------------------------------------------------------------- /ControlR.Web.Client/ClientRoutes.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Client; 2 | 3 | public static class ClientRoutes 4 | { 5 | public const string About = "/about"; 6 | public const string Deploy = "/deploy"; 7 | public const string Home = "/"; 8 | public const string Invite = "/invite"; 9 | public const string InviteConfirmation = InviteConfirmationBase + "/{activationCode?}"; 10 | public const string InviteConfirmationBase = "/invite-confirmation"; 11 | public const string Permissions = "/permissions"; 12 | public const string ServerStats = "/server-stats"; 13 | public const string Settings = "/settings"; 14 | } 15 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Components/ContentWindows/DeviceContentHarness.razor.css: -------------------------------------------------------------------------------- 1 | .device-content-window-harness { 2 | position: fixed; 3 | bottom: 0; 4 | left: 0; 5 | width: 100vw; 6 | display: flex; 7 | flex-wrap: nowrap; 8 | overflow-x: auto; 9 | gap: 10px; 10 | z-index: 9000; 11 | pointer-events: none; 12 | } -------------------------------------------------------------------------------- /ControlR.Web.Client/Components/ContentWindows/DeviceContentInstance.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | 3 | namespace ControlR.Web.Client.Components.ContentWindows; 4 | 5 | public class DeviceContentInstance(DeviceViewModel device, RenderFragment content, DeviceContentInstanceType contentType) 6 | { 7 | public RenderFragment Content { get; } = content; 8 | public DeviceContentInstanceType ContentType { get; } = contentType; 9 | 10 | public string ContentTypeName 11 | { 12 | get 13 | { 14 | return ContentType switch 15 | { 16 | DeviceContentInstanceType.RemoteControl => "Remote", 17 | DeviceContentInstanceType.Terminal => "Terminal", 18 | _ => "Content" 19 | }; 20 | } 21 | } 22 | 23 | public DeviceViewModel DeviceUpdate { get; } = device; 24 | public Guid WindowId { get; } = Guid.NewGuid(); 25 | } 26 | 27 | public enum DeviceContentInstanceType 28 | { 29 | RemoteControl, 30 | Terminal 31 | } -------------------------------------------------------------------------------- /ControlR.Web.Client/Components/Dashboard.razor.css: -------------------------------------------------------------------------------- 1 | .child-content-grid { 2 | display: grid; 3 | grid-template-columns: min-content auto; 4 | text-align: left; 5 | column-gap: 20px; 6 | overflow-x: hidden; 7 | text-overflow: ellipsis; 8 | } 9 | 10 | ::deep .child-content-grid > :nth-child(odd) { 11 | white-space: nowrap !important; 12 | } 13 | 14 | .top-controls-grid { 15 | display: grid; 16 | grid-template-columns: minmax(min-content, 400px); 17 | } -------------------------------------------------------------------------------- /ControlR.Web.Client/Components/Dialogs/EditDeviceDialog.razor.css: -------------------------------------------------------------------------------- 1 | ::deep .no-wrap textarea { 2 | white-space: nowrap !important; 3 | font-family: monospace; 4 | } -------------------------------------------------------------------------------- /ControlR.Web.Client/Components/JsInteropableComponent.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Microsoft.JSInterop; 3 | 4 | namespace ControlR.Web.Client.Components; 5 | 6 | public class JsInteropableComponent : ViewportAwareComponent 7 | { 8 | private IJSObjectReference? _jsModule; 9 | 10 | [Inject] 11 | public required IJSRuntime JsRuntime { get; init; } 12 | 13 | protected IJSObjectReference JsModule => _jsModule ?? throw new InvalidOperationException("JS module is not initialized"); 14 | protected ManualResetEventAsync JsModuleReady { get; } = new(); 15 | 16 | protected override async Task OnAfterRenderAsync(bool firstRender) 17 | { 18 | await base.OnAfterRenderAsync(firstRender); 19 | if (firstRender) 20 | { 21 | var componentType = GetType(); 22 | var assembly = componentType.Assembly.GetName().Name!; 23 | var jsPath = componentType.FullName! 24 | .Replace($"{assembly}", "") 25 | .Replace(".", "/") 26 | + ".razor.js"; 27 | 28 | _jsModule ??= await JsRuntime.InvokeAsync("import", jsPath); 29 | 30 | JsModuleReady.Set(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Components/Pages/About.razor.css: -------------------------------------------------------------------------------- 1 | .about-grid { 2 | display: grid; 3 | grid-template-columns: auto 1fr; 4 | column-gap: 15px; 5 | row-gap: 20px; 6 | } -------------------------------------------------------------------------------- /ControlR.Web.Client/Components/Pages/Home.razor: -------------------------------------------------------------------------------- 1 | @attribute [Route(ClientRoutes.Home)] 2 | 3 | Home 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Components/Pages/Invite.razor.css: -------------------------------------------------------------------------------- 1 | ::deep .mud-table-toolbar { 2 | flex-wrap: wrap !important; 3 | column-gap: 20px; 4 | margin-bottom: 20px; 5 | } 6 | 7 | ::deep .invite-data-grid { 8 | min-height: 150px; 9 | } -------------------------------------------------------------------------------- /ControlR.Web.Client/Components/Pages/Permissions.razor: -------------------------------------------------------------------------------- 1 | @attribute [Route(ClientRoutes.Permissions)] 2 | @attribute [Authorize(Roles = RoleNames.TenantAdministrator)] 3 | 4 | Permissions 5 | 6 | Permissions 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Components/Shared/RadialGauge.razor.css: -------------------------------------------------------------------------------- 1 | .gauge-base { 2 | transition: stroke-dashoffset 0.35s; 3 | transform: rotate(-90deg); 4 | transform-origin: 50% 50%; 5 | } 6 | 7 | .gauge-progress { 8 | transition: stroke-dashoffset 0.35s; 9 | transform: rotate(-90deg); 10 | transform-origin: 50% 50%; 11 | } 12 | 13 | .center-value { 14 | position: absolute; 15 | top: 50%; 16 | left: 50%; 17 | transform: translate(-50%, -50%); 18 | } 19 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Components/Terminal.razor.css: -------------------------------------------------------------------------------- 1 | .terminal-grid { 2 | position: relative; 3 | display: grid; 4 | grid-template-rows: 1fr auto; 5 | row-gap: 10px; 6 | overflow: hidden; 7 | height: calc(100% - .5rem); 8 | border-radius: 10px; 9 | margin: .25rem; 10 | padding: .75rem; 11 | background-color: rgb(12,12,12); 12 | } 13 | 14 | .terminal-output-container { 15 | overflow: auto; 16 | height: 100%; 17 | unicode-bidi: embed; 18 | font-family: monospace; 19 | white-space: pre; 20 | } -------------------------------------------------------------------------------- /ControlR.Web.Client/Components/ViewportAwareComponent.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | 3 | namespace ControlR.Web.Client.Components; 4 | 5 | public class ViewportAwareComponent : ComponentBase, IBrowserViewportObserver, IAsyncDisposable 6 | { 7 | public Breakpoint CurrentBreakpoint { get; private set; } 8 | 9 | public Guid Id { get; } = Guid.NewGuid(); 10 | 11 | [Inject] 12 | public required IBrowserViewportService ViewportService { get; init; } 13 | 14 | public virtual async ValueTask DisposeAsync() 15 | { 16 | await ViewportService.UnsubscribeAsync(this); 17 | GC.SuppressFinalize(this); 18 | } 19 | 20 | public async Task NotifyBrowserViewportChangeAsync(BrowserViewportEventArgs browserViewportEventArgs) 21 | { 22 | CurrentBreakpoint = browserViewportEventArgs.Breakpoint; 23 | await InvokeAsync(StateHasChanged); 24 | } 25 | 26 | protected override async Task OnInitializedAsync() 27 | { 28 | CurrentBreakpoint = await ViewportService.GetCurrentBreakpointAsync(); 29 | await ViewportService.SubscribeAsync(this); 30 | await base.OnInitializedAsync(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Components/Welcome.razor: -------------------------------------------------------------------------------- 1 | @inject IControlrApi ServerSettingsApi 2 |
3 | 4 | Welcome to ControlR 5 | 6 |
7 | 8 | Login 9 | 10 |
11 | @if (_isPublicRegistrationEnabled) 12 | { 13 |
14 | 15 | Register 16 | 17 |
18 | } 19 |
20 | 21 | @code { 22 | private bool _isPublicRegistrationEnabled; 23 | 24 | protected override async Task OnInitializedAsync() 25 | { 26 | var settingsResults = await ServerSettingsApi.GetServerSettings(); 27 | if (settingsResults.IsSuccess) 28 | { 29 | _isPublicRegistrationEnabled = settingsResults.Value.IsPublicRegistrationEnabled; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /ControlR.Web.Client/DataValidation/EqualToAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace ControlR.Web.Client.DataValidation; 4 | 5 | 6 | public class EqualToAttribute(string comparisonProperty) : ValidationAttribute 7 | { 8 | private readonly string _comparisonProperty = comparisonProperty; 9 | 10 | protected override ValidationResult? IsValid(object? value, ValidationContext validationContext) 11 | { 12 | var currentValue = value; 13 | var property = validationContext.ObjectType.GetProperty(_comparisonProperty) 14 | ?? throw new ArgumentException("Property with this name not found."); 15 | 16 | var comparisonValue = property.GetValue(validationContext.ObjectInstance); 17 | 18 | if (!Equals(currentValue, comparisonValue)) 19 | { 20 | return new ValidationResult(ErrorMessage ?? $"{validationContext.MemberName} must be equal to {_comparisonProperty}"); 21 | } 22 | 23 | return ValidationResult.Success; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Enums/ControlMode.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Client.Enums; 2 | internal enum ControlMode 3 | { 4 | Mouse, 5 | Touch, 6 | } 7 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Enums/ViewMode.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Client.Enums; 2 | public enum ViewMode 3 | { 4 | Fit, 5 | Stretch, 6 | Original 7 | } 8 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Enums/WindowState.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Client.Enums; 2 | public enum WindowState 3 | { 4 | Restored, 5 | Minimized, 6 | Maximized 7 | } -------------------------------------------------------------------------------- /ControlR.Web.Client/Extensions/AuthenticationStateProviderExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components.Authorization; 2 | 3 | namespace ControlR.Web.Client.Extensions; 4 | 5 | public static class AuthenticationStateProviderExtensions 6 | { 7 | public static async Task IsAuthenticated(this AuthenticationStateProvider provider) 8 | { 9 | var state = await provider.GetAuthenticationStateAsync(); 10 | return state.User.Identity?.IsAuthenticated ?? false; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Extensions/DialogServiceExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Client.Extensions; 2 | 3 | public static class DialogServiceExtensions 4 | { 5 | public static async Task ShowPrompt( 6 | this IDialogService dialogService, 7 | string title, 8 | string? subtitle = null, 9 | string? inputHintText = null, 10 | MaxWidth maxWidth = MaxWidth.Small) 11 | { 12 | inputHintText ??= "Enter your response here."; 13 | 14 | var dialogOptions = new DialogOptions 15 | { 16 | BackdropClick = false, 17 | FullWidth = true, 18 | MaxWidth = maxWidth 19 | }; 20 | 21 | var parameters = new DialogParameters 22 | { 23 | { nameof(PromptDialog.Title), title }, 24 | { nameof(PromptDialog.Subtitle), subtitle }, 25 | { nameof(PromptDialog.InputHintText), inputHintText } 26 | }; 27 | 28 | var dialogRef = await dialogService.ShowAsync(title, parameters, dialogOptions); 29 | var result = await dialogRef.Result; 30 | if (result?.Data is string { } response) 31 | { 32 | return response; 33 | } 34 | 35 | return null; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Extensions/IMessengerExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Client.Extensions; 2 | 3 | public static class MessengerExtensions 4 | { 5 | public static async Task SendToast(this IMessenger messenger, string message, Severity severity = Severity.Info) 6 | { 7 | await messenger.Send(new ToastMessage(message, severity)); 8 | } 9 | } -------------------------------------------------------------------------------- /ControlR.Web.Client/Extensions/MessageSeverityExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Client.Extensions; 2 | 3 | public static class MessageSeverityExtensions 4 | { 5 | public static Severity ToMudSeverity(this MessageSeverity messageSeverity) 6 | { 7 | return messageSeverity switch 8 | { 9 | MessageSeverity.Information => Severity.Info, 10 | MessageSeverity.Success => Severity.Success, 11 | MessageSeverity.Warning => Severity.Warning, 12 | MessageSeverity.Error => Severity.Error, 13 | _ => Severity.Info 14 | }; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Models/Messages/DesktopChangedMessage.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Client.Models.Messages; 2 | internal class DesktopChangedMessage(Guid sessionId) 3 | { 4 | public Guid SessionId { get; } = sessionId; 5 | } 6 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Models/Messages/DeviceContentWindowStateMessage.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Client.Models.Messages; 2 | internal class DeviceContentWindowStateMessage(Guid windowId, WindowState state) 3 | { 4 | public Guid WindowId { get; } = windowId; 5 | public WindowState State { get; } = state; 6 | } 7 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Models/Messages/HubConnectionStateChangedMessage.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.SignalR.Client; 2 | 3 | namespace ControlR.Web.Client.Models.Messages; 4 | 5 | public record HubConnectionStateChangedMessage(HubConnectionState NewState); 6 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Models/Messages/ToastMessage.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Client.Models.Messages; 2 | 3 | /// 4 | /// This message allows using outside of UI elements. 5 | /// If injecting the interface into a service that 6 | /// gets instantiated before the UI is ready, the Snackbar ctor will throw. 7 | /// 8 | /// 9 | /// 10 | internal class ToastMessage(string message, Severity severity) 11 | { 12 | public string Message { get; } = message; 13 | public Severity Severity { get; } = severity; 14 | } 15 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Models/RemoteControlSession.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Client.Models; 2 | public class RemoteControlSession(DeviceViewModel device, int initialSystemSession) 3 | { 4 | public DeviceViewModel Device { get; } = device; 5 | public int InitialSystemSession { get; } = initialSystemSession; 6 | public Guid SessionId { get; private set; } = Guid.NewGuid(); 7 | 8 | public void CreateNewSessionId() 9 | { 10 | SessionId = Guid.NewGuid(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ControlR.Web.Client/RedirectToLogin.razor: -------------------------------------------------------------------------------- 1 | @inject NavigationManager NavigationManager 2 | 3 | @code { 4 | protected override void OnInitialized() 5 | { 6 | NavigationManager.NavigateTo($"Account/Login?returnUrl={Uri.EscapeDataString(NavigationManager.Uri)}", forceLoad: true); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Routes.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | You are not authorized to access this page. 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Services/BusyCounter.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Client.Services; 2 | 3 | public interface IBusyCounter 4 | { 5 | bool IsBusy { get; } 6 | 7 | int PendingOperations { get; } 8 | 9 | IDisposable IncrementBusyCounter(Action? additionalDisposedAction = null); 10 | } 11 | 12 | internal class BusyCounter(IMessenger messenger) : IBusyCounter 13 | { 14 | private volatile int _busyCounter; 15 | 16 | public bool IsBusy => _busyCounter > 0; 17 | 18 | 19 | public int PendingOperations => _busyCounter; 20 | 21 | public IDisposable IncrementBusyCounter(Action? additionalDisposedAction = null) 22 | { 23 | Interlocked.Increment(ref _busyCounter); 24 | 25 | messenger.SendGenericMessage(EventMessageKind.PendingOperationsChanged); 26 | 27 | return new CallbackDisposable(() => 28 | { 29 | Interlocked.Decrement(ref _busyCounter); 30 | messenger.SendGenericMessage(EventMessageKind.PendingOperationsChanged); 31 | 32 | additionalDisposedAction?.Invoke(); 33 | }); 34 | } 35 | } -------------------------------------------------------------------------------- /ControlR.Web.Client/Services/ClipboardManager.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Client.Services; 2 | 3 | public interface IClipboardManager 4 | { 5 | Task SetText(string text); 6 | Task GetText(); 7 | } 8 | 9 | internal class ClipboardManager(IJsInterop jsInterop) : IClipboardManager 10 | { 11 | private readonly IJsInterop _jsInterop = jsInterop; 12 | 13 | public async Task GetText() 14 | { 15 | return await _jsInterop.GetClipboardText(); 16 | } 17 | 18 | public async Task SetText(string? text) 19 | { 20 | await _jsInterop.SetClipboardText(text); 21 | } 22 | } -------------------------------------------------------------------------------- /ControlR.Web.Client/Services/ScreenWake.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Client.Services; 2 | 3 | public interface IScreenWake 4 | { 5 | Task SetScreenWakeLock(bool isWakeEnabled); 6 | } 7 | public class ScreenWake( 8 | IJsInterop jsInterop, 9 | ILogger logger) : IScreenWake 10 | { 11 | private readonly IJsInterop _jsInterop = jsInterop; 12 | private readonly ILogger _logger = logger; 13 | public async Task SetScreenWakeLock(bool isWakeEnabled) 14 | { 15 | try 16 | { 17 | await _jsInterop.SetScreenWakeLock(isWakeEnabled); 18 | } 19 | catch (Exception ex) 20 | { 21 | _logger.LogError(ex, "Error while requesting screen wake lock."); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Services/Stores/DeviceStore.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.SignalR.Client; 2 | 3 | namespace ControlR.Web.Client.Services.Stores; 4 | 5 | public interface IDeviceStore : IStoreBase 6 | { } 7 | 8 | internal class DeviceStore : StoreBase, IDeviceStore 9 | { 10 | public DeviceStore( 11 | IControlrApi controlrApi, 12 | ISnackbar snackbar, 13 | IMessenger messenger, 14 | ILogger logger) 15 | : base(controlrApi, snackbar, logger) 16 | { 17 | messenger.Register(this, HandleHubConnectionStateChanged); 18 | } 19 | 20 | protected override async Task RefreshImpl() 21 | { 22 | await foreach (var device in ControlrApi.GetAllDevices()) 23 | { 24 | Cache.AddOrUpdate(device.Id, device, (_, _) => device); 25 | } 26 | } 27 | 28 | 29 | private async Task HandleHubConnectionStateChanged(object subscriber, HubConnectionStateChangedMessage message) 30 | { 31 | if (message.NewState == HubConnectionState.Connected) 32 | { 33 | await Refresh(); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /ControlR.Web.Client/Services/Stores/InviteStore.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace ControlR.Web.Client.Services.Stores; 4 | 5 | public interface IInviteStore : IStoreBase 6 | { } 7 | 8 | public class InviteStore( 9 | IControlrApi controlrApi, 10 | ISnackbar snackbar, 11 | ILogger> logger) 12 | : StoreBase(controlrApi, snackbar, logger), IInviteStore 13 | { 14 | private readonly IControlrApi _controlrApi = controlrApi; 15 | protected override async Task RefreshImpl() 16 | { 17 | Cache.Clear(); 18 | var getResult = await _controlrApi.GetPendingTenantInvites(); 19 | if (!getResult.IsSuccess) 20 | { 21 | Snackbar.Add(getResult.Reason, Severity.Error); 22 | return; 23 | } 24 | 25 | foreach (var invite in getResult.Value) 26 | { 27 | Cache.AddOrUpdate(invite.Id, invite, (_, _) => invite); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ControlR.Web.Client/Services/Stores/RoleStore.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Web.Client.ViewModels; 2 | 3 | namespace ControlR.Web.Client.Services.Stores; 4 | 5 | public interface IRoleStore : IStoreBase 6 | { } 7 | 8 | internal class RoleStore( 9 | IControlrApi controlrApi, 10 | ISnackbar snackbar, 11 | ILogger logger) 12 | : StoreBase(controlrApi, snackbar, logger), IRoleStore 13 | { 14 | protected override async Task RefreshImpl() 15 | { 16 | Cache.Clear(); 17 | var getResult = await ControlrApi.GetAllRoles(); 18 | if (!getResult.IsSuccess) 19 | { 20 | Snackbar.Add(getResult.Reason, Severity.Error); 21 | return; 22 | } 23 | 24 | foreach (var role in getResult.Value) 25 | { 26 | var vm = new RoleViewModel(role); 27 | Cache.AddOrUpdate(vm.Id, vm, (_, _) => vm); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /ControlR.Web.Client/Services/Stores/TagStore.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Web.Client.ViewModels; 2 | 3 | namespace ControlR.Web.Client.Services.Stores; 4 | 5 | public interface ITagStore : IStoreBase 6 | { } 7 | 8 | public class TagStore(IControlrApi controlrApi, ISnackbar snackbar, ILogger logger) 9 | : StoreBase(controlrApi, snackbar, logger), ITagStore 10 | { 11 | protected override async Task RefreshImpl() 12 | { 13 | Cache.Clear(); 14 | var getResult = await ControlrApi.GetAllTags(includeLinkedIds: true); 15 | if (!getResult.IsSuccess) 16 | { 17 | Snackbar.Add(getResult.Reason, Severity.Error); 18 | return; 19 | } 20 | 21 | foreach (var tag in getResult.Value) 22 | { 23 | var vm = new TagViewModel(tag); 24 | Cache.AddOrUpdate(vm.Id, vm, (_, _) => vm); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /ControlR.Web.Client/Services/Stores/UserStore.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Client.Services.Stores; 2 | 3 | public interface IUserStore : IStoreBase 4 | { 5 | } 6 | 7 | public class UserStore( 8 | IControlrApi controlrApi, 9 | ISnackbar snackbar, 10 | ILogger logger) : StoreBase(controlrApi, snackbar, logger), IUserStore 11 | { 12 | protected override async Task RefreshImpl() 13 | { 14 | Cache.Clear(); 15 | var getResult = await ControlrApi.GetAllUsers(); 16 | if (!getResult.IsSuccess) 17 | { 18 | Snackbar.Add(getResult.Reason, Severity.Error); 19 | return; 20 | } 21 | 22 | foreach (var user in getResult.Value) 23 | { 24 | Cache.AddOrUpdate(user.Id, user, (_, _) => user); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /ControlR.Web.Client/ViewModels/RoleViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Client.ViewModels; 2 | 3 | public class RoleViewModel(RoleResponseDto dto) : IHasPrimaryKey 4 | { 5 | public Guid Id { get; } = dto.Id; 6 | public string Name { get; } = dto.Name; 7 | public ConcurrentHashSet UserIds { get; } = [.. dto.UserIds]; 8 | } 9 | -------------------------------------------------------------------------------- /ControlR.Web.Client/ViewModels/TagViewModel.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Client.ViewModels; 2 | 3 | public class TagViewModel(TagResponseDto dto) : IHasPrimaryKey 4 | { 5 | public ConcurrentHashSet DeviceIds { get; } = [.. dto.DeviceIds]; 6 | 7 | public Guid Id { get; } = dto.Id; 8 | 9 | public string Name { get; } = dto.Name; 10 | 11 | public TagType Type { get; set; } = dto.Type; 12 | 13 | public ConcurrentHashSet UserIds { get; } = [.. dto.UserIds]; 14 | } -------------------------------------------------------------------------------- /ControlR.Web.Client/wwwroot/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ControlR.Web.Client/wwwroot/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Api/LogoutController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace ControlR.Web.Server.Api; 4 | 5 | [Route("api/[controller]")] 6 | [ApiController] 7 | [Authorize] 8 | public class LogoutController : ControllerBase 9 | { 10 | [HttpPost] 11 | public async Task Logout( 12 | [FromBody] object _, 13 | [FromServices] SignInManager signInManager) 14 | { 15 | await signInManager.SignOutAsync(); 16 | return NoContent(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Api/RolesController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System.Collections.Immutable; 3 | 4 | namespace ControlR.Web.Server.Api; 5 | 6 | [Route("api/roles")] 7 | [ApiController] 8 | [Authorize(Roles = RoleNames.TenantAdministrator)] 9 | public class RolesController : ControllerBase 10 | { 11 | [HttpGet] 12 | public async Task> GetAll( 13 | [FromServices] RoleManager roleManager) 14 | { 15 | var roles = await roleManager.Roles 16 | .AsNoTracking() 17 | .Include(r => r.UserRoles) 18 | .Select(r => new 19 | { 20 | r.Id, 21 | Name = r.Name ?? "", 22 | UserIds = r.UserRoles!.Select(u => u.UserId) 23 | }) 24 | .ToListAsync(); 25 | 26 | var dtos = roles.Select(x => new RoleResponseDto(x.Id, x.Name, [.. x.UserIds])); 27 | return Ok(dtos); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Api/ServerSettingsController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.OutputCaching; 3 | 4 | namespace ControlR.Web.Server.Api; 5 | 6 | [Route("api/server-settings")] 7 | [ApiController] 8 | [OutputCache(Duration = 60)] 9 | public class ServerSettingsController : ControllerBase 10 | { 11 | [HttpGet] 12 | public async Task Get( 13 | [FromServices] AppDb db, 14 | [FromServices] IOptionsMonitor appOptions) 15 | { 16 | var registrationEnabled = appOptions.CurrentValue.EnablePublicRegistration || !await db.Users.AnyAsync(); 17 | return new ServerSettingsDto(registrationEnabled); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Api/UsersController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace ControlR.Web.Server.Api; 4 | 5 | [Route("api/[controller]")] 6 | [ApiController] 7 | [Authorize(Roles = RoleNames.TenantAdministrator)] 8 | public class UsersController : ControllerBase 9 | { 10 | [HttpGet] 11 | [Authorize(Roles = RoleNames.TenantAdministrator)] 12 | public async Task>> GetAll( 13 | [FromServices] AppDb appDb) 14 | { 15 | return await appDb.Users 16 | .Select(x => new UserResponseDto(x.Id, x.UserName, x.Email)) 17 | .ToListAsync(); 18 | } 19 | } -------------------------------------------------------------------------------- /ControlR.Web.Server/Authz/AuthorizationPolicyBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Server.Authz; 2 | 3 | public static class AuthorizationPolicyBuilderExtensions 4 | { 5 | public static AuthorizationPolicyBuilder RequireServiceProviderAssertion( 6 | this AuthorizationPolicyBuilder builder, 7 | Func> assertion) 8 | { 9 | builder.Requirements.Add(new ServiceProviderAsyncRequirement(assertion)); 10 | return builder; 11 | } 12 | 13 | public static AuthorizationPolicyBuilder RequireServiceProviderAssertion( 14 | this AuthorizationPolicyBuilder builder, 15 | Func assertion) 16 | { 17 | builder.Requirements.Add(new ServiceProviderRequirement(assertion)); 18 | return builder; 19 | } 20 | } -------------------------------------------------------------------------------- /ControlR.Web.Server/Authz/Roles/RoleFactory.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Helpers; 2 | 3 | namespace ControlR.Web.Server.Authz.Roles; 4 | 5 | public static class RoleFactory 6 | { 7 | public static AppRole[] GetBuiltInRoles() 8 | { 9 | return 10 | [ 11 | new AppRole 12 | { 13 | Id = DeterministicGuid.Create(1), 14 | Name = RoleNames.ServerAdministrator, 15 | NormalizedName = RoleNames.ServerAdministrator.ToUpper() 16 | }, 17 | new AppRole 18 | { 19 | Id = DeterministicGuid.Create(2), 20 | Name = RoleNames.TenantAdministrator, 21 | NormalizedName = RoleNames.TenantAdministrator.ToUpper() 22 | }, 23 | new AppRole 24 | { 25 | Id = DeterministicGuid.Create(3), 26 | Name = RoleNames.DeviceSuperUser, 27 | NormalizedName = RoleNames.DeviceSuperUser.ToUpper() 28 | }, 29 | new AppRole 30 | { 31 | Id = DeterministicGuid.Create(4), 32 | Name = RoleNames.AgentInstaller, 33 | NormalizedName = RoleNames.AgentInstaller.ToUpper() 34 | } 35 | ]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Authz/ServiceProviderAsyncRequirement.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Server.Authz; 2 | 3 | public class ServiceProviderAsyncRequirement( 4 | Func> assertion) 5 | : IAuthorizationRequirement 6 | { 7 | public Func> Assertion { get; } = 8 | assertion; 9 | } -------------------------------------------------------------------------------- /ControlR.Web.Server/Authz/ServiceProviderAsyncRequirementHandler.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Server.Authz; 2 | 3 | public class ServiceProviderAsyncRequirementHandler(IServiceProvider serviceProvider) : IAuthorizationHandler 4 | { 5 | private readonly IServiceProvider _serviceProvider = serviceProvider; 6 | 7 | public async Task HandleAsync(AuthorizationHandlerContext context) 8 | { 9 | foreach (var requirement in context.Requirements.OfType()) 10 | { 11 | if (await requirement.Assertion.Invoke(_serviceProvider, context, this)) 12 | { 13 | context.Succeed(requirement); 14 | } 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /ControlR.Web.Server/Authz/ServiceProviderRequirement.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Server.Authz; 2 | 3 | public class ServiceProviderRequirement( 4 | Func assertion) 5 | : IAuthorizationRequirement 6 | { 7 | public Func Assertion { get; } = 8 | assertion; 9 | } -------------------------------------------------------------------------------- /ControlR.Web.Server/Authz/ServiceProviderRequirementHandler.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Server.Authz; 2 | 3 | public class ServiceProviderRequirementHandler(IServiceProvider serviceProvider) : IAuthorizationHandler 4 | { 5 | private readonly IServiceProvider _serviceProvider = serviceProvider; 6 | 7 | public async Task HandleAsync(AuthorizationHandlerContext context) 8 | { 9 | var requirements = context.Requirements 10 | .OfType() 11 | .ToAsyncEnumerable(); 12 | 13 | await foreach (var requirement in requirements) 14 | { 15 | if (requirement.Assertion.Invoke(_serviceProvider, context, this)) 16 | { 17 | context.Succeed(requirement); 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /ControlR.Web.Server/Components/Account/IdentityUserAccessor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | 3 | namespace ControlR.Web.Server.Components.Account; 4 | internal sealed class IdentityUserAccessor(UserManager userManager, IdentityRedirectManager redirectManager) 5 | { 6 | public async Task GetRequiredUserAsync(HttpContext context) 7 | { 8 | var user = await userManager.GetUserAsync(context.User); 9 | 10 | if (user is null) 11 | { 12 | redirectManager.RedirectToWithStatus("Account/InvalidUser", $"Error: Unable to load user with ID '{userManager.GetUserId(context.User)}'.", context); 13 | } 14 | 15 | return user; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Components/Account/Pages/AccessDenied.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/AccessDenied" 2 | 3 | Access denied 4 | 5 | You do not have access to this resource. 6 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Components/Account/Pages/ForgotPasswordConfirmation.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/ForgotPasswordConfirmation" 2 | 3 | Forgot password confirmation 4 | 5 | Forgot password confirmation 6 | 7 | Please check your email to reset your password. 8 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Components/Account/Pages/InvalidPasswordReset.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/InvalidPasswordReset" 2 | 3 | Invalid password reset 4 | 5 |

Invalid password reset

6 |

7 | The password reset link is invalid. 8 |

9 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Components/Account/Pages/InvalidUser.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/InvalidUser" 2 | 3 | Invalid user 4 | 5 |

Invalid user

6 | 7 | 8 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Components/Account/Pages/Lockout.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/Lockout" 2 | 3 | Locked out 4 | 5 |
6 |

Locked out

7 |

This account has been locked out. Please try again later.

8 |
9 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Components/Account/Pages/Manage/_Imports.razor: -------------------------------------------------------------------------------- 1 | @layout ManageLayout 2 | @attribute [Microsoft.AspNetCore.Authorization.Authorize] 3 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Components/Account/Pages/ResetPasswordConfirmation.razor: -------------------------------------------------------------------------------- 1 | @page "/Account/ResetPasswordConfirmation" 2 | Reset password confirmation 3 | 4 | 5 | Reset password confirmation 6 | 7 | 8 | 9 | Your password has been reset. Please click here to log in. 10 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Components/Account/Pages/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using MudBlazor 2 | @layout AccountLayout 3 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Components/Account/Shared/AccountLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | @layout ControlR.Web.Client.Components.Layout.MainLayout 3 | @inject NavigationManager NavigationManager 4 | 5 | @if (HttpContext is null) 6 | { 7 |

Loading...

8 | } 9 | else 10 | { 11 | @Body 12 | } 13 | 14 | @code { 15 | [CascadingParameter] 16 | private HttpContext? HttpContext { get; set; } 17 | 18 | protected override void OnParametersSet() 19 | { 20 | if (HttpContext is null) 21 | { 22 | // If this code runs, we're currently rendering in interactive mode, so there is no HttpContext. 23 | // The identity pages need to set cookies, so they require an HttpContext. To achieve this we 24 | // must transition back from interactive mode to a server-rendered page. 25 | NavigationManager.Refresh(forceReload: true); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Components/Account/Shared/ManageLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | @layout AccountLayout 3 | 4 | Manage your account 5 | 6 | 7 | 8 | Change your account settings 9 | 10 | 11 | 12 | @Body 13 | 14 | 15 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Components/Account/Shared/ManageNavMenu.razor: -------------------------------------------------------------------------------- 1 | @inject SignInManager SignInManager 2 | 3 | 4 | Profile 5 | Email 6 | Password 7 | @if (_hasExternalLogins) 8 | { 9 | External logins 10 | } 11 | Two-factor authentication 12 | Personal data 13 | 14 | 15 | @code { 16 | private bool _hasExternalLogins; 17 | 18 | protected override async Task OnInitializedAsync() 19 | { 20 | _hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any(); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /ControlR.Web.Server/Components/Account/Shared/ShowRecoveryCodes.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Recovery codes 5 | 6 | 7 | 8 | 9 | Put these codes in a safe place. 10 | 11 | 12 | 13 | If you lose your device and don't have the recovery codes, you will lose access to your account. 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | @code { 25 | [Parameter] 26 | public string[] RecoveryCodes { get; set; } = []; 27 | 28 | [Parameter] 29 | public string? StatusMessage { get; set; } 30 | 31 | private string JoinedCodes => string.Join('\n', RecoveryCodes); 32 | } 33 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Components/Account/Shared/StatusMessage.razor: -------------------------------------------------------------------------------- 1 | @if (!string.IsNullOrEmpty(DisplayMessage)) 2 | { 3 | var severity = DisplayMessage.StartsWith("Error") ? Severity.Error : Severity.Success; 4 | 5 | @DisplayMessage 6 | } 7 | 8 | @code { 9 | private string? _messageFromCookie; 10 | 11 | [Parameter] public string? Message { get; set; } 12 | 13 | [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; 14 | 15 | private string? DisplayMessage => Message ?? _messageFromCookie; 16 | 17 | protected override void OnInitialized() 18 | { 19 | _messageFromCookie = HttpContext.Request.Cookies[IdentityRedirectManager.StatusCookieName]; 20 | 21 | if (_messageFromCookie is not null) 22 | { 23 | HttpContext.Response.Cookies.Delete(IdentityRedirectManager.StatusCookieName); 24 | } 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /ControlR.Web.Server/Components/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Authorization 4 | @using MudBlazor.StaticInput 5 | @using Microsoft.AspNetCore.Components.Forms 6 | @using Microsoft.AspNetCore.Components.Routing 7 | @using Microsoft.AspNetCore.Components.Web 8 | @using static Microsoft.AspNetCore.Components.Web.RenderMode 9 | @using Microsoft.AspNetCore.Components.Web.Virtualization 10 | @using Microsoft.JSInterop 11 | @using MudBlazor 12 | @using MudBlazor.Services 13 | @using ControlR.Web 14 | @using ControlR.Web.Client 15 | @using ControlR.Web.Server.Components 16 | @using System.Text 17 | @using Microsoft.AspNetCore.Identity 18 | @using Microsoft.AspNetCore.WebUtilities 19 | @using ControlR.Web.Server.Data 20 | @using ControlR.Web.Server.Components.Account.Shared 21 | @using System.ComponentModel.DataAnnotations 22 | @using System.Security.Claims 23 | @using System.Text.Encodings.Web 24 | @using Microsoft.AspNetCore.Authentication 25 | @using System.Globalization 26 | @using QRCoder 27 | @using ControlR.Web.Client.Authz 28 | @using ControlR.Web.Client.Authz.Policies -------------------------------------------------------------------------------- /ControlR.Web.Server/Data/Configuration/ClaimsDbContextOptions.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Server.Data.Configuration; 2 | 3 | public class ClaimsDbContextOptions 4 | { 5 | public Guid TenantId { get; init; } 6 | public Guid UserId { get; init; } 7 | } 8 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Data/Configuration/DbContextOptionsBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Web.Client.Extensions; 2 | using Microsoft.EntityFrameworkCore.Infrastructure; 3 | using System.Security.Claims; 4 | 5 | namespace ControlR.Web.Server.Data.Configuration; 6 | 7 | public static class DbContextOptionsBuilderExtensions 8 | { 9 | public static DbContextOptionsBuilder UseUserClaims( 10 | this DbContextOptionsBuilder builder, 11 | ClaimsPrincipal user) 12 | { 13 | if (!user.TryGetTenantId(out var tenantId) || 14 | !user.TryGetUserId(out var userId)) 15 | { 16 | return builder; 17 | } 18 | 19 | var options = new ClaimsDbContextOptions 20 | { 21 | TenantId = tenantId, 22 | UserId = userId 23 | }; 24 | 25 | if (builder is not IDbContextOptionsBuilderInfrastructure builderInfrastructure) 26 | { 27 | throw new ArgumentException( 28 | $"Expected {nameof(builder)} to be of type {nameof(IDbContextOptionsBuilderInfrastructure)}"); 29 | } 30 | 31 | builderInfrastructure.AddOrUpdateExtension(new ClaimsDbContextOptionsExtension(options)); 32 | 33 | return builder; 34 | } 35 | } -------------------------------------------------------------------------------- /ControlR.Web.Server/Data/Entities/AppRole.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Server.Data.Entities; 2 | 3 | public class AppRole : IdentityRole 4 | { 5 | public List>? UserRoles { get; set; } 6 | } 7 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Data/Entities/AppUser.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations.Schema; 2 | using ControlR.Web.Server.Data.Entities.Bases; 3 | using Microsoft.AspNetCore.Identity; 4 | 5 | 6 | namespace ControlR.Web.Server.Data.Entities; 7 | 8 | public class AppUser : IdentityUser, ITenantEntityBase 9 | { 10 | private DateTimeOffset _createdAt; 11 | 12 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 13 | public DateTimeOffset CreatedAt 14 | { 15 | get => _createdAt; 16 | set 17 | { 18 | if (value == default || 19 | _createdAt != default) 20 | { 21 | return; 22 | } 23 | 24 | _createdAt = value; 25 | } 26 | } 27 | 28 | public bool IsOnline { get; set; } 29 | public List? Tags { get; set; } 30 | public Tenant? Tenant { get; set; } 31 | public Guid TenantId { get; set; } 32 | public List? UserPreferences { get; set; } 33 | public List>? UserRoles { get; set; } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Data/Entities/Bases/EntityBase.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.ComponentModel.DataAnnotations.Schema; 3 | 4 | namespace ControlR.Web.Server.Data.Entities.Bases; 5 | 6 | public interface IEntityBase 7 | { 8 | public DateTimeOffset CreatedAt { get; set; } 9 | public Guid Id { get; set; } 10 | } 11 | 12 | public class EntityBase : IEntityBase 13 | { 14 | private DateTimeOffset _createdAt; 15 | private Guid _id; 16 | 17 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 18 | public DateTimeOffset CreatedAt 19 | { 20 | get => _createdAt; 21 | set 22 | { 23 | if (value == default || 24 | _createdAt != default) 25 | { 26 | return; 27 | } 28 | 29 | _createdAt = value; 30 | } 31 | } 32 | 33 | [Key] 34 | public Guid Id 35 | { 36 | get => _id; 37 | set 38 | { 39 | if (value == Guid.Empty) 40 | { 41 | return; 42 | } 43 | 44 | _id = value; 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /ControlR.Web.Server/Data/Entities/Bases/TenantEntityBase.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Server.Data.Entities.Bases; 2 | 3 | public interface ITenantEntityBase : IEntityBase 4 | { 5 | Tenant? Tenant { get; set; } 6 | Guid TenantId { get; set; } 7 | } 8 | 9 | public class TenantEntityBase : EntityBase, ITenantEntityBase 10 | { 11 | public Tenant? Tenant { get; set; } 12 | public Guid TenantId { get; set; } 13 | } 14 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Data/Entities/Tag.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Enums; 2 | using ControlR.Web.Server.Data.Entities.Bases; 3 | using System.ComponentModel.DataAnnotations; 4 | 5 | namespace ControlR.Web.Server.Data.Entities; 6 | 7 | public class Tag : TenantEntityBase 8 | { 9 | [StringLength(50)] 10 | public required string Name { get; set; } 11 | 12 | public TagType Type { get; set; } 13 | public List? Users { get; set; } 14 | public List? Devices { get; set; } 15 | } 16 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Data/Entities/Tenant.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Web.Server.Data.Entities.Bases; 2 | 3 | namespace ControlR.Web.Server.Data.Entities; 4 | 5 | public class Tenant : EntityBase 6 | { 7 | public List? Devices { get; set; } 8 | public string? Name { get; set; } 9 | public List? Tags { get; set; } 10 | public List? Users { get; set; } 11 | } 12 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Data/Entities/TenantInvite.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Web.Server.Data.Entities.Bases; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace ControlR.Web.Server.Data.Entities; 5 | 6 | public class TenantInvite : TenantEntityBase 7 | { 8 | [Required] 9 | public required string ActivationCode { get; set; } 10 | 11 | [EmailAddress] 12 | public required string InviteeEmail { get; set; } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Data/Entities/UserPreference.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using ControlR.Web.Server.Data.Entities.Bases; 3 | 4 | namespace ControlR.Web.Server.Data.Entities; 5 | 6 | public class UserPreference : TenantEntityBase 7 | { 8 | [StringLength(100)] 9 | [RegularExpression("^[a-zA-Z0-9-]+$", ErrorMessage = "Preference name can only contain letters, numbers, and hyphens.")] 10 | public required string Name { get; set; } 11 | 12 | public AppUser? User { get; set; } 13 | public Guid UserId { get; set; } 14 | 15 | [StringLength(100)] 16 | [RegularExpression("^[a-zA-Z0-9-]+$", ErrorMessage = "Preference values can only contain letters, numbers, and hyphens.")] 17 | public required string Value { get; set; } 18 | } 19 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Data/Migrations/20241207195022_Add_TagNameLength.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace ControlR.Web.Server.Data.Migrations; 6 | 7 | /// 8 | public partial class Add_TagNameLength : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.AlterColumn( 14 | name: "Name", 15 | table: "Tags", 16 | type: "character varying(50)", 17 | maxLength: 50, 18 | nullable: false, 19 | oldClrType: typeof(string), 20 | oldType: "text"); 21 | } 22 | 23 | /// 24 | protected override void Down(MigrationBuilder migrationBuilder) 25 | { 26 | migrationBuilder.AlterColumn( 27 | name: "Name", 28 | table: "Tags", 29 | type: "text", 30 | nullable: false, 31 | oldClrType: typeof(string), 32 | oldType: "character varying(50)", 33 | oldMaxLength: 50); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Data/Migrations/20241212010510_Add_UserIsOnline.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | #nullable disable 4 | 5 | namespace ControlR.Web.Server.Data.Migrations; 6 | 7 | /// 8 | public partial class Add_UserIsOnline : Migration 9 | { 10 | /// 11 | protected override void Up(MigrationBuilder migrationBuilder) 12 | { 13 | migrationBuilder.AddColumn( 14 | name: "IsOnline", 15 | table: "AspNetUsers", 16 | type: "boolean", 17 | nullable: false, 18 | defaultValue: false); 19 | } 20 | 21 | /// 22 | protected override void Down(MigrationBuilder migrationBuilder) 23 | { 24 | migrationBuilder.DropColumn( 25 | name: "IsOnline", 26 | table: "AspNetUsers"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Data/Migrations/20250103164738_Add_AgentInstaller.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.EntityFrameworkCore.Migrations; 3 | 4 | #nullable disable 5 | 6 | namespace ControlR.Web.Server.Data.Migrations; 7 | 8 | /// 9 | public partial class Add_AgentInstaller : Migration 10 | { 11 | /// 12 | protected override void Up(MigrationBuilder migrationBuilder) 13 | { 14 | migrationBuilder.InsertData( 15 | table: "AspNetRoles", 16 | columns: ["Id", "ConcurrencyStamp", "Name", "NormalizedName"], 17 | values: [new Guid("dde33610-89dc-e6a4-8d8a-33f3823a180e"), null, "Agent Installer", "AGENT INSTALLER"]); 18 | } 19 | 20 | /// 21 | protected override void Down(MigrationBuilder migrationBuilder) 22 | { 23 | migrationBuilder.DeleteData( 24 | table: "AspNetRoles", 25 | keyColumn: "Id", 26 | keyValue: new Guid("dde33610-89dc-e6a4-8d8a-33f3823a180e")); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Extensions/HttpExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Server.Extensions; 2 | 3 | public static class HttpExtensions 4 | { 5 | public static Uri ToOrigin(this HttpRequest request) 6 | { 7 | return new Uri($"{request.Scheme}://{request.Host}"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Helpers/CoordinateHelper.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Web.Server.Interfaces; 2 | 3 | namespace ControlR.Web.Server.Helpers; 4 | 5 | public static class CoordinateHelper 6 | { 7 | public static T FindClosestCoordinate(ICoordinate target, IReadOnlyList coordinates) 8 | where T : ICoordinate 9 | { 10 | if (coordinates.Count == 0) 11 | { 12 | throw new ArgumentException("Hosts can't be empty."); 13 | } 14 | 15 | var minDistance = double.MaxValue; 16 | var closestHost = coordinates[0]; 17 | 18 | foreach (var host in coordinates) 19 | { 20 | var distance = CalculateDistance(host, target); 21 | if (distance < minDistance) 22 | { 23 | minDistance = distance; 24 | closestHost = host; 25 | } 26 | } 27 | 28 | return closestHost; 29 | } 30 | 31 | private static double CalculateDistance(ICoordinate a, ICoordinate b) 32 | { 33 | var dx = a.Longitude - b.Longitude; 34 | var dy = a.Latitude - b.Latitude; 35 | return Math.Sqrt(dx * dx + dy * dy); 36 | } 37 | } -------------------------------------------------------------------------------- /ControlR.Web.Server/Helpers/StreamSignaler.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Server.Helpers; 2 | 3 | public class StreamSignaler(Guid streamId) : IDisposable 4 | { 5 | private bool _disposedValue; 6 | 7 | public ManualResetEventAsync EndSignal { get; } = new(); 8 | public ManualResetEventAsync ReadySignal { get; } = new(); 9 | public string RequesterConnectionId { get; internal set; } = string.Empty; 10 | public string ResponderConnectionId { get; internal set; } = string.Empty; 11 | public IAsyncEnumerable? Stream { get; internal set; } 12 | public Guid StreamId { get; init; } = streamId; 13 | public void Dispose() 14 | { 15 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 16 | Dispose(disposing: true); 17 | GC.SuppressFinalize(this); 18 | } 19 | 20 | protected virtual void Dispose(bool disposing) 21 | { 22 | if (!_disposedValue) 23 | { 24 | if (disposing) 25 | { 26 | EndSignal.Dispose(); 27 | ReadySignal.Dispose(); 28 | } 29 | _disposedValue = true; 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /ControlR.Web.Server/Hubs/HubGroupNames.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Server.Hubs; 2 | 3 | public static class HubGroupNames 4 | { 5 | public const string ServerAdministrators = "server-administrators"; 6 | 7 | public static string GetDeviceGroupName(Guid deviceId, Guid tenantId) 8 | { 9 | return $"tenant-{tenantId}-device-{deviceId}"; 10 | } 11 | 12 | public static string GetTagGroupName(Guid tagId, Guid tenantId) 13 | { 14 | return $"tenant-{tenantId}-tag-{tagId}"; 15 | } 16 | 17 | public static string GetTenantDevicesGroupName(Guid tenantId) 18 | { 19 | return $"tenant-{tenantId}-devices"; 20 | } 21 | public static string GetUserRoleGroupName(string roleName, Guid tenantId) 22 | { 23 | return $"tenant-{tenantId}-user-role-{roleName}"; 24 | } 25 | } -------------------------------------------------------------------------------- /ControlR.Web.Server/Hubs/HubWithItems.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.SignalR; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace ControlR.Web.Server.Hubs; 5 | 6 | public abstract class HubWithItems : Hub 7 | where T : class 8 | { 9 | protected TItem GetItem(TItem defaultValue, [CallerMemberName] string key = "") 10 | { 11 | if (Context.Items.TryGetValue(key, out var cached) && 12 | cached is TItem item) 13 | { 14 | return item; 15 | } 16 | return defaultValue; 17 | } 18 | 19 | protected void SetItem(TItem item, [CallerMemberName] string key = "") 20 | { 21 | Context.Items[key] = item; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Interfaces/ICoordinate.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Server.Interfaces; 2 | 3 | public interface ICoordinate 4 | { 5 | double Longitude { get; set; } 6 | double Latitude { get; set; } 7 | } 8 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Models/AgentInstallerKey.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Server.Models; 2 | 3 | public record AgentInstallerKey( 4 | Guid TenantId, 5 | Guid CreatorId, 6 | string KeySecret, 7 | InstallerKeyType KeyType, 8 | uint? AllowedUses, 9 | DateTimeOffset? Expiration, 10 | uint CurrentUses = 0); -------------------------------------------------------------------------------- /ControlR.Web.Server/Models/Coordinate.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Web.Server.Interfaces; 2 | 3 | namespace ControlR.Web.Server.Models; 4 | 5 | public class Coordinate(double latitude, double longitude) : ICoordinate 6 | { 7 | public double Latitude { get; set; } = latitude; 8 | public double Longitude { get; set; } = longitude; 9 | } 10 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Models/ExternalWebSocketHost.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Web.Server.Interfaces; 2 | 3 | namespace ControlR.Web.Server.Models; 4 | 5 | public class ExternalWebSocketHost : ICoordinate 6 | { 7 | public string? Label { get; set; } 8 | public double Latitude { get; set; } 9 | public double Longitude { get; set; } 10 | public Uri? Origin { get; set; } 11 | } 12 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Properties/serviceDependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "mssql1": { 4 | "type": "mssql", 5 | "connectionId": "ConnectionStrings:DefaultConnection" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /ControlR.Web.Server/Properties/serviceDependencies.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "mssql1": { 4 | "type": "mssql.local", 5 | "connectionId": "ConnectionStrings:DefaultConnection" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /ControlR.Web.Server/RateLimiting/RateLimitPolicyNames.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Server.RateLimiting; 2 | 3 | public static class RateLimitPolicyNames 4 | { 5 | public const string TwentyPerMinute = nameof(TwentyPerMinute); 6 | } 7 | -------------------------------------------------------------------------------- /ControlR.Web.Server/ResultObjects/CreateUserResult.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace ControlR.Web.Server.ResultObjects; 4 | 5 | public class CreateUserResult(bool succeeded, IdentityResult identityResult, AppUser? user = null) 6 | { 7 | [MemberNotNullWhen(true, nameof(User))] 8 | public bool Succeeded { get; init; } = succeeded; 9 | 10 | public IdentityResult IdentityResult { get; init; } = identityResult; 11 | public AppUser? User { get; init; } = user; 12 | } -------------------------------------------------------------------------------- /ControlR.Web.Server/Services/IDeviceGridCacheService.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Dtos.ServerApi; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace ControlR.Web.Server.Services; 5 | 6 | /// 7 | /// Interface for device grid caching service. 8 | /// Note: This interface is being phased out in favor of ASP.NET Core's built-in output caching. 9 | /// 10 | public interface IDeviceGridCacheService 11 | { 12 | /// 13 | /// Gets cached device grid response based on request parameters and user ID 14 | /// 15 | ActionResult? GetFromCache(DeviceSearchRequestDto request, string userId); 16 | 17 | /// 18 | /// Caches device grid response for a specific user 19 | /// 20 | void SetCache(DeviceSearchRequestDto request, DeviceSearchResponseDto response, string userId); 21 | 22 | /// 23 | /// Invalidates cache for a specific user 24 | /// 25 | void InvalidateUserCache(string userId); 26 | 27 | /// 28 | /// Invalidates cache for all users 29 | /// 30 | void InvalidateAllCache(); 31 | } 32 | -------------------------------------------------------------------------------- /ControlR.Web.Server/Startup/HttpClientConfigurer.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.Server.Startup; 2 | 3 | public static class HttpClientConfigurer 4 | { 5 | public static void ConfigureHttpClient(IServiceProvider services, HttpClient client) 6 | { 7 | var options = services.GetRequiredService>(); 8 | client.BaseAddress = options.CurrentValue.ServerBaseUri ?? 9 | throw new InvalidOperationException("ServerBaseUri cannot be empty."); 10 | } 11 | } -------------------------------------------------------------------------------- /ControlR.Web.Server/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "DetailedErrors": true, 3 | "Logging": { 4 | "LogLevel": { 5 | "Default": "Information", 6 | "Microsoft": "Warning" 7 | } 8 | }, 9 | "POSTGRES_USER": "postgres", 10 | "POSTGRES_PASSWORD": "password", 11 | "POSTGRES_HOST": "localhost", 12 | 13 | "AzureMonitor": { 14 | "ConnectionString": "" 15 | }, 16 | 17 | "OTLP_ENDPOINT_URL": "https://localhost:21062", 18 | "AppOptions": { 19 | "ServerBaseUri": "https://localhost:7033", 20 | "AllowAgentsToSelfBootstrap": true, 21 | "EnablePublicRegistration": false, 22 | "UseExternalWebSocketRelay": false, 23 | "EnableCloudflareProxySupport": false, 24 | "ExternalWebSocketHosts": [], 25 | "MicrosoftClientId": "", 26 | "MicrosoftClientSecret": "", 27 | "UseInMemoryDatabase": false, 28 | "InMemoryDatabaseName": "" 29 | } 30 | } -------------------------------------------------------------------------------- /ControlR.Web.Server/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | 10 | "AzureMonitor": { 11 | "ConnectionString": "" 12 | }, 13 | "OTLP_ENDPOINT_URL": "", 14 | "AppOptions": { 15 | "ServerBaseUri": "http://localhost:8080", 16 | "AllowAgentsToSelfBootstrap": false, 17 | "EnablePublicRegistration": false, 18 | "EnableCloudflareProxySupport": false, 19 | "UseExternalWebSocketRelay": false, 20 | "SmtpDisplayName": "", 21 | "SmtpEmail": "", 22 | "SmtpHost": "", 23 | "SmtpLocalDomain": "", 24 | "SmtpCheckCertificateRevocation": true, 25 | "SmtpPassword": "", 26 | "SmtpPort": 587, 27 | "SmtpUserName": "", 28 | "KnownProxies": [], 29 | "KnownNetworks": [], 30 | "ExternalWebSocketHosts": [ 31 | 32 | ], 33 | "MicrosoftClientId": "", 34 | "MicrosoftClientSecret": "", 35 | "UseInMemoryDatabase": false, 36 | "InMemoryDatabaseName": "" 37 | } 38 | } -------------------------------------------------------------------------------- /ControlR.Web.Server/wwwroot/app.css: -------------------------------------------------------------------------------- 1 | .text-end { 2 | text-align: end; 3 | } 4 | 5 | .text-start { 6 | text-align: start; 7 | } 8 | 9 | .text-center { 10 | text-align: center; 11 | } 12 | 13 | .text-justify { 14 | text-align: justify; 15 | } 16 | 17 | .d-grid { 18 | display: grid; 19 | } 20 | 21 | .z9001 { 22 | z-index: 9001 !important; 23 | } 24 | 25 | .z9002 { 26 | z-index: 9002 !important; 27 | } 28 | 29 | .no-wrap { 30 | white-space: nowrap !important; 31 | } 32 | 33 | .ellipsis-text { 34 | white-space: nowrap; 35 | overflow: hidden; 36 | text-overflow: ellipsis; 37 | } 38 | 39 | .validation-error { 40 | color: red; 41 | } 42 | 43 | 44 | 45 | #mud-snackbar-container { 46 | /* Place above content windows. */ 47 | z-index: 9001 !important; 48 | } 49 | -------------------------------------------------------------------------------- /ControlR.Web.Server/wwwroot/downloads/AgentVersion.txt: -------------------------------------------------------------------------------- 1 | 0.11.153.0 2 | -------------------------------------------------------------------------------- /ControlR.Web.Server/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbound/ControlR/79e29a6c09b6fbbd08ff37bd7e6b9eca3a9d00b3/ControlR.Web.Server/wwwroot/favicon.ico -------------------------------------------------------------------------------- /ControlR.Web.Server/wwwroot/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbound/ControlR/79e29a6c09b6fbbd08ff37bd7e6b9eca3a9d00b3/ControlR.Web.Server/wwwroot/icon-192.png -------------------------------------------------------------------------------- /ControlR.Web.Server/wwwroot/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbound/ControlR/79e29a6c09b6fbbd08ff37bd7e6b9eca3a9d00b3/ControlR.Web.Server/wwwroot/icon-512.png -------------------------------------------------------------------------------- /ControlR.Web.Server/wwwroot/images/sign-in-microsoft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbound/ControlR/79e29a6c09b6fbbd08ff37bd7e6b9eca3a9d00b3/ControlR.Web.Server/wwwroot/images/sign-in-microsoft.png -------------------------------------------------------------------------------- /ControlR.Web.Server/wwwroot/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ControlR", 3 | "short_name": "ControlR", 4 | "id": "controlr", 5 | "start_url": "/", 6 | "display": "standalone", 7 | "background_color": "#141414", 8 | "theme_color": "#46AA46", 9 | "prefer_related_applications": false, 10 | "icons": [ 11 | { 12 | "src": "icon-512.png", 13 | "type": "image/png", 14 | "sizes": "512x512" 15 | }, 16 | { 17 | "src": "icon-192.png", 18 | "type": "image/png", 19 | "sizes": "192x192" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /ControlR.Web.ServiceDefaults/ServiceNames.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.ServiceDefaults; 2 | 3 | public static class ServiceNames 4 | { 5 | public const string Postgres = "postgres"; 6 | public const string Controlr = "controlr"; 7 | public const string ControlrAgent = "controlr-agent"; 8 | } -------------------------------------------------------------------------------- /ControlR.Web.WebSocketRelay/.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "dotnet-ef": { 6 | "version": "8.0.6", 7 | "commands": [ 8 | "dotnet-ef" 9 | ], 10 | "rollForward": false 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /ControlR.Web.WebSocketRelay/ControlR.Web.WebSocketRelay.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | true 8 | true 9 | Linux 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ControlR.Web.WebSocketRelay/ControlR.Web.WebSocketRelay.http: -------------------------------------------------------------------------------- 1 | @ControlR.Web.WebSocketRelay_HostAddress = http://localhost:5172 2 | 3 | GET {{ControlR.Web.WebSocketRelay_HostAddress}}/api/status/ 4 | Accept: application/json 5 | 6 | ### 7 | 8 | GET {{ControlR.Web.WebSocketRelay_HostAddress}}/api/status/ 9 | Accept: application/json 10 | 11 | ### 12 | -------------------------------------------------------------------------------- /ControlR.Web.WebSocketRelay/Dtos/StatusOkDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Web.WebSocketRelay.Dtos; 2 | 3 | public record StatusOkDto(string Message); 4 | -------------------------------------------------------------------------------- /ControlR.Web.WebSocketRelay/Program.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.WebSocketRelay.Common.Extensions; 2 | using ControlR.Web.WebSocketRelay.Serialization; 3 | 4 | var builder = WebApplication.CreateSlimBuilder(args); 5 | 6 | builder.Logging.AddSimpleConsole(options => 7 | { 8 | options.IncludeScopes = true; 9 | options.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff zzz "; 10 | }); 11 | 12 | builder.Services.ConfigureHttpJsonOptions(options => 13 | { 14 | options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); 15 | }); 16 | 17 | builder.Services.AddWebSocketRelay(); 18 | 19 | builder.Services.AddHealthChecks(); 20 | 21 | var app = builder.Build(); 22 | app.MapHealthChecks("/health"); 23 | 24 | app.MapWebSocketRelay("/relay"); 25 | 26 | app.Run(); 27 | -------------------------------------------------------------------------------- /ControlR.Web.WebSocketRelay/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "http": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "launchUrl": "health", 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | }, 10 | "dotnetRunMessages": true, 11 | "applicationUrl": "http://localhost:5172" 12 | }, 13 | "Container (Dockerfile)": { 14 | "commandName": "Docker", 15 | "launchBrowser": true, 16 | "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/health", 17 | "environmentVariables": { 18 | "ASPNETCORE_HTTP_PORTS": "8080" 19 | }, 20 | "publishAllPorts": true 21 | } 22 | }, 23 | "$schema": "http://json.schemastore.org/launchsettings.json" 24 | } -------------------------------------------------------------------------------- /ControlR.Web.WebSocketRelay/Serialization/AppJsonSerializerContext.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Web.WebSocketRelay.Dtos; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace ControlR.Web.WebSocketRelay.Serialization; 5 | 6 | [JsonSerializable(typeof(StatusOkDto))] 7 | public partial class AppJsonSerializerContext : JsonSerializerContext 8 | { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /ControlR.Web.WebSocketRelay/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ControlR.Web.WebSocketRelay/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | 6 | true 7 | Jared Goodwin 8 | ©️ $([System.DateTime]::Now.Year) Jared Goodwin 9 | https://github.com/bitbound/controlr 10 | https://github.com/bitbound/controlr 11 | 12 | 13 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Clients/ControlR.Libraries.Clients.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Clients/Messages/DtoReceivedMessage.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Clients.Messages; 2 | public record DtoReceivedMessage(T Dto); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Clients/Messages/EventMessageKind.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Clients.Messages; 2 | 3 | public enum EventMessageKind 4 | { 5 | PendingOperationsChanged, 6 | DeviceContentWindowsChanged, 7 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Clients/Messages/ValueMessage.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Clients.Messages; 2 | 3 | public class ValueMessage(T value) 4 | where T : notnull 5 | { 6 | public T Value { get; } = value; 7 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Clients/Usings.cs: -------------------------------------------------------------------------------- 1 | global using ControlR.Libraries.Shared.Extensions; 2 | global using ControlR.Libraries.Shared.Dtos; 3 | global using ControlR.Libraries.Shared.Primitives; 4 | global using Bitbound.SimpleMessenger; 5 | global using ControlR.Libraries.Shared.Services.Buffers; 6 | global using MessagePack; 7 | global using Microsoft.Extensions.Logging; 8 | global using ControlR.Libraries.Clients.Messages; -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.DevicesCommon/NativeMethods.txt: -------------------------------------------------------------------------------- 1 | SendSAS 2 | WTSQuerySessionInformation 3 | WTSGetActiveConsoleSessionId 4 | WTSEnumerateSessions 5 | WTSFreeMemory 6 | WTSOpenServer 7 | WTS_CURRENT_SERVER_HANDLE 8 | PROCESS_INFORMATION 9 | OpenProcess 10 | OpenProcessToken 11 | CreateProcessAsUser 12 | DuplicateTokenEx 13 | GetThreadDesktop 14 | OpenInputDesktop 15 | GetUserObjectInformation 16 | GetCurrentThreadId 17 | CloseDesktop 18 | SetThreadDesktop 19 | SwitchDesktop 20 | SendInput 21 | VkKeyScanEx 22 | MapVirtualKeyEx 23 | GetKeyboardLayout 24 | GetAsyncKeyState 25 | GetKeyState 26 | GetMessageExtraInfo 27 | GetSystemMetrics 28 | WTSQueryUserToken 29 | GetTokenInformation -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.DevicesCommon/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("ControlR.Agent")] 4 | [assembly: InternalsVisibleTo("ControlR.Streamer")] -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.DevicesCommon/Services/FileLoggerProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace ControlR.Libraries.DevicesCommon.Services; 4 | 5 | public class FileLoggerProvider( 6 | string componentVersion, 7 | Func logPathFactory, 8 | TimeSpan logRetention) : ILoggerProvider 9 | { 10 | public ILogger CreateLogger(string categoryName) 11 | { 12 | return new FileLogger(componentVersion, categoryName, logPathFactory, logRetention); 13 | } 14 | 15 | public void Dispose() 16 | { 17 | GC.SuppressFinalize(this); 18 | } 19 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.DevicesCommon/Services/HostLifetimeEventResponder.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Microsoft.Extensions.Hosting; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace ControlR.Libraries.DevicesCommon.Services; 6 | 7 | public class HostLifetimeEventResponder( 8 | IHostApplicationLifetime appLifetime, 9 | ILogger logger) : IHostedService 10 | { 11 | public Task StartAsync(CancellationToken cancellationToken) 12 | { 13 | appLifetime.ApplicationStarted.Register(() => 14 | { 15 | var exeVersion = Assembly.GetExecutingAssembly().GetName().Version; 16 | logger.LogInformation("Host initialized. Assembly version: {AsmVersion}.", exeVersion); 17 | }); 18 | return Task.CompletedTask; 19 | } 20 | 21 | public Task StopAsync(CancellationToken cancellationToken) 22 | { 23 | return Task.CompletedTask; 24 | } 25 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.DevicesCommon/Usings.cs: -------------------------------------------------------------------------------- 1 | global using ControlR.Libraries.Shared.Extensions; 2 | global using ControlR.Libraries.Shared.Primitives; 3 | global using Result = ControlR.Libraries.Shared.Primitives.Result; 4 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.DevicesNative/ControlR.Libraries.DevicesNative.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | Debug;Release 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.DevicesNative/Linux/LIbc.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable IDE0079 // Remove unnecessary suppression 2 | #pragma warning disable SYSLIB1054 // Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time 3 | #pragma warning disable CA1401 // P/Invokes should not be visible 4 | using System.Runtime.InteropServices; 5 | 6 | namespace ControlR.Libraries.DevicesNative.Linux; 7 | 8 | public static class Libc 9 | { 10 | [DllImport("libc", EntryPoint = "geteuid", SetLastError = true)] 11 | public static extern uint Geteuid(); 12 | 13 | 14 | [DllImport("libc", EntryPoint = "setsid", SetLastError = true)] 15 | public static extern int Setsid(); 16 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.DevicesNative/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("ControlR.Agent")] 4 | [assembly: InternalsVisibleTo("ControlR.Streamer")] -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Constants/HttpConstants.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Constants; 2 | 3 | public static class HttpConstants 4 | { 5 | public const string AgentVersionEndpoint = "/api/version/agent"; 6 | public const string DevicesEndpoint = "/api/devices"; 7 | public const string DeviceTagsEndpoint = "/api/device-tags"; 8 | public const string InstallerKeysEndpoint = "/api/installer-keys"; 9 | public const string InvitesEndpoint = "/api/invites"; 10 | public const string LogoutEndpoint = "/api/logout"; 11 | public const string RolesEndpoint = "/api/roles"; 12 | public const string ServerSettingsEndpoint = "/api/server-settings"; 13 | public const string ServerVersionEndpoint = "/api/version/server"; 14 | public const string TagsEndpoint = "/api/tags"; 15 | public const string UserPreferencesEndpoint = "/api/user-preferences"; 16 | public const string UserRolesEndpoint = "/api/user-roles"; 17 | public const string UsersEndpoint = "/api/users"; 18 | public const string UserTagsEndpoint = "/api/user-tags"; 19 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Constants/UserPreferenceNames.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Constants; 2 | 3 | public static class UserPreferenceNames 4 | { 5 | public const string NotifyUserOnSessionStartName = "notify-user-on-session-start"; 6 | public const string HideOfflineDevicesName = "hide-offline-devices"; 7 | public const string UserDisplayName = "user-display-name"; 8 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Constants/Validators.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace ControlR.Libraries.Shared.Constants; 4 | 5 | public static partial class Validators 6 | { 7 | [GeneratedRegex("[^a-z0-9-]")] 8 | public static partial Regex TagNameValidator(); 9 | 10 | [GeneratedRegex("[^A-Za-z0-9_-]")] 11 | public static partial Regex UsernameValidator(); 12 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/ControlR.Libraries.Shared.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | Debug;Release 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Converters/TimeSpanJsonConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace ControlR.Libraries.Shared.Converters; 5 | 6 | public class TimeSpanJsonConverter : JsonConverter 7 | { 8 | public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 9 | { 10 | var stringValue = reader.GetString(); 11 | if (TimeSpan.TryParse(stringValue, out var result)) 12 | { 13 | return result; 14 | } 15 | 16 | throw new ArgumentException("Failed to convert to TimeSpan."); 17 | } 18 | 19 | public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options) 20 | { 21 | writer.WriteStringValue(value.ToString()); 22 | } 23 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/DtoType.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos; 2 | 3 | public enum DtoType 4 | { 5 | None = 0, 6 | StreamingSessionRequest = 2, 7 | GetWindowsSessions = 3, 8 | DeviceUpdateRequest = 4, 9 | TerminalSessionRequest = 5, 10 | CloseTerminalRequest = 7, 11 | PowerStateChange = 8, 12 | TerminalInput = 9, 13 | WakeDevice = 13, 14 | ChangeDisplays = 17, 15 | CloseStreamingSession = 19, 16 | InvokeCtrlAltDel = 20, 17 | ClipboardText = 21, 18 | TriggerAgentUpdate = 22, 19 | DisplayData = 23, 20 | MovePointer = 26, 21 | MouseButtonEvent = 27, 22 | KeyEvent = 28, 23 | TypeText = 29, 24 | ResetKeyboardState = 30, 25 | WheelScroll = 31, 26 | ScreenRegion = 32, 27 | MouseClick = 33, 28 | CursorChanged = 34, 29 | RequestClipboardText = 35, 30 | WindowsSessionEnding = 36, 31 | WindowsSessionSwitched = 37, 32 | RefreshDeviceInfoRequest = 38, 33 | Ack = 39, 34 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/DtoWrapper.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using ControlR.Libraries.Shared.Dtos.HubDtos; 3 | 4 | namespace ControlR.Libraries.Shared.Dtos; 5 | 6 | 7 | [MessagePackObject] 8 | public class DtoWrapper 9 | { 10 | [Key(nameof(DtoType))] 11 | [JsonConverter(typeof(JsonStringEnumConverter))] 12 | public required DtoType DtoType { get; init; } 13 | 14 | [Key(nameof(Payload))] 15 | public required byte[] Payload { get; init; } 16 | 17 | public static DtoWrapper Create(T dto, DtoType dtoType) 18 | { 19 | return new DtoWrapper() 20 | { 21 | DtoType = dtoType, 22 | Payload = MessagePackSerializer.Serialize(dto) 23 | }; 24 | } 25 | 26 | public T GetPayload() 27 | { 28 | return MessagePackSerializer.Deserialize(Payload); 29 | } 30 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/HubDtos/CloseStreamingSessionRequestDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.HubDtos; 2 | 3 | [MessagePackObject] 4 | public record CloseStreamingSessionRequestDto(); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/HubDtos/CloseTerminalRequestDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.HubDtos; 2 | 3 | [MessagePackObject] 4 | public record CloseTerminalRequestDto([property: Key(0)] Guid TerminalId); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/HubDtos/DisplayDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.HubDtos; 2 | 3 | [MessagePackObject(keyAsPropertyName: true)] 4 | public class DisplayDto 5 | { 6 | 7 | public required string DisplayId { get; init; } 8 | 9 | public double Height { get; init; } 10 | 11 | public bool IsPrimary { get; init; } 12 | 13 | public double Left { get; init; } 14 | 15 | public required string Name { get; init; } 16 | 17 | public double ScaleFactor { get; init; } 18 | 19 | public double Top { get; init; } 20 | 21 | public double Width { get; init; } 22 | } 23 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/HubDtos/GetWindowsSessionsDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.HubDtos; 2 | 3 | [MessagePackObject] 4 | public record GetWindowsSessionsDto(); 5 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/HubDtos/InvokeCtrlAltDelRequestDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.HubDtos; 2 | 3 | [MessagePackObject] 4 | public record InvokeCtrlAltDelRequestDto(); 5 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/HubDtos/PowerStateChangeDto.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Enums; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace ControlR.Libraries.Shared.Dtos.HubDtos; 5 | 6 | [MessagePackObject] 7 | [method: SerializationConstructor] 8 | [method: JsonConstructor] 9 | public class PowerStateChangeDto(PowerStateChangeType type) 10 | { 11 | [Key(nameof(Type))] 12 | public PowerStateChangeType Type { get; set; } = type; 13 | } 14 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/HubDtos/RefreshDeviceInfoRequestDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.HubDtos; 2 | 3 | [MessagePackObject] 4 | public record RefreshDeviceInfoRequestDto(); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/HubDtos/ResetKeyboardStateDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.HubDtos; 2 | 3 | [MessagePackObject] 4 | public record ResetKeyboardStateDto(); 5 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/HubDtos/ServerStatusDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.HubDtos; 2 | 3 | [MessagePackObject] 4 | public record ServerStatsDto( 5 | [property: Key(0)] int TotalTenants, 6 | [property: Key(1)] int OnlineAgents, 7 | [property: Key(2)] int TotalAgents, 8 | [property: Key(3)] int OnlineUsers, 9 | [property: Key(4)] int TotalUsers); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/HubDtos/TerminalInputDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.HubDtos; 2 | [MessagePackObject] 3 | public record TerminalInputDto( 4 | [property: Key(0)] Guid TerminalId, 5 | [property: Key(1)] string Input); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/HubDtos/TerminalOutputDto.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Enums; 2 | 3 | namespace ControlR.Libraries.Shared.Dtos.HubDtos; 4 | 5 | [MessagePackObject] 6 | public record TerminalOutputDto( 7 | [property: Key(0)] Guid TerminalId, 8 | [property: Key(1)] string Output, 9 | [property: Key(2)] TerminalOutputKind OutputKind, 10 | [property: Key(3)] DateTimeOffset Timestamp); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/HubDtos/TerminalSessionRequest.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.HubDtos; 2 | 3 | [MessagePackObject] 4 | public record TerminalSessionRequest( 5 | [property: Key(0)] Guid TerminalId, 6 | [property: Key(1)] string ViewerConnectionId); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/HubDtos/TerminalSessionRequestResult.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Enums; 2 | 3 | namespace ControlR.Libraries.Shared.Dtos.HubDtos; 4 | 5 | [MessagePackObject] 6 | public record TerminalSessionRequestResult([property: Key(0)] TerminalSessionKind SessionKind); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/HubDtos/TriggerAgentUpdateDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.HubDtos; 2 | 3 | [MessagePackObject] 4 | public record TriggerAgentUpdateDto(); 5 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/HubDtos/WakeDeviceDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.HubDtos; 2 | 3 | [MessagePackObject] 4 | public record WakeDeviceDto( 5 | [property: Key(0)] string[] MacAddresses); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/Interfaces/IHasPrimaryKey.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.Interfaces; 2 | 3 | public interface IHasPrimaryKey 4 | { 5 | Guid Id { get; } 6 | } 7 | 8 | public interface IHasSettablePrimaryKey : IHasPrimaryKey 9 | { 10 | new Guid Id { get; set; } 11 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/ServerApi/AcceptInvitationRequestDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.ServerApi; 2 | public record AcceptInvitationRequestDto( 3 | string ActivationCode, 4 | string Email, 5 | string Password); 6 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/ServerApi/AcceptInvitationResponseDto.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics.CodeAnalysis; 2 | 3 | namespace ControlR.Libraries.Shared.Dtos.ServerApi; 4 | public record AcceptInvitationResponseDto( 5 | [property: MemberNotNullWhen(false, nameof(AcceptInvitationResponseDto.ErrorMessage))] 6 | bool IsSuccessful, 7 | string? ErrorMessage = null); 8 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/ServerApi/CreateDeviceRequestDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.ServerApi; 2 | 3 | [MessagePackObject] 4 | public record CreateDeviceRequestDto( 5 | [property: Key(0)] DeviceDto Device, 6 | [property: Key(1)] string InstallerKey); 7 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/ServerApi/CreateInstallerKeyRequestDto.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace ControlR.Libraries.Shared.Dtos.ServerApi; 4 | 5 | [DataContract] 6 | public record CreateInstallerKeyRequestDto( 7 | [property: DataMember] InstallerKeyType KeyType, 8 | [property: DataMember] DateTimeOffset? Expiration = null, 9 | [property: DataMember] uint? AllowedUses = null); 10 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/ServerApi/CreateInstallerKeyResponseDto.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace ControlR.Libraries.Shared.Dtos.ServerApi; 4 | 5 | [DataContract] 6 | public record CreateInstallerKeyResponseDto( 7 | [property: DataMember] InstallerKeyType TokenType, 8 | [property: DataMember] string AccessKey, 9 | [property: DataMember] uint? AllowedUses = null, 10 | [property: DataMember] DateTimeOffset? Expiration = null); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/ServerApi/DeviceSearchRequestDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.ServerApi; 2 | 3 | public class DeviceSearchRequestDto 4 | { 5 | public string? SearchText { get; set; } 6 | public bool HideOfflineDevices { get; set; } 7 | public List? TagIds { get; set; } 8 | public int Page { get; set; } 9 | public int PageSize { get; set; } 10 | public List? SortDefinitions { get; set; } 11 | } 12 | 13 | public class DeviceColumnSort 14 | { 15 | public string? PropertyName { get; set; } 16 | public bool Descending { get; set; } 17 | public int SortOrder { get; set; } 18 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/ServerApi/DeviceSearchResponseDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.ServerApi; 2 | 3 | public class DeviceSearchResponseDto 4 | { 5 | public List? Items { get; set; } 6 | public int TotalItems { get; set; } 7 | public bool AnyDevicesForUser { get; set; } 8 | } 9 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/ServerApi/DeviceTagAddRequestDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.ServerApi; 2 | 3 | public record DeviceTagAddRequestDto( 4 | Guid DeviceId, 5 | Guid TagId); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/ServerApi/EntityBaseRecordDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.ServerApi; 2 | 3 | [MessagePackObject] 4 | public record EntityBaseRecordDto( 5 | [property: Key(0)] Guid Id); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/ServerApi/InstallerKeyType.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace ControlR.Libraries.Shared.Dtos.ServerApi; 4 | 5 | [DataContract] 6 | public enum InstallerKeyType 7 | { 8 | [EnumMember] 9 | Unknown = 0, 10 | [EnumMember] 11 | UsageBased = 1, 12 | [EnumMember] 13 | TimeBased = 2 14 | } 15 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/ServerApi/RoleResponseDto.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | 3 | namespace ControlR.Libraries.Shared.Dtos.ServerApi; 4 | public record RoleResponseDto(Guid Id, string Name, IReadOnlyList UserIds) : IHasPrimaryKey; 5 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/ServerApi/ServerSettingsDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.ServerApi; 2 | public record ServerSettingsDto(bool IsPublicRegistrationEnabled); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/ServerApi/TagCreateRequestDto.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Enums; 2 | 3 | namespace ControlR.Libraries.Shared.Dtos.ServerApi; 4 | 5 | public record TagCreateRequestDto(string Name, TagType Type); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/ServerApi/TagRenameRequestDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.ServerApi; 2 | public record TagRenameRequestDto(Guid TagId, string NewTagName); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/ServerApi/TagResponseDto.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Enums; 2 | 3 | namespace ControlR.Libraries.Shared.Dtos.ServerApi; 4 | 5 | public record TagResponseDto( 6 | Guid Id, 7 | string Name, 8 | TagType Type, 9 | IReadOnlyList UserIds, 10 | IReadOnlyList DeviceIds) : IHasPrimaryKey; -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/ServerApi/TenantInviteRequestDto.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace ControlR.Libraries.Shared.Dtos.ServerApi; 4 | 5 | public record TenantInviteRequestDto( 6 | [EmailAddress] 7 | string InviteeEmail); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/ServerApi/TenantInviteResponseDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.ServerApi; 2 | public record TenantInviteResponseDto( 3 | Guid Id, 4 | DateTimeOffset CreatedAt, 5 | string InviteeEmail, 6 | Uri InviteUrl 7 | ) : IHasPrimaryKey; 8 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/ServerApi/UserPreferenceRequestDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.ServerApi; 2 | public record UserPreferenceRequestDto(string Name, string Value); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/ServerApi/UserPreferenceResponseDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.ServerApi; 2 | public record UserPreferenceResponseDto(Guid Id, string Name, string Value) : EntityBaseRecordDto(Id); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/ServerApi/UserResponseDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.ServerApi; 2 | 3 | public record UserResponseDto( 4 | Guid Id, 5 | string? UserName, 6 | string? Email) : IHasPrimaryKey; -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/ServerApi/UserRoleAddRequest.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.ServerApi; 2 | public record UserRoleAddRequestDto( 3 | Guid UserId, 4 | Guid RoleId); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/ServerApi/UserTagAddRequestDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.ServerApi; 2 | 3 | public record UserTagAddRequestDto( 4 | Guid UserId, 5 | Guid TagId); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/StreamerDtos/AckDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.StreamerDtos; 2 | 3 | [MessagePackObject(keyAsPropertyName: true)] 4 | public record AckDto(int ReceivedSize); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/StreamerDtos/ChangeDisplaysDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.StreamerDtos; 2 | 3 | [MessagePackObject(keyAsPropertyName: true)] 4 | public record ChangeDisplaysDto(string DisplayId); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/StreamerDtos/ClipboardTextDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.StreamerDtos; 2 | 3 | [MessagePackObject(keyAsPropertyName: true)] 4 | public record ClipboardTextDto( 5 | string? Text, 6 | Guid SessionId); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/StreamerDtos/CursorChangedDto.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Enums; 2 | 3 | namespace ControlR.Libraries.Shared.Dtos.StreamerDtos; 4 | 5 | [MessagePackObject(keyAsPropertyName: true)] 6 | public record CursorChangedDto( 7 | WindowsCursor Cursor, 8 | Guid SessionId); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/StreamerDtos/DesktopChangedDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.StreamerDtos; 2 | 3 | [MessagePackObject(keyAsPropertyName: true)] 4 | public record DesktopChangedDto(string DesktopName); 5 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/StreamerDtos/DisplayDataDto.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Dtos.HubDtos; 2 | 3 | namespace ControlR.Libraries.Shared.Dtos.StreamerDtos; 4 | 5 | [MessagePackObject(keyAsPropertyName: true)] 6 | public record DisplayDataDto(DisplayDto[] Displays); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/StreamerDtos/KeyEventDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.StreamerDtos; 2 | 3 | [MessagePackObject(keyAsPropertyName: true)] 4 | public record KeyEventDto( 5 | string Key, 6 | bool IsPressed); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/StreamerDtos/MouseButtonEventDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.StreamerDtos; 2 | 3 | [MessagePackObject(keyAsPropertyName: true)] 4 | public record MouseButtonEventDto( 5 | int Button, 6 | bool IsPressed, 7 | double PercentX, 8 | double PercentY); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/StreamerDtos/MouseClickDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.StreamerDtos; 2 | 3 | [MessagePackObject(keyAsPropertyName: true)] 4 | public record MouseClickDto( 5 | int Button, 6 | bool IsDoubleClick, 7 | double PercentX, 8 | double PercentY); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/StreamerDtos/MovePointerDto.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace ControlR.Libraries.Shared.Dtos.StreamerDtos; 4 | 5 | [MessagePackObject(keyAsPropertyName: true)] 6 | public record MovePointerDto( 7 | double PercentX, 8 | double PercentY); 9 | 10 | [JsonConverter(typeof(JsonStringEnumConverter))] 11 | public enum MovePointerType 12 | { 13 | Absolute, 14 | Relative 15 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/StreamerDtos/RequestClipboardTextDto.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Dtos.HubDtos; 2 | 3 | namespace ControlR.Libraries.Shared.Dtos.StreamerDtos; 4 | 5 | [MessagePackObject(keyAsPropertyName: true)] 6 | public record RequestClipboardTextDto(); 7 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/StreamerDtos/ScreenRegionDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.StreamerDtos; 2 | 3 | [MessagePackObject(keyAsPropertyName: true)] 4 | public record ScreenRegionDto( 5 | int X, 6 | int Y, 7 | int Width, 8 | int Height, 9 | byte[] EncodedImage); 10 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/StreamerDtos/StreamerDownloadProgressDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.StreamerDtos; 2 | 3 | [MessagePackObject(keyAsPropertyName: true)] 4 | public record StreamerDownloadProgressDto( 5 | Guid StreamingSessionId, 6 | string ViewerConnectionId, 7 | double Progress, 8 | string Message); 9 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/StreamerDtos/StreamerSessionRequestDto.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace ControlR.Libraries.Shared.Dtos.StreamerDtos; 4 | 5 | [MessagePackObject(keyAsPropertyName: true)] 6 | [method: JsonConstructor] 7 | [method: SerializationConstructor] 8 | public record StreamerSessionRequestDto( 9 | Guid SessionId, 10 | Uri WebsocketUri, 11 | int TargetSystemSession, 12 | string ViewerConnectionId, 13 | Guid DeviceId, 14 | bool NotifyUserOnSessionStart, 15 | string ViewerName = ""); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/StreamerDtos/TypeTextDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.StreamerDtos; 2 | 3 | [MessagePackObject(keyAsPropertyName: true)] 4 | public record TypeTextDto(string Text); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/StreamerDtos/WheelScrollDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.StreamerDtos; 2 | 3 | [MessagePackObject(keyAsPropertyName: true)] 4 | public record WheelScrollDto( 5 | double PercentX, 6 | double PercentY, 7 | double ScrollY, 8 | double ScrollX); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/StreamerDtos/WindowsSessionEndingDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.StreamerDtos; 2 | 3 | [MessagePackObject(keyAsPropertyName: true)] 4 | public record WindowsSessionEndingDto(); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Dtos/StreamerDtos/WindowsSessionSwitchedDto.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Dtos.StreamerDtos; 2 | 3 | [MessagePackObject(keyAsPropertyName: true)] 4 | public record WindowsSessionSwitchedDto(); -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Enums/ConnectionType.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace ControlR.Libraries.Shared.Enums; 4 | 5 | public enum ConnectionType 6 | { 7 | [EnumMember] 8 | Unknown, 9 | [EnumMember] 10 | Viewer, 11 | [EnumMember] 12 | Agent, 13 | [EnumMember] 14 | Desktop 15 | } 16 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Enums/MessageSeverity.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace ControlR.Libraries.Shared.Enums; 4 | 5 | public enum MessageSeverity 6 | { 7 | [EnumMember] 8 | Information, 9 | [EnumMember] 10 | Warning, 11 | [EnumMember] 12 | Error, 13 | [EnumMember] 14 | Success, 15 | } 16 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Enums/PowerStateChangeType.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace ControlR.Libraries.Shared.Enums; 4 | 5 | public enum PowerStateChangeType 6 | { 7 | [EnumMember] 8 | None, 9 | 10 | [EnumMember] 11 | Restart, 12 | 13 | [EnumMember] 14 | Shutdown 15 | } 16 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Enums/RuntimeId.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Enums; 2 | 3 | public enum RuntimeId 4 | { 5 | WinX86, 6 | WinX64, 7 | OsxArm64, 8 | OsxX64, 9 | LinuxX64 10 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Enums/SystemPlatform.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Enums; 2 | 3 | public enum SystemPlatform 4 | { 5 | Unknown, 6 | Windows, 7 | Linux, 8 | MacOs, 9 | MacCatalyst, 10 | Android, 11 | Ios, 12 | Browser 13 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Enums/TagType.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Enums; 2 | 3 | public enum TagType 4 | { 5 | Unknown = 0, 6 | Permission = 1 7 | } 8 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Enums/TerminalOutputKind.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Enums; 2 | 3 | public enum TerminalOutputKind 4 | { 5 | StandardOutput, 6 | StandardError, 7 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Enums/TerminalSessionKind.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Enums; 2 | 3 | public enum TerminalSessionKind 4 | { 5 | Unknown, 6 | WindowsPowerShell, 7 | PowerShell, 8 | Bash, 9 | Sh, 10 | Zsh 11 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Enums/WindowsCursor.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Enums; 2 | 3 | public enum WindowsCursor 4 | { 5 | Unknown, 6 | NormalArrow, 7 | Ibeam, 8 | SizeNs, 9 | SizeWe, 10 | SizeNwse, 11 | SizeNesw, 12 | Wait, 13 | Hand 14 | } 15 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Exceptions/ClientConnectionNotFoundException.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Exceptions; 2 | 3 | public class ClientConnectionNotFoundException(string connectionId) : Exception 4 | { 5 | private const string ErrorMessage = "The client connection does not exist."; 6 | public string ConnectionId => connectionId; 7 | public override string Message => ErrorMessage; 8 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Exceptions/DtoTypeMismatchException.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Dtos.HubDtos; 2 | 3 | namespace ControlR.Libraries.Shared.Exceptions; 4 | 5 | public class DtoTypeMismatchException(DtoType expectedType, DtoType actualType) 6 | : Exception($"DtoType mismatch. Expected: {expectedType}. Actual: {actualType}.") 7 | { 8 | public DtoType ExpectedType { get; } = expectedType; 9 | public DtoType ActualType { get; } = actualType; 10 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Exceptions/ProcessStatusException.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Exceptions; 2 | 3 | /// 4 | /// Thrown when a process exit with a non-zero status code. 5 | /// 6 | public class ProcessStatusException(int statusCode) : Exception 7 | { 8 | public override string Message => 9 | $"Process exited with status code {statusCode}"; 10 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Extensions/CancellationTokenExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Extensions; 2 | 3 | public static class CancellationTokenExtensions 4 | { 5 | public static async Task WhenCancelled(this CancellationToken cancellationToken, CancellationToken extraCancellationToken = default) 6 | { 7 | if (cancellationToken.IsCancellationRequested || extraCancellationToken.IsCancellationRequested) 8 | { 9 | return; 10 | } 11 | 12 | using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, extraCancellationToken); 13 | var tcs = new TaskCompletionSource(); 14 | linkedCts.Token.Register(() => tcs.TrySetResult()); 15 | await tcs.Task; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Extensions/DateTimeExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Extensions; 2 | public static class DateTimeExtensions 3 | { 4 | public static DateTimeOffset ToDateTimeOffset(this DateTime dateTime) => new(dateTime); 5 | } 6 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Extensions/GenericExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace ControlR.Libraries.Shared.Extensions; 4 | 5 | public static class GenericExtensions 6 | { 7 | public static T Apply(this T self, Action action) 8 | { 9 | action(self); 10 | return self; 11 | } 12 | 13 | public static Task AsTaskResult(this T result) 14 | { 15 | return Task.FromResult(result); 16 | } 17 | 18 | public static TTo CloneAs(this TFrom value) 19 | { 20 | return JsonSerializer.Deserialize(JsonSerializer.Serialize(value)) ?? 21 | throw new JsonException("Failed to clone object via JsonSerializer."); 22 | } 23 | 24 | public static Result TryCloneAs(this TFrom value) 25 | { 26 | try 27 | { 28 | var converted = JsonSerializer.Deserialize(JsonSerializer.Serialize(value)); 29 | if (converted is not null) 30 | { 31 | return Result.Ok(converted); 32 | } 33 | 34 | return Result.Fail("Serialization failure."); 35 | } 36 | catch (Exception ex) 37 | { 38 | return Result.Fail(ex); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Extensions/IServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Services; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace ControlR.Libraries.Shared.Extensions; 5 | public static class ServiceCollectionExtensions 6 | { 7 | public static IServiceCollection AddLazyDi(this IServiceCollection services) 8 | { 9 | return services.AddTransient(typeof(ILazyDi<>), typeof(LazyDi<>)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Extensions/ListExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Extensions; 2 | 3 | public static class ListExtensions 4 | { 5 | public static bool TryReplace(this IList self, T newItem, Predicate predicate) 6 | { 7 | if (newItem is null) 8 | { 9 | return false; 10 | } 11 | 12 | var foundItem = self.FirstOrDefault(x => predicate(x)); 13 | 14 | if (foundItem is null) 15 | { 16 | return false; 17 | } 18 | 19 | var index = self.IndexOf(foundItem); 20 | 21 | if (index == -1) 22 | { 23 | return false; 24 | } 25 | 26 | self[index] = newItem; 27 | return true; 28 | } 29 | 30 | public static void RemoveDuplicates(this List self) 31 | { 32 | var distinct = self.Distinct().ToList(); 33 | self.Clear(); 34 | self.AddRange(distinct); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Extensions/ProcessExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace ControlR.Libraries.Shared.Extensions; 4 | 5 | public static class ProcessExtensions 6 | { 7 | public static void KillAndDispose(this Process process) 8 | { 9 | try 10 | { 11 | process.Kill(); 12 | } 13 | catch { } 14 | try 15 | { 16 | process.Dispose(); 17 | } 18 | catch { } 19 | } 20 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace ControlR.Libraries.Shared.Extensions; 4 | 5 | public static class StringExtensions 6 | { 7 | public static string SanitizeForFileSystem(this string self) 8 | { 9 | var invalidCharacters = Path.GetInvalidFileNameChars().ToHashSet(); 10 | return string.Concat(self.Aggregate( 11 | new StringBuilder(), 12 | (sb, c) => invalidCharacters.Contains(c) 13 | ? sb.Append('_') 14 | : sb.Append(c))); 15 | } 16 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Helpers/DeterministicGuid.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | 3 | namespace ControlR.Libraries.Shared.Helpers; 4 | public static class DeterministicGuid 5 | { 6 | public static Guid Create(int seed) 7 | { 8 | var seedBytes = BitConverter.GetBytes(seed); 9 | var hash = MD5.HashData(seedBytes); 10 | return new Guid(hash); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Helpers/DisposeHelper.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Helpers; 2 | public static class DisposeHelper 3 | { 4 | public static void DisposeAll(params IDisposable?[] disposables) 5 | { 6 | foreach (var disposable in disposables) 7 | { 8 | try 9 | { 10 | disposable?.Dispose(); 11 | } 12 | catch { } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Helpers/JsonValueFilter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace ControlR.Libraries.Shared.Helpers; 4 | public static class JsonValueFilter 5 | { 6 | public static Func GetQuickFilter( 7 | string searchText, 8 | ILogger logger) 9 | { 10 | 11 | return x => 12 | { 13 | if (string.IsNullOrWhiteSpace(searchText)) 14 | { 15 | return true; 16 | } 17 | 18 | var element = JsonSerializer.SerializeToElement(x); 19 | foreach (var property in element.EnumerateObject()) 20 | { 21 | try 22 | { 23 | if (property.Value.ToString().Contains(searchText, StringComparison.OrdinalIgnoreCase)) 24 | { 25 | return true; 26 | } 27 | } 28 | catch (Exception ex) 29 | { 30 | logger.LogError(ex, "Error while filtering items of type {Type}.", typeof(T).Name); 31 | } 32 | } 33 | 34 | return false; 35 | }; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Helpers/MathHelper.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Helpers; 2 | public static class MathHelper 3 | { 4 | public static double GetDistanceBetween(double point1X, double point1Y, double point2X, double point2Y) 5 | { 6 | return Math.Sqrt(Math.Pow(point1X - point2X, 2) + 7 | Math.Pow(point1Y - point2Y, 2)); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Helpers/NoopDisposable.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Helpers; 2 | 3 | public class NoopDisposable : IDisposable 4 | { 5 | public void Dispose() 6 | { 7 | GC.SuppressFinalize(this); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Helpers/RandomGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | 3 | namespace ControlR.Libraries.Shared.Helpers; 4 | 5 | public class RandomGenerator 6 | { 7 | private const string AllowableCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ0123456789"; 8 | 9 | public static string CreateAccessToken() 10 | { 11 | return GenerateString(64); 12 | } 13 | 14 | public static string CreateDeviceToken() 15 | { 16 | return GenerateString(128); 17 | } 18 | public static string GenerateString(int length) 19 | { 20 | var bytes = RandomNumberGenerator.GetBytes(length); 21 | return new string([.. bytes.Select(x => AllowableCharacters[x % AllowableCharacters.Length])]); 22 | } 23 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Hubs/IAgentHub.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Dtos.HubDtos; 2 | using ControlR.Libraries.Shared.Dtos.ServerApi; 3 | using ControlR.Libraries.Shared.Dtos.StreamerDtos; 4 | using DeviceDto = ControlR.Libraries.Shared.Dtos.ServerApi.DeviceDto; 5 | 6 | namespace ControlR.Libraries.Shared.Hubs; 7 | public interface IAgentHub 8 | { 9 | Task SendStreamerDownloadProgress(StreamerDownloadProgressDto progressDto); 10 | Task SendTerminalOutputToViewer(string viewerConnectionId, TerminalOutputDto outputDto); 11 | Task> UpdateDevice(DeviceDto deviceDto); 12 | } 13 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Hubs/IViewerHub.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Dtos.HubDtos; 2 | using ControlR.Libraries.Shared.Dtos.StreamerDtos; 3 | using ControlR.Libraries.Shared.Models; 4 | 5 | namespace ControlR.Libraries.Shared.Hubs; 6 | 7 | public interface IViewerHub 8 | { 9 | Task CheckIfServerAdministrator(); 10 | 11 | Task> CreateTerminalSession( 12 | Guid deviceId, 13 | TerminalSessionRequest requestDto); 14 | 15 | Task> GetServerStats(); 16 | 17 | Task GetWebSocketRelayOrigin(); 18 | Task GetWindowsSessions(Guid deviceId); 19 | 20 | Task RequestStreamingSession(Guid deviceId, StreamerSessionRequestDto sessionRequestDto); 21 | Task SendDtoToAgent(Guid deviceId, DtoWrapper wrapper); 22 | Task SendDtoToUserGroups(DtoWrapper wrapper); 23 | Task SendTerminalInput(Guid deviceId, TerminalInputDto dto); 24 | Task UninstallAgent(Guid deviceId, string reason); 25 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/IO/ReactiveFileStream.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.IO; 2 | public class ReactiveFileStream(string path, FileMode mode) : FileStream(path, mode) 3 | { 4 | private int _totalRead = 0; 5 | 6 | private int _totalWritten = 0; 7 | 8 | public event EventHandler? TotalBytesReadChanged; 9 | 10 | public event EventHandler? TotalBytesWrittenChanged; 11 | 12 | public override int Read(byte[] buffer, int offset, int count) 13 | { 14 | var read = base.Read(buffer, offset, count); 15 | _totalRead += read; 16 | TotalBytesReadChanged?.Invoke(this, _totalRead); 17 | return read; 18 | } 19 | 20 | public override void Write(byte[] buffer, int offset, int count) 21 | { 22 | base.Write(buffer, offset, count); 23 | _totalWritten += count; 24 | TotalBytesWrittenChanged?.Invoke(this, _totalWritten); 25 | } 26 | 27 | protected override void Dispose(bool disposing) 28 | { 29 | if (disposing) 30 | { 31 | TotalBytesReadChanged = null; 32 | TotalBytesWrittenChanged = null; 33 | } 34 | 35 | base.Dispose(disposing); 36 | } 37 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Interfaces/HubClients/IAgentHubClient.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Dtos.HubDtos; 2 | using ControlR.Libraries.Shared.Dtos.StreamerDtos; 3 | using ControlR.Libraries.Shared.Models; 4 | 5 | namespace ControlR.Libraries.Shared.Interfaces.HubClients; 6 | 7 | public interface IAgentHubClient : IHubClient 8 | { 9 | Task CreateStreamingSession(StreamerSessionRequestDto dto); 10 | Task> CreateTerminalSession(TerminalSessionRequest requestDto); 11 | 12 | Task GetWindowsSessions(); 13 | Task ReceiveTerminalInput(TerminalInputDto dto); 14 | Task UninstallAgent(string reason); 15 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Interfaces/HubClients/IDesktopHubClient.cs: -------------------------------------------------------------------------------- 1 |  2 | 3 | namespace ControlR.Libraries.Shared.Interfaces.HubClients; 4 | public interface IStreamerHubClient : IHubClient 5 | { 6 | } 7 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Interfaces/HubClients/IHubClient.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Dtos.HubDtos; 2 | 3 | namespace ControlR.Libraries.Shared.Interfaces.HubClients; 4 | 5 | public interface IHubClient 6 | { 7 | Task ReceiveDto(DtoWrapper dtoWrapper); 8 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Interfaces/HubClients/IViewerHubClient.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Dtos.HubDtos; 2 | using ControlR.Libraries.Shared.Dtos.ServerApi; 3 | using ControlR.Libraries.Shared.Dtos.StreamerDtos; 4 | using ControlR.Libraries.Shared.Models; 5 | 6 | namespace ControlR.Libraries.Shared.Interfaces.HubClients; 7 | 8 | public interface IViewerHubClient : IHubClient 9 | { 10 | Task InvokeToast(ToastInfo toastInfo); 11 | Task ReceiveDeviceUpdate(DeviceDto deviceDto); 12 | Task ReceiveServerStats(ServerStatsDto serverStats); 13 | Task ReceiveStreamerDownloadProgress(StreamerDownloadProgressDto progressDto); 14 | Task ReceiveTerminalOutput(TerminalOutputDto output); 15 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Models/AgentAppOptions.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Models; 2 | 3 | [MessagePackObject] 4 | public class AgentAppOptions 5 | { 6 | public const string SectionKey = "AppOptions"; 7 | 8 | [Key(0)] 9 | public Guid DeviceId { get; set; } 10 | 11 | [Key(1)] 12 | public Uri? ServerUri { get; set; } 13 | 14 | [Key(2)] 15 | public Guid TenantId { get; set; } 16 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Models/AgentAppSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Nodes; 2 | 3 | namespace ControlR.Libraries.Shared.Models; 4 | 5 | public class AgentAppSettings 6 | { 7 | public AgentAppOptions? AppOptions { get; set; } 8 | public JsonNode? Serilog { get; set; } 9 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Models/ToastInfo.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Enums; 2 | namespace ControlR.Libraries.Shared.Models; 3 | 4 | [MessagePackObject] 5 | public record ToastInfo( 6 | [property: Key(0)] string Message, 7 | [property: Key(1)] MessageSeverity MessageSeverity); 8 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Models/WindowsSession.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Models; 2 | 3 | public enum WindowsSessionType 4 | { 5 | Console = 0, 6 | Rdp = 1 7 | } 8 | 9 | [MessagePackObject] 10 | public class WindowsSession 11 | { 12 | [Key(nameof(Id))] 13 | public uint Id { get; set; } 14 | 15 | [Key(nameof(Name))] 16 | public string Name { get; set; } = string.Empty; 17 | 18 | [Key(nameof(Type))] 19 | public WindowsSessionType Type { get; set; } 20 | 21 | [Key(nameof(Username))] 22 | public string Username { get; set; } = string.Empty; 23 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Primitives/CallbackDisposable.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Primitives; 2 | 3 | public sealed class CallbackDisposable( 4 | Action disposeCallback, 5 | Action? exceptionHandler = null) : IDisposable 6 | { 7 | public void Dispose() 8 | { 9 | try 10 | { 11 | disposeCallback(); 12 | } 13 | catch (Exception ex) 14 | { 15 | exceptionHandler?.Invoke(ex); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Primitives/CallbackDisposableAsync.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Primitives; 2 | 3 | public sealed class CallbackDisposableAsync( 4 | Func disposeCallback, 5 | Func? exceptionHandler = null) : IAsyncDisposable 6 | { 7 | public async ValueTask DisposeAsync() 8 | { 9 | try 10 | { 11 | await disposeCallback(); 12 | } 13 | catch (Exception ex) 14 | { 15 | if (exceptionHandler is not null) 16 | { 17 | await exceptionHandler.Invoke(ex); 18 | } 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Primitives/Closable.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Collections; 2 | using System.Collections.Concurrent; 3 | 4 | namespace ControlR.Libraries.Shared.Primitives; 5 | 6 | public interface IClosable 7 | { 8 | Task InvokeOnClosed(); 9 | IDisposable OnClosed(Func callback); 10 | } 11 | 12 | public class Closable(ILogger logger) : IClosable 13 | { 14 | private readonly ConcurrentList> _onCloseCallbacks = []; 15 | 16 | public IDisposable OnClosed(Func callback) 17 | { 18 | _onCloseCallbacks.Add(callback); 19 | return new CallbackDisposable(() => { _onCloseCallbacks.Remove(callback); }); 20 | } 21 | 22 | public async Task InvokeOnClosed() 23 | { 24 | foreach (var callback in _onCloseCallbacks) 25 | { 26 | try 27 | { 28 | await callback(); 29 | } 30 | catch (Exception ex) 31 | { 32 | logger.LogError(ex, "Error while executing on close callback."); 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Primitives/DisposableValue.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace ControlR.Libraries.Shared.Primitives; 4 | 5 | public sealed class DisposableValue( 6 | T value, 7 | Action? disposeCallback = null) : IDisposable 8 | { 9 | private readonly Action? _disposeCallback = disposeCallback; 10 | 11 | public T Value { get; } = value; 12 | 13 | public void Dispose() 14 | { 15 | try 16 | { 17 | _disposeCallback?.Invoke(); 18 | } 19 | catch (Exception ex) 20 | { 21 | Debug.WriteLine 22 | ($"Error while invoking callback in {nameof(DisposableValue)}. " + 23 | $"Exception: {ex}"); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("ControlR.Devices.Common")] 4 | [assembly: InternalsVisibleTo("ControlR.Streamer")] 5 | [assembly: InternalsVisibleTo("ControlR.Web.Server")] 6 | [assembly: InternalsVisibleTo("ControlR.Web.Client")] 7 | [assembly: InternalsVisibleTo("ControlR.Agent.LoadTester")] -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Services/Buffers/EphemeralBuffer.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | 3 | namespace ControlR.Libraries.Shared.Services.Buffers; 4 | 5 | public interface IEphemeralBuffer : IDisposable 6 | { 7 | int Size { get; } 8 | T[] Value { get; } 9 | } 10 | 11 | internal sealed class EphemeralBuffer(int size) : IEphemeralBuffer 12 | { 13 | private bool _disposedValue; 14 | 15 | public int Size { get; } = size; 16 | public T[] Value { get; } = ArrayPool.Shared.Rent(size); 17 | 18 | public void Dispose() 19 | { 20 | Dispose(true); 21 | } 22 | 23 | private void Dispose(bool disposing) 24 | { 25 | if (_disposedValue) 26 | { 27 | return; 28 | } 29 | 30 | _disposedValue = true; 31 | 32 | if (disposing) 33 | { 34 | ArrayPool.Shared.Return(Value); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Services/Buffers/MemoryProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.IO; 2 | 3 | namespace ControlR.Libraries.Shared.Services.Buffers; 4 | 5 | public interface IMemoryProvider 6 | { 7 | IEphemeralBuffer CreateEphemeralBuffer(int size); 8 | MemoryStream GetRecyclableStream(); 9 | } 10 | 11 | public class MemoryProvider : IMemoryProvider 12 | { 13 | private readonly RecyclableMemoryStreamManager _memoryStreamManager = new(); 14 | public IEphemeralBuffer CreateEphemeralBuffer(int size) 15 | { 16 | return new EphemeralBuffer(size); 17 | } 18 | 19 | public MemoryStream GetRecyclableStream() 20 | { 21 | return _memoryStreamManager.GetStream(); 22 | } 23 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Services/Http/WsRelayApi.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Shared.Services.Http; 2 | 3 | public interface IWsRelayApi 4 | { 5 | Task IsHealthy(Uri origin); 6 | } 7 | 8 | internal class WsRelayApi( 9 | HttpClient client, 10 | ILogger logger) : IWsRelayApi 11 | { 12 | public async Task IsHealthy(Uri origin) 13 | { 14 | try 15 | { 16 | var healthUri = new Uri(origin.ToHttpUri(), "/health"); 17 | using var response = await client.GetAsync(healthUri); 18 | response.EnsureSuccessStatusCode(); 19 | return true; 20 | } 21 | catch (Exception ex) 22 | { 23 | logger.LogError(ex, "Error while checking health."); 24 | return false; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Shared/Usings.cs: -------------------------------------------------------------------------------- 1 | global using ControlR.Libraries.Shared.Extensions; 2 | global using ControlR.Libraries.Shared.Primitives; 3 | global using ControlR.Libraries.Shared.Dtos; 4 | global using MessagePack; 5 | global using Microsoft.Extensions.Logging; 6 | global using ControlR.Libraries.Shared.Dtos.Interfaces; -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Signalr.Client/ControlR.Libraries.Signalr.Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Signalr.Client/Diagnostics/DefaultActivitySource.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace ControlR.Libraries.Signalr.Client.Diagnostics; 4 | internal static class DefaultActivitySource 5 | { 6 | public const string Name = "ControlR.Libraries.Signalr.Client"; 7 | public static readonly ActivitySource Instance = new(Name); 8 | public static Activity? StartActivity(string name) => Instance.StartActivity(name); 9 | } 10 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Signalr.Client/Exceptions/DynamicObjectGenerationException.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.Signalr.Client.Exceptions; 2 | 3 | public class DynamicObjectGenerationException(string message) : Exception(message); 4 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.Signalr.Client/Internals/IInvocationHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Threading.Channels; 3 | 4 | namespace ControlR.Libraries.Signalr.Client.Internals; 5 | 6 | /// 7 | /// Represents an object that can handle invocations of methods and 8 | /// proxy them to a SignalR hub. 9 | /// 10 | public interface IInvocationHandler 11 | { 12 | Task InvokeAsync(MethodInfo method, object[] args); 13 | 14 | ChannelReader InvokeChannel(MethodInfo method, object[] args); 15 | 16 | ValueTask InvokeValueTaskAsync(MethodInfo method, object[] args); 17 | 18 | Task InvokeVoidAsync(MethodInfo method, object[] args); 19 | IAsyncEnumerable Stream(MethodInfo method, object[] args); 20 | } 21 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.WebSocketRelay.Common/ControlR.Libraries.WebSocketRelay.Common.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | false 8 | WebSocket Relay 9 | ©️ $([System.DateTime]::Now.Year) Jared Goodwin 10 | https://github.com/bitbound/controlr 11 | An ASP.NET Core Native AOT library for bridging client websocket connections. 12 | appicon.png 13 | README.md 14 | https://github.com/bitbound/controlr 15 | 1.0.3 16 | MIT 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.WebSocketRelay.Common/Extensions/Extensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.WebSockets; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace ControlR.Libraries.WebSocketRelay.Common.Extensions; 6 | public static class Extensions 7 | { 8 | public static IServiceCollection AddWebSocketRelay( 9 | this IServiceCollection services) 10 | { 11 | services.AddWebSockets(_ => { }); 12 | services.AddSingleton(); 13 | return services; 14 | } 15 | 16 | public static IApplicationBuilder MapWebSocketRelay( 17 | this IApplicationBuilder app, 18 | string websocketPath = "/relay") 19 | { 20 | app.UseWhen(x => x.Request.Path.StartsWithSegments(websocketPath), x => 21 | { 22 | x.UseWebSockets(); 23 | x.UseMiddleware(); 24 | }); 25 | return app; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.WebSocketRelay.Common/Helpers/DisposeHelper.cs: -------------------------------------------------------------------------------- 1 | namespace ControlR.Libraries.WebSocketRelay.Common.Helpers; 2 | 3 | internal static class DisposeHelper 4 | { 5 | public static void DisposeAll(params IDisposable?[] disposables) 6 | { 7 | foreach (var disposable in disposables) 8 | { 9 | try 10 | { 11 | disposable?.Dispose(); 12 | } 13 | catch { } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.WebSocketRelay.Common/Usings.cs: -------------------------------------------------------------------------------- 1 | global using ControlR.Libraries.WebSocketRelay.Common.Helpers; 2 | global using ControlR.Libraries.WebSocketRelay.Common.Middleware; 3 | global using ControlR.Libraries.WebSocketRelay.Common.Sessions; -------------------------------------------------------------------------------- /Libraries/ControlR.Libraries.WebSocketRelay.Common/appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbound/ControlR/79e29a6c09b6fbbd08ff37bd7e6b9eca3a9d00b3/Libraries/ControlR.Libraries.WebSocketRelay.Common/appicon.png -------------------------------------------------------------------------------- /Tests/ControlR.Agent.LoadTester/ControlR.Agent.LoadTester.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0 6 | enable 7 | enable 8 | Linux 9 | ..\.. 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/ControlR.Agent.LoadTester/FakeAgentUpdater.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Agent.Common.Services; 2 | using ControlR.Libraries.Shared.Primitives; 3 | using Microsoft.Extensions.Hosting; 4 | 5 | namespace ControlR.Agent.LoadTester; 6 | internal class FakeAgentUpdater : IHostedService, IAgentUpdater 7 | { 8 | public ManualResetEventAsync UpdateCheckCompletedSignal { get; } = new(); 9 | 10 | public Task CheckForUpdate(CancellationToken cancellationToken = default) 11 | { 12 | UpdateCheckCompletedSignal.Set(); 13 | return Task.CompletedTask; 14 | } 15 | 16 | public Task StartAsync(CancellationToken cancellationToken) 17 | { 18 | return Task.CompletedTask; 19 | } 20 | 21 | public Task StopAsync(CancellationToken cancellationToken) 22 | { 23 | return Task.CompletedTask; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Tests/ControlR.Agent.LoadTester/FakeAppHostLifetime.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Hosting; 2 | 3 | namespace ControlR.Agent.LoadTester; 4 | internal class FakeAppHostLifetime(CancellationToken stoppingToken) : IHostApplicationLifetime 5 | { 6 | private readonly static CancellationTokenSource _source = new(0); 7 | public CancellationToken ApplicationStarted { get; } = _source.Token; 8 | 9 | public CancellationToken ApplicationStopping { get; } = stoppingToken; 10 | 11 | public CancellationToken ApplicationStopped { get; } = stoppingToken; 12 | 13 | public void StopApplication() 14 | { 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Tests/ControlR.Agent.LoadTester/FakeCpuUtilizationSampler.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Agent.Common.Services; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace ControlR.Agent.LoadTester; 5 | internal class FakeCpuUtilizationSampler : ICpuUtilizationSampler, IHostedService 6 | { 7 | public double CurrentUtilization { get; } = 0; 8 | 9 | public Task StartAsync(CancellationToken cancellationToken) 10 | { 11 | return Task.CompletedTask; 12 | } 13 | 14 | public Task StopAsync(CancellationToken cancellationToken) 15 | { 16 | return Task.CompletedTask; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/ControlR.Agent.LoadTester/FakeSettingsProvider.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Agent.Common.Services; 2 | using ControlR.Libraries.Shared.Models; 3 | 4 | namespace ControlR.Agent.LoadTester; 5 | internal class FakeSettingsProvider(Guid deviceId, Uri serverUri) : ISettingsProvider 6 | { 7 | public Guid DeviceId => deviceId; 8 | public string InstanceId { get; } = string.Empty; 9 | 10 | public Uri ServerUri { get; } = serverUri; 11 | 12 | public string GetAppSettingsPath() 13 | { 14 | return string.Empty; 15 | } 16 | 17 | public Task UpdateAppOptions(AgentAppOptions options) 18 | { 19 | return Task.CompletedTask; 20 | } 21 | 22 | public Task UpdateId(Guid uid) 23 | { 24 | return Task.CompletedTask; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Tests/ControlR.Agent.LoadTester/FakeStreamerLauncher.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Agent.Common.Interfaces; 2 | using ControlR.Libraries.Shared.Extensions; 3 | using ControlR.Libraries.Shared.Primitives; 4 | 5 | namespace ControlR.Agent.LoadTester; 6 | internal class FakeStreamerLauncher : IStreamerLauncher 7 | { 8 | public Task CreateSession(Guid sessionId, Uri websocketUri, string viewerConnectionId, int targetWindowsSession = -1, bool notifyUserOnSessionStart = false, string? viewerName = null) 9 | { 10 | return Result.Ok().AsTaskResult(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Tests/ControlR.Agent.LoadTester/FakeStreamerUpdater.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Agent.Common.Interfaces; 2 | using ControlR.Libraries.Shared.Dtos.StreamerDtos; 3 | using Microsoft.Extensions.Hosting; 4 | 5 | namespace ControlR.Agent.LoadTester; 6 | internal class FakeStreamerUpdater : IHostedService, IStreamerUpdater 7 | { 8 | 9 | public Task EnsureLatestVersion(StreamerSessionRequestDto requestDto, CancellationToken cancellationToken) 10 | { 11 | return Task.FromResult(true); 12 | } 13 | 14 | public Task EnsureLatestVersion(CancellationToken cancellationToken) 15 | { 16 | return Task.FromResult(true); 17 | } 18 | 19 | public Task StartAsync(CancellationToken cancellationToken) 20 | { 21 | return Task.CompletedTask; 22 | } 23 | 24 | public Task StopAsync(CancellationToken cancellationToken) 25 | { 26 | return Task.CompletedTask; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Tests/ControlR.Agent.LoadTester/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Versioning; 2 | 3 | [assembly: SupportedOSPlatform("windows6.0.6000")] -------------------------------------------------------------------------------- /Tests/ControlR.Agent.LoadTester/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | Release 8 | Any CPU 9 | bin\publish\ 10 | FileSystem 11 | <_TargetId>Folder 12 | net9.0 13 | win-x86 14 | true 15 | false 16 | false 17 | false 18 | 19 | -------------------------------------------------------------------------------- /Tests/ControlR.Agent.LoadTester/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "local": { 4 | "commandName": "Project", 5 | "commandLineArgs": "--server-uri http://localhost:5120 --agent-count 10 --start-count 0" 6 | }, 7 | "Container (Dockerfile)": { 8 | "commandName": "Docker" 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /Tests/ControlR.Agent.LoadTester/TestAgentRetryPolicy.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.SignalR.Client; 2 | 3 | namespace ControlR.Agent.LoadTester; 4 | 5 | public class TestAgentRetryPolicy : IRetryPolicy 6 | { 7 | public TimeSpan? NextRetryDelay(RetryContext retryContext) 8 | { 9 | return TimeSpan.FromSeconds(10); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Tests/ControlR.Libraries.DevicesCommon.Tests/Usings.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitbound/ControlR/79e29a6c09b6fbbd08ff37bd7e6b9eca3a9d00b3/Tests/ControlR.Libraries.DevicesCommon.Tests/Usings.cs -------------------------------------------------------------------------------- /Tests/ControlR.Libraries.ScreenCapture.Benchmarks/ControlR.Libraries.ScreenCapture.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net9.0-windows10.0.20348.0 6 | enable 7 | enable 8 | Debug;Release 9 | true 10 | true 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Tests/ControlR.Libraries.ScreenCapture.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.ScreenCapture.Benchmarks; 2 | 3 | 4 | //var config = new DebugInProcessConfig(); 5 | //var summary = BenchmarkRunner.Run(config); 6 | //Console.WriteLine($"{summary}"); 7 | 8 | var test = new CaptureTests(); 9 | //test.DoCaptures(); 10 | //test.DoEncoding(); 11 | //test.DoCaptureEncodeAndDiff(); 12 | //test.DoDiffSizeComparison(); 13 | test.DoSaveToDesktop(); 14 | //await test.DoWinRtComparison(); -------------------------------------------------------------------------------- /Tests/ControlR.Tests.TestingUtilities/ControlR.Tests.TestingUtilities.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net9.0 4 | enable 5 | enable 6 | false 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Tests/ControlR.Tests.TestingUtilities/OptionsMonitorWrapper.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Libraries.Shared.Primitives; 2 | using Microsoft.Extensions.Options; 3 | 4 | namespace ControlR.Tests.TestingUtilities; 5 | 6 | public class OptionsMonitorWrapper(T options) : IOptionsMonitor 7 | { 8 | private readonly List> _listeners = []; 9 | 10 | private T _options = options; 11 | 12 | public T CurrentValue => _options; 13 | 14 | public T Get(string? name) => _options; 15 | 16 | public void Update(T options, string? name) 17 | { 18 | _options = options; 19 | lock (_listeners) 20 | { 21 | foreach (var listener in _listeners) 22 | { 23 | listener.Invoke(options, name); 24 | } 25 | } 26 | } 27 | 28 | public IDisposable OnChange(Action listener) 29 | { 30 | lock (_listeners) 31 | { 32 | _listeners.Add(listener); 33 | } 34 | 35 | return new CallbackDisposable(() => 36 | { 37 | lock (_listeners) 38 | { 39 | _listeners.Remove(listener); 40 | } 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Tests/ControlR.Tests.TestingUtilities/XunitLoggerProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Xunit.Abstractions; 3 | 4 | namespace ControlR.Tests.TestingUtilities; 5 | 6 | public class XunitLoggerProvider(ITestOutputHelper testOutputHelper) : ILoggerProvider 7 | { 8 | private readonly ITestOutputHelper _testOutputHelper = testOutputHelper; 9 | public ILogger CreateLogger(string categoryName) 10 | { 11 | return new XunitLogger(_testOutputHelper, categoryName); 12 | } 13 | 14 | public void Dispose() 15 | { 16 | GC.SuppressFinalize(this); 17 | } 18 | } -------------------------------------------------------------------------------- /Tests/ControlR.Web.Server.Tests/Helpers/EntityFrameworkQueryHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Logging; 7 | using Xunit; 8 | using Xunit.Abstractions; 9 | using System.Collections.Immutable; 10 | using Microsoft.AspNetCore.Http; 11 | using Microsoft.EntityFrameworkCore; 12 | 13 | namespace ControlR.Web.Server.Tests.Helpers; 14 | 15 | /// 16 | /// Extension methods to help with debugging EF Core queries 17 | /// 18 | public static class EntityFrameworkQueryHelper 19 | { 20 | /// 21 | /// Converts database-level queries to client-side evaluation for tests 22 | /// 23 | /// The original query 24 | /// A queryable that will be evaluated client-side 25 | public static IQueryable AsClientEvaluation(this IQueryable query) 26 | { 27 | // This forces client-side evaluation for tests 28 | return query.ToList().AsQueryable(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Tests/ControlR.Web.Server.Tests/Helpers/TestApp.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.Extensions.Time.Testing; 3 | 4 | namespace ControlR.Web.Server.Tests.Helpers; 5 | 6 | public class TestApp( 7 | WebApplication app, 8 | FakeTimeProvider timeProvider) : IAsyncDisposable 9 | { 10 | public WebApplication App { get; } = app; 11 | public IServiceProvider Services => App.Services; 12 | public FakeTimeProvider TimeProvider { get; } = timeProvider; 13 | public async ValueTask DisposeAsync() 14 | { 15 | await App.DisposeAsync(); 16 | GC.SuppressFinalize(this); 17 | } 18 | } -------------------------------------------------------------------------------- /Tests/ControlR.Web.Server.Tests/Helpers/TestAppExtensions.cs: -------------------------------------------------------------------------------- 1 | using ControlR.Web.Server.Data.Entities; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using System.Security.Claims; 6 | 7 | namespace ControlR.Web.Server.Tests.Helpers; 8 | 9 | public static class TestAppExtensions 10 | { 11 | /// 12 | /// Creates an instance of a controller with the necessary services injected from the TestApp 13 | /// 14 | /// The controller type to create 15 | /// The test application instance 16 | /// An instance of the controller 17 | public static T CreateController(this TestApp testApp) where T : ControllerBase 18 | { 19 | var controller = ActivatorUtilities.CreateInstance(testApp.Services); 20 | controller.ControllerContext = new ControllerContext 21 | { 22 | HttpContext = new DefaultHttpContext 23 | { 24 | RequestServices = testApp.Services 25 | } 26 | }; 27 | return controller; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /docker-compose/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md 26 | !**/.gitignore 27 | !.git/HEAD 28 | !.git/config 29 | !.git/packed-refs 30 | !.git/refs/heads/** -------------------------------------------------------------------------------- /docker-compose/.env: -------------------------------------------------------------------------------- 1 | ControlR_POSTGRES_USER=postgres 2 | ControlR_POSTGRES_PASSWORD=password 3 | ControlR_ASPIRE_BROWSER_TOKEN=token -------------------------------------------------------------------------------- /docker-compose/docker-compose.override.yml: -------------------------------------------------------------------------------- 1 | services: 2 | controlr: 3 | image: ${DOCKER_REGISTRY-}controlrwebserver 4 | environment: 5 | ASPNETCORE_ENVIRONMENT: Development 6 | ASPNETCORE_HTTP_PORTS: 8080 7 | ASPNETCORE_HTTPS_PORTS: 8081 8 | ports: 9 | - "5120:8080" 10 | - "7031:8081" 11 | build: 12 | context: ../ 13 | dockerfile: ControlR.Web.Server/Dockerfile 14 | volumes: 15 | - ${APPDATA}/Microsoft/UserSecrets:/home/app/.microsoft/usersecrets:ro 16 | - ${APPDATA}/ASP.NET/Https:/home/app/.aspnet/https:ro 17 | 18 | aspire: 19 | ports: 20 | - "18889:18889" 21 | environment: 22 | DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS: true -------------------------------------------------------------------------------- /docker-compose/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "controlr": { 4 | "commandName": "DockerCompose", 5 | "commandVersion": "1.0", 6 | "composeLaunchAction": "LaunchBrowser", 7 | "composeLaunchServiceName": "controlr", 8 | "composeLaunchUrl": "http://localhost:5120/", 9 | "serviceActions": { 10 | "controlr": "StartDebugging" 11 | } 12 | }, 13 | "postgres": { 14 | "commandName": "DockerCompose", 15 | "commandVersion": "1.0", 16 | "composeLaunchAction": "DoNothing", 17 | "composeLaunchServiceName": "postgres", 18 | "serviceActions": { 19 | "postgres": "StartWithoutDebugging", 20 | "aspire": "StartWithoutDebugging", 21 | "controlr": "DoNotStart" 22 | } 23 | } 24 | } 25 | } --------------------------------------------------------------------------------