├── .github ├── dependabot.yml └── workflows │ └── build.yml ├── .gitignore ├── .npmrc ├── .prettierrc.js ├── .vscode ├── launch.json └── tasks.json ├── @xoutput ├── README.md ├── api │ ├── README.md │ ├── eslint.config.cjs │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ ├── common │ │ │ ├── DeviceTypes.ts │ │ │ ├── Emulators.ts │ │ │ ├── SourceTypes.ts │ │ │ ├── TargetTypes.ts │ │ │ └── input │ │ │ │ ├── InputDeviceApi.ts │ │ │ │ ├── InputDeviceSource.ts │ │ │ │ └── InputDeviceTarget.ts │ │ ├── index.ts │ │ ├── rest │ │ │ ├── emulation │ │ │ │ ├── EmulatedControllerInfo.ts │ │ │ │ └── ListEmulatorsResponse.ts │ │ │ ├── help │ │ │ │ └── Info.ts │ │ │ ├── input │ │ │ │ └── InputDeviceInfo.ts │ │ │ ├── mapping │ │ │ │ ├── CreateMappedControllerRequest.ts │ │ │ │ └── MappedControllerInfo.ts │ │ │ └── notifications │ │ │ │ └── Notification.ts │ │ └── websocket │ │ │ ├── MessageBase.ts │ │ │ ├── common │ │ │ ├── DebugRequest.ts │ │ │ ├── PingRequest.ts │ │ │ └── PongResponse.ts │ │ │ ├── ds4 │ │ │ ├── Ds4FeedbackResponse.ts │ │ │ └── Ds4InputRequest.ts │ │ │ ├── emulation │ │ │ ├── ControllerInputResponse.ts │ │ │ ├── ControllerSourceValue.ts │ │ │ └── ControllerTargetValue.ts │ │ │ ├── input │ │ │ ├── InputDeviceDetailsRequest.ts │ │ │ ├── InputDeviceFeedbackResponse.ts │ │ │ ├── InputDeviceInputRequest.ts │ │ │ ├── InputDeviceInputResponse.ts │ │ │ ├── InputDeviceSourceValue.ts │ │ │ └── InputDeviceTargetValue.ts │ │ │ └── xbox │ │ │ ├── XboxFeedbackResponse.ts │ │ │ └── XboxInputRequest.ts │ ├── tsconfig.json │ └── vite.config.js ├── client │ ├── README.md │ ├── eslint.config.cjs │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ │ ├── index.ts │ │ ├── rest │ │ │ ├── emulation │ │ │ │ ├── EmulatedControllersClient.ts │ │ │ │ └── EmulationClient.ts │ │ │ ├── help │ │ │ │ └── InfoClient.ts │ │ │ ├── http.ts │ │ │ ├── input │ │ │ │ └── InputsClient.ts │ │ │ ├── mapping │ │ │ │ └── MappedControllersClient.ts │ │ │ └── notifications │ │ │ │ └── NotificationClient.ts │ │ └── websocket │ │ │ ├── ds4 │ │ │ └── Ds4DeviceClient.ts │ │ │ ├── emulation │ │ │ └── EmulatedControllerFeedbackClient.ts │ │ │ ├── input │ │ │ ├── InputDeviceClient.ts │ │ │ └── InputDeviceFeedbackClient.ts │ │ │ ├── mapping │ │ │ └── MappedControllerFeedbackClient.ts │ │ │ ├── websocket.ts │ │ │ └── xbox │ │ │ └── XboxDeviceClient.ts │ ├── tsconfig.json │ └── vite.config.js └── webapp │ ├── README.md │ ├── eslint.config.js │ ├── package.json │ ├── pnpm-lock.yaml │ ├── src │ ├── events │ │ ├── base.ts │ │ └── eventholder.ts │ ├── favicon.ico │ ├── files.d.ts │ ├── gamepad │ │ ├── GamepadReader.ts │ │ └── GamepadService.ts │ ├── index.html │ ├── index.scss │ ├── index.tsx │ ├── queries │ │ ├── useEmulatedControllersQuery.ts │ │ ├── useInputsQuery.ts │ │ ├── useMappedControllersQuery.ts │ │ └── useNotificationsQuery.ts │ ├── translation │ │ ├── English.json │ │ ├── Hungarian.json │ │ └── Translation.ts │ ├── ui │ │ ├── MainMenu.tsx │ │ ├── MainMenuListItem.tsx │ │ ├── RootElement.tsx │ │ ├── Router.tsx │ │ ├── TranslatedText.tsx │ │ ├── components │ │ │ ├── Asnyc.tsx │ │ │ └── Placeholder.tsx │ │ ├── emulation │ │ │ ├── EmulatedControllers.tsx │ │ │ ├── axis.tsx │ │ │ ├── button.tsx │ │ │ ├── common.ts │ │ │ ├── dpad.tsx │ │ │ ├── ds4.tsx │ │ │ ├── slider.tsx │ │ │ ├── square.tsx │ │ │ └── xbox.tsx │ │ ├── input │ │ │ ├── Gamepad.tsx │ │ │ ├── GamepadValue.tsx │ │ │ ├── InputDevice.tsx │ │ │ ├── InputDevices.tsx │ │ │ ├── InputReader.tsx │ │ │ └── dpad.tsx │ │ ├── mapping │ │ │ └── MappedControllers.tsx │ │ └── notifications │ │ │ └── Notifications.tsx │ └── utils │ │ └── EventEmitter.ts │ ├── tsconfig.json │ └── vite.config.js ├── LICENSE ├── README.md ├── XOutput.Api ├── Common │ ├── DeviceTypes.cs │ ├── Emulators.cs │ ├── Input │ │ ├── InputDeviceApi.cs │ │ ├── InputDeviceSource.cs │ │ └── InputDeviceTarget.cs │ ├── SourceTypes.cs │ └── TargetTypes.cs ├── README.md ├── Rest │ ├── Emulation │ │ ├── EmulatedControllerInfo.cs │ │ └── ListEmulatorsResponse.cs │ ├── Help │ │ └── InfoResponse.cs │ ├── Input │ │ └── InputDeviceInfo.cs │ ├── Mapping │ │ ├── CreateMappedControllerRequest.cs │ │ └── MappedControllerInfo.cs │ └── Notifications │ │ └── Notification.cs ├── Serialization │ ├── MessageReader.cs │ └── MessageWriter.cs ├── Websocket │ ├── Common │ │ ├── DebugRequest.cs │ │ ├── PingRequest.cs │ │ └── PongResponse.cs │ ├── Ds4 │ │ ├── Ds4FeedbackResponse.cs │ │ └── Ds4InputRequest.cs │ ├── Emulation │ │ ├── ControllerInputResponse.cs │ │ ├── ControllerSourceValue.cs │ │ └── ControllerTargetValue.cs │ ├── Input │ │ ├── InputDeviceDetailsRequest.cs │ │ ├── InputDeviceFeedbackResponse.cs │ │ ├── InputDeviceInputRequest.cs │ │ ├── InputDeviceInputResponse.cs │ │ ├── InputDeviceSourceValue.cs │ │ └── InputDeviceTargetValue.cs │ ├── MessageBase.cs │ └── Xbox │ │ ├── XboxFeedbackResponse.cs │ │ └── XboxInputRequest.cs └── XOutput.Api.csproj ├── XOutput.ApiTests ├── Serialization │ ├── MessageReaderTests.cs │ └── MessageWriterTests.cs └── XOutput.ApiTests.csproj ├── XOutput.App ├── App.xaml ├── App.xaml.cs ├── AppConfiguration.cs ├── Configuration │ ├── AppConfig.cs │ └── RegistryModifierService.cs ├── Devices │ ├── DPadDirection.cs │ ├── HidGuardianManager.cs │ └── Input │ │ ├── DeviceConnectedEvent.cs │ │ ├── DeviceDisconnectedEvent.cs │ │ ├── DeviceInputChangedEvent.cs │ │ ├── DirectInput │ │ ├── DirectDeviceForceFeedback.cs │ │ ├── DirectInputDevice.cs │ │ ├── DirectInputDeviceProvider.cs │ │ ├── DirectInputSource.cs │ │ └── Native │ │ │ ├── DInput8.cs │ │ │ └── Windows.cs │ │ ├── ForceFeedbackTarget.cs │ │ ├── IInputDevice.cs │ │ ├── IInputDeviceProvider.cs │ │ ├── IdHelper.cs │ │ ├── IgnoredDeviceService.cs │ │ ├── IgnoredDevicesConfig.cs │ │ ├── InputConfig.cs │ │ ├── InputConfigManager.cs │ │ ├── InputDeviceHolder.cs │ │ ├── InputDeviceManager.cs │ │ ├── InputDeviceMethod.cs │ │ ├── InputSource.cs │ │ ├── Keyboard │ │ ├── KeyboardButton.cs │ │ ├── KeyboardDevice.cs │ │ ├── KeyboardDeviceProvider.cs │ │ ├── KeyboardHook.cs │ │ └── KeyboardSource.cs │ │ ├── Mouse │ │ ├── MouseButton.cs │ │ ├── MouseDevice.cs │ │ ├── MouseDeviceProvider.cs │ │ ├── MouseHook.cs │ │ └── MouseSource.cs │ │ └── RawInput │ │ ├── RawInputDevice.cs │ │ ├── RawInputDeviceProvider.cs │ │ └── RawInputSource.cs ├── Properties │ └── launchSettings.json ├── README.md ├── Resources │ ├── Translations │ │ ├── English.json │ │ └── Hungarian.json │ └── icon.ico ├── UI │ ├── Component │ │ ├── Titled.xaml │ │ └── Titled.xaml.cs │ ├── Converter │ │ ├── DynamicTranslationConverter.cs │ │ └── TranslationConverter.cs │ ├── IViewBase.cs │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── MainWindowModel.cs │ ├── MainWindowViewModel.cs │ ├── ModelBase.cs │ ├── TranslationModel.cs │ ├── TranslationService.cs │ ├── View │ │ ├── DirectInputPanel.xaml │ │ ├── DirectInputPanel.xaml.cs │ │ ├── DirectInputPanelModel.cs │ │ ├── DirectInputPanelViewModel.cs │ │ ├── GeneralPanel.xaml │ │ ├── GeneralPanel.xaml.cs │ │ ├── GeneralPanelModel.cs │ │ ├── GeneralPanelViewModel.cs │ │ ├── InputView │ │ │ ├── DirectInputDeviceModel.cs │ │ │ ├── DirectInputDeviceView.xaml │ │ │ ├── DirectInputDeviceView.xaml.cs │ │ │ ├── DirectInputDeviceViewModel.cs │ │ │ ├── RawInputDeviceModel.cs │ │ │ ├── RawInputDeviceView.xaml │ │ │ ├── RawInputDeviceView.xaml.cs │ │ │ └── RawInputDeviceViewModel.cs │ │ ├── RawInputPanel.xaml │ │ ├── RawInputPanel.xaml.cs │ │ ├── RawInputPanelModel.cs │ │ ├── RawInputPanelViewModel.cs │ │ ├── WindowsApiKeyboardPanel.xaml │ │ ├── WindowsApiKeyboardPanel.xaml.cs │ │ ├── WindowsApiKeyboardPanelModel.cs │ │ ├── WindowsApiKeyboardPanelViewModel.cs │ │ ├── WindowsApiMousePanel.xaml │ │ ├── WindowsApiMousePanel.xaml.cs │ │ ├── WindowsApiMousePanelModel.cs │ │ ├── WindowsApiMousePanelViewModel.cs │ │ ├── XInputPanel.xaml │ │ ├── XInputPanel.xaml.cs │ │ ├── XInputPanelModel.cs │ │ └── XInputPanelViewModel.cs │ └── ViewModelBase.cs ├── XOutput.App.csproj └── nlog.config ├── XOutput.AppTests ├── DPadDirectionTests.cs ├── SourceTypesTests.cs └── XOutput.AppTests.csproj ├── XOutput.Client ├── ClientConfiguration.cs ├── README.md ├── Rest │ ├── Emulation │ │ ├── EmulatedContollersClient.cs │ │ └── EmulationClient.cs │ ├── Help │ │ └── InfoClient.cs │ ├── HttpClientProvider.cs │ ├── HttpJsonClient.cs │ ├── Input │ │ └── InputsClient.cs │ ├── Mapping │ │ └── MappedContollersClient.cs │ └── Notifications │ │ └── NotificationClient.cs ├── Websocket │ ├── Ds4 │ │ └── Ds4DeviceClient.cs │ ├── Emulation │ │ └── EmulatedControllerFeedbackClient.cs │ ├── Input │ │ ├── InputDeviceClient.cs │ │ └── InputDeviceFeedbackClient.cs │ ├── Mapping │ │ └── MappedControllerFeedbackClient.cs │ ├── WebsocketJsonClient.cs │ └── Xbox │ │ └── XBoxDeviceClient.cs └── XOutput.Client.csproj ├── XOutput.Core ├── Configuration │ ├── ConfigurationBase.cs │ ├── ConfigurationManager.cs │ └── JsonConfigurationManager.cs ├── CoreConfiguration.cs ├── DependencyInjection │ ├── ApplicationContext.cs │ ├── Dependency.cs │ ├── MultipleValuesFoundException.cs │ ├── NoValueFoundException.cs │ ├── Resolver.cs │ ├── ResolverMethod.cs │ ├── Scope.cs │ └── TypeFinder.cs ├── Exceptions │ ├── ExceptionHandler.cs │ └── SafeCallResult.cs ├── External │ ├── CommandRunner.cs │ ├── ProcessErrorException.cs │ └── ProcessHelper.cs ├── Notifications │ ├── NotificationItem.cs │ ├── NotificationService.cs │ ├── NotificationTypes.cs │ └── Notifications.cs ├── Number │ └── NumberHelper.cs ├── README.md ├── Resources │ └── AssemblyResourceManager.cs ├── Threading │ ├── ThreadContext.cs │ ├── ThreadCreator.cs │ ├── ThreadResult.cs │ └── WindowHandle.cs ├── Versioning │ ├── GithubVersionGetter.cs │ ├── IVersionGetter.cs │ ├── UpdateChecker.cs │ └── Version.cs ├── WebSocket │ └── WebSocketHelper.cs └── XOutput.Core.csproj ├── XOutput.CoreTests ├── DependencyInjection │ └── ApplicationContextTests.cs ├── Exceptions │ └── ExceptionHandlerTests.cs ├── External │ └── CommandRunnerTests.cs ├── Number │ └── NumberHelperTests.cs ├── Threading │ └── ThreadCreatorTests.cs └── XOutput.CoreTests.csproj ├── XOutput.Emulation ├── Ds4 │ ├── Ds4Device.cs │ ├── Ds4FeedbackEvent.cs │ ├── Ds4Input.cs │ └── IDs4Emulator.cs ├── EmulatorService.cs ├── IDevice.cs ├── IEmulator.cs ├── NetworkDeviceInfo.cs ├── NetworkDeviceInfoService.cs ├── README.md ├── SCPToolkit │ ├── NativeMethods.cs │ ├── ScpClient.cs │ ├── ScpDevice.cs │ └── ScpEmulator.cs ├── ViGEm │ ├── ViGEmDs4Device.cs │ ├── VigemEmulator.cs │ └── VigemXboxDevice.cs ├── XOutput.Emulation.csproj └── Xbox │ ├── IXboxEmulator.cs │ ├── XboxDevice.cs │ ├── XboxFeedbackEvent.cs │ └── XboxInput.cs ├── XOutput.Mapping ├── Controller │ ├── ControllerBase.cs │ ├── ControllerConfig.cs │ ├── Ds4 │ │ ├── Ds4Controller.cs │ │ └── Ds4InputTypes.cs │ ├── IMappedController.cs │ ├── MappedControllers.cs │ └── Xbox │ │ ├── XboxController.cs │ │ └── XboxInputTypes.cs ├── Input │ ├── InputDevice.cs │ ├── InputDeviceFeedbackEvent.cs │ ├── InputDeviceInputChangedEvent.cs │ ├── InputDeviceSourceWithValue.cs │ ├── InputDeviceTargetWithValue.cs │ └── InputDevices.cs ├── Mapper │ ├── ForceFeedbackMapper.cs │ ├── InputMapper.cs │ ├── InputMapperCollection.cs │ └── Mapper.cs ├── README.md └── XOutput.Mapping.csproj ├── XOutput.MappingTests ├── Mapper │ ├── InputMapperCollectionTests.cs │ └── InputMapperTests.cs └── XOutput.MappingTests.csproj ├── XOutput.Server ├── App.cs ├── Configuration │ └── ServerConfig.cs ├── HttpServer.cs ├── Properties │ └── launchSettings.json ├── README.md ├── Rest │ ├── Emulation │ │ ├── EmulatedContollersController.cs │ │ └── EmulatorsController.cs │ ├── Help │ │ └── InfoController.cs │ ├── Input │ │ └── InputsController.cs │ ├── Mapping │ │ └── MappedContollersController.cs │ └── Notifications │ │ └── NotificationController.cs ├── ServerConfiguration.cs ├── Startup.cs ├── Websocket │ ├── CloseFunction.cs │ ├── Ds4 │ │ ├── Ds4InputRequestHandler.cs │ │ └── Ds4WebSocketHandler.cs │ ├── Emulation │ │ ├── EmulatedControllerFeedbackHandler.cs │ │ └── EmulatedControllerFeedbackWebSocketHandler.cs │ ├── IMessageHandler.cs │ ├── IWebSocketHandler.cs │ ├── Input │ │ ├── InputDeviceFeedbackHandler.cs │ │ ├── InputDeviceFeedbackWebSocketHandler.cs │ │ ├── InputDeviceMessageHandler.cs │ │ └── InputDeviceWebSocketHandler.cs │ ├── Mapping │ │ ├── MappedControllerFeedbackHandler.cs │ │ └── MappedControllerFeedbackWebSocketHandler.cs │ ├── SenderFunction.cs │ ├── WebSocketService.cs │ └── Xbox │ │ ├── XboxInputRequestHandler.cs │ │ └── XboxWebSocketHandler.cs ├── XOutput.Server.csproj ├── appsettings.Development.json ├── appsettings.json └── nlog.config ├── XOutput.sln ├── bin ├── README.md ├── error-log.ps1 ├── install.bat ├── install.ps1 ├── uninstall.bat └── uninstall.ps1 ├── docs ├── README.md ├── browser-client.md ├── client-project.md ├── desktop-client.md └── server.md ├── latest.version ├── package.json ├── pnpm-clean.ps1 ├── pnpm-lock.yaml └── pnpm-workspaces.yaml /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "npm" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | - package-ecosystem: "nuget" 12 | directory: "/" 13 | schedule: 14 | interval: "daily" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE 2 | .vs 3 | */*.csproj.user 4 | .idea 5 | 6 | # Output 7 | */bin 8 | */obj 9 | @xoutput/*/dist 10 | @xoutput/*/lib 11 | @xoutput/webapp/webapp 12 | 13 | # Nuget packages 14 | packages 15 | *.nupkg 16 | 17 | # Test result files 18 | TestResults 19 | *Tests/coverage.*.opencover.xml 20 | 21 | # node modules 22 | node_modules 23 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'es5', 3 | tabWidth: 2, 4 | semi: true, 5 | singleQuote: true, 6 | printWidth: 140, 7 | }; 8 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch server", 6 | "type": "coreclr", 7 | "request": "launch", 8 | "preLaunchTask": "dotnet-build", 9 | "program": "${workspaceFolder}/XOutput.Server/bin/Debug/net7.0/XOutput.Server.dll", 10 | "args": [], 11 | "cwd": "${workspaceFolder}/XOutput.Server", 12 | "stopAtEntry": false, 13 | "env": { 14 | "ASPNETCORE_ENVIRONMENT": "Development" 15 | } 16 | }, 17 | { 18 | "name": "Launch app", 19 | "type": "coreclr", 20 | "request": "launch", 21 | "preLaunchTask": "dotnet-build", 22 | "program": "${workspaceFolder}/XOutput.App/bin/Debug/net7.0-windows/XOutput.App.dll", 23 | "args": [], 24 | "cwd": "${workspaceFolder}/XOutput.App", 25 | "stopAtEntry": false 26 | } 27 | ] 28 | } -------------------------------------------------------------------------------- /@xoutput/README.md: -------------------------------------------------------------------------------- 1 | # @xoutput 2 | 3 | - [@xoutput/api](api) 4 | - [@xoutput/client](client) 5 | - [@xoutput/webapp](webapp) 6 | -------------------------------------------------------------------------------- /@xoutput/api/README.md: -------------------------------------------------------------------------------- 1 | # @xoutput/api 2 | 3 | This project contains the TypeScript types for communication between the [server](../../XOutput.Server) and the clients. 4 | -------------------------------------------------------------------------------- /@xoutput/api/eslint.config.cjs: -------------------------------------------------------------------------------- 1 | const react = require('eslint-plugin-react'); 2 | const globals = require('globals'); 3 | 4 | module.exports = [ 5 | { 6 | files: ['src/**/*.{js,jsx,mjs,cjs,ts,tsx}'], 7 | languageOptions: { 8 | globals: { 9 | ...globals.browser, 10 | }, 11 | }, 12 | } 13 | ]; -------------------------------------------------------------------------------- /@xoutput/api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@xoutput/api", 3 | "type": "module", 4 | "version": "4.0.0", 5 | "description": "XOutput TypeScript API", 6 | "main": "lib/index.umd.cjs", 7 | "module": "lib/index.js", 8 | "types": "lib/index.d.ts", 9 | "files": [ 10 | "lib" 11 | ], 12 | "scripts": { 13 | "build": "tsc && vite build", 14 | "watch": "vite build --watch", 15 | "lint": "eslint", 16 | "fix-format": "eslint --fix && prettier --write \"src/**/*.{ts,tsx}\"", 17 | "test": "echo \"No test specified\"" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/csutorasa/XOutput.git" 22 | }, 23 | "keywords": [ 24 | "XOutput" 25 | ], 26 | "homepage": "https://github.com/csutorasa/XOutput#readme", 27 | "url": "https://github.com/csutorasa/XOutput/issues", 28 | "author": "Ármin Csutorás", 29 | "license": "MIT", 30 | "devDependencies": { 31 | "@typescript-eslint/eslint-plugin": "^6.10.0", 32 | "@typescript-eslint/parser": "^6.10.0", 33 | "eslint": "^8.53.0", 34 | "eslint-plugin-react": "^7.33.2", 35 | "prettier": "^3.0.3", 36 | "typescript": "^5.2.2", 37 | "vite": "^4.5.0", 38 | "vite-plugin-dts": "^3.6.3" 39 | }, 40 | "engines": { 41 | "node": ">=20.0.0", 42 | "npm": "use-pnpm-instead", 43 | "pnpm": ">=8.0.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /@xoutput/api/src/common/DeviceTypes.ts: -------------------------------------------------------------------------------- 1 | export type DeviceTypes = 'MicrosoftXbox360' | 'SonyDualShock4'; 2 | -------------------------------------------------------------------------------- /@xoutput/api/src/common/Emulators.ts: -------------------------------------------------------------------------------- 1 | export type Emulators = 'SCPToolkit' | 'ViGEm'; 2 | -------------------------------------------------------------------------------- /@xoutput/api/src/common/SourceTypes.ts: -------------------------------------------------------------------------------- 1 | export type SourceTypes = 'None' | 'Button' | 'Slider' | 'Dpad' | 'AxisX' | 'AxisY' | 'AxisZ'; 2 | -------------------------------------------------------------------------------- /@xoutput/api/src/common/TargetTypes.ts: -------------------------------------------------------------------------------- 1 | export type TargetTypes = 'ForceFeedback'; 2 | -------------------------------------------------------------------------------- /@xoutput/api/src/common/input/InputDeviceApi.ts: -------------------------------------------------------------------------------- 1 | export type InputDeviceApi = 'WindowsApi' | 'DirectInput' | 'RawInput' | 'GamepadApi'; 2 | -------------------------------------------------------------------------------- /@xoutput/api/src/common/input/InputDeviceSource.ts: -------------------------------------------------------------------------------- 1 | import { SourceTypes } from '../SourceTypes'; 2 | 3 | export type InputDeviceSource = { 4 | id: number; 5 | name: string; 6 | type: SourceTypes; 7 | }; 8 | -------------------------------------------------------------------------------- /@xoutput/api/src/common/input/InputDeviceTarget.ts: -------------------------------------------------------------------------------- 1 | import { TargetTypes } from '../TargetTypes'; 2 | 3 | export type InputDeviceTarget = { 4 | id: number; 5 | name: string; 6 | type: TargetTypes; 7 | }; 8 | -------------------------------------------------------------------------------- /@xoutput/api/src/rest/emulation/EmulatedControllerInfo.ts: -------------------------------------------------------------------------------- 1 | import { Emulators } from '../../common/Emulators'; 2 | import { DeviceTypes } from '../../common/DeviceTypes'; 3 | 4 | export type EmulatedControllerInfo = { 5 | id: string; 6 | address?: string; 7 | name: string; 8 | deviceType: DeviceTypes; 9 | emulator: Emulators; 10 | active: boolean; 11 | }; 12 | -------------------------------------------------------------------------------- /@xoutput/api/src/rest/emulation/ListEmulatorsResponse.ts: -------------------------------------------------------------------------------- 1 | import { DeviceTypes } from '../../common/DeviceTypes'; 2 | 3 | export type EmulatorResponse = { 4 | installed: boolean; 5 | SupportedDeviceTypes: DeviceTypes[]; 6 | }; 7 | -------------------------------------------------------------------------------- /@xoutput/api/src/rest/help/Info.ts: -------------------------------------------------------------------------------- 1 | export type InfoResponse = { 2 | version: string; 3 | }; 4 | -------------------------------------------------------------------------------- /@xoutput/api/src/rest/input/InputDeviceInfo.ts: -------------------------------------------------------------------------------- 1 | import { InputDeviceSource } from '../../common/input/InputDeviceSource'; 2 | import { InputDeviceTarget } from '../../common/input/InputDeviceTarget'; 3 | 4 | export type InputDeviceInfo = { 5 | id: string; 6 | name: string; 7 | deviceApi: string; 8 | sources: InputDeviceSource[]; 9 | targets: InputDeviceTarget[]; 10 | }; 11 | -------------------------------------------------------------------------------- /@xoutput/api/src/rest/mapping/CreateMappedControllerRequest.ts: -------------------------------------------------------------------------------- 1 | import { DeviceTypes } from '../../common/DeviceTypes'; 2 | 3 | export type CreateMappedControllerRequest = { 4 | name: string; 5 | deviceType: DeviceTypes; 6 | }; 7 | -------------------------------------------------------------------------------- /@xoutput/api/src/rest/mapping/MappedControllerInfo.ts: -------------------------------------------------------------------------------- 1 | import { Emulators } from '../../common/Emulators'; 2 | import { DeviceTypes } from '../../common/DeviceTypes'; 3 | 4 | export type MappedControllerInfo = { 5 | id: string; 6 | name: string; 7 | deviceType: DeviceTypes; 8 | emulator: Emulators; 9 | active: boolean; 10 | }; 11 | -------------------------------------------------------------------------------- /@xoutput/api/src/rest/notifications/Notification.ts: -------------------------------------------------------------------------------- 1 | export type NotificationLevel = 'Info' | 'Warn' | 'Error'; 2 | 3 | export type Notification = { 4 | id: string; 5 | acknowledged: boolean; 6 | createdAt: string; 7 | key: string; 8 | level: string; 9 | parameters: string[]; 10 | }; 11 | -------------------------------------------------------------------------------- /@xoutput/api/src/websocket/MessageBase.ts: -------------------------------------------------------------------------------- 1 | export type MessageBase = { 2 | type: T; 3 | }; 4 | -------------------------------------------------------------------------------- /@xoutput/api/src/websocket/common/DebugRequest.ts: -------------------------------------------------------------------------------- 1 | import { MessageBase } from '../MessageBase'; 2 | 3 | export const DebugRequestType = 'Ping'; 4 | 5 | export interface DebugRequest extends MessageBase { 6 | data: string; 7 | } 8 | -------------------------------------------------------------------------------- /@xoutput/api/src/websocket/common/PingRequest.ts: -------------------------------------------------------------------------------- 1 | import { MessageBase } from '../MessageBase'; 2 | 3 | export const PingRequestType = 'Ping'; 4 | 5 | export interface PingRequest extends MessageBase { 6 | timestamp: number; 7 | } 8 | -------------------------------------------------------------------------------- /@xoutput/api/src/websocket/common/PongResponse.ts: -------------------------------------------------------------------------------- 1 | import { MessageBase } from '../MessageBase'; 2 | 3 | export const PongResponseType = 'Pong'; 4 | 5 | export interface PongResponse extends MessageBase { 6 | timestamp: number; 7 | } 8 | -------------------------------------------------------------------------------- /@xoutput/api/src/websocket/ds4/Ds4FeedbackResponse.ts: -------------------------------------------------------------------------------- 1 | import { MessageBase } from '../MessageBase'; 2 | 3 | export const Ds4FeedbackResponseType = 'Ds4Feedback'; 4 | 5 | export interface Ds4FeedbackResponse extends MessageBase { 6 | smallForceFeedback: number; 7 | bigForceFeedback: number; 8 | } 9 | -------------------------------------------------------------------------------- /@xoutput/api/src/websocket/ds4/Ds4InputRequest.ts: -------------------------------------------------------------------------------- 1 | import { MessageBase } from '../MessageBase'; 2 | 3 | export const Ds4InputRequestType = 'Ds4Input'; 4 | 5 | export interface Ds4InputRequest extends MessageBase { 6 | circle: boolean; 7 | cross: boolean; 8 | triangle: boolean; 9 | square: boolean; 10 | l1: boolean; 11 | l3: boolean; 12 | r1: boolean; 13 | r3: boolean; 14 | options: boolean; 15 | share: boolean; 16 | ps: boolean; 17 | up: boolean; 18 | down: boolean; 19 | left: boolean; 20 | right: boolean; 21 | lX: number; 22 | lY: number; 23 | rX: number; 24 | rY: number; 25 | l2: number; 26 | r2: number; 27 | } 28 | -------------------------------------------------------------------------------- /@xoutput/api/src/websocket/emulation/ControllerInputResponse.ts: -------------------------------------------------------------------------------- 1 | import { MessageBase } from '../MessageBase'; 2 | import { ControllerSourceValue } from './ControllerSourceValue'; 3 | import { ControllerTargetValue } from './ControllerTargetValue'; 4 | 5 | export const ControllerInputResponseType = 'ControllerInputFeedback'; 6 | 7 | export interface ControllerInputResponse extends MessageBase { 8 | sources: ControllerSourceValue[]; 9 | targets: ControllerTargetValue[]; 10 | } 11 | -------------------------------------------------------------------------------- /@xoutput/api/src/websocket/emulation/ControllerSourceValue.ts: -------------------------------------------------------------------------------- 1 | export type ControllerSourceValue = { 2 | id: string; 3 | value: number; 4 | }; 5 | -------------------------------------------------------------------------------- /@xoutput/api/src/websocket/emulation/ControllerTargetValue.ts: -------------------------------------------------------------------------------- 1 | export type ControllerTargetValue = { 2 | id: string; 3 | value: number; 4 | }; 5 | -------------------------------------------------------------------------------- /@xoutput/api/src/websocket/input/InputDeviceDetailsRequest.ts: -------------------------------------------------------------------------------- 1 | import { MessageBase } from '../MessageBase'; 2 | import { InputDeviceSource } from '../../common/input/InputDeviceSource'; 3 | import { InputDeviceTarget } from '../../common/input/InputDeviceTarget'; 4 | import { InputDeviceApi } from '../../common/input/InputDeviceApi'; 5 | 6 | export const InputDeviceDetailsRequestType = 'InputDeviceDetails'; 7 | 8 | export type InputDeviceDetailsRequest = MessageBase & { 9 | id: string; 10 | name: string; 11 | hardwareId?: string; 12 | sources: InputDeviceSource[]; 13 | targets: InputDeviceTarget[]; 14 | inputApi: InputDeviceApi; 15 | }; 16 | -------------------------------------------------------------------------------- /@xoutput/api/src/websocket/input/InputDeviceFeedbackResponse.ts: -------------------------------------------------------------------------------- 1 | import { MessageBase } from '../MessageBase'; 2 | import { InputDeviceTargetValue } from './InputDeviceTargetValue'; 3 | 4 | export const InputDeviceFeedbackResponseType = 'InputDeviceFeedback'; 5 | 6 | export type InputDeviceFeedbackResponse = MessageBase & { 7 | targets: InputDeviceTargetValue[]; 8 | }; 9 | -------------------------------------------------------------------------------- /@xoutput/api/src/websocket/input/InputDeviceInputRequest.ts: -------------------------------------------------------------------------------- 1 | import { MessageBase } from '../MessageBase'; 2 | import { InputDeviceSourceValue } from './InputDeviceSourceValue'; 3 | 4 | export const InputDeviceInputRequestType = 'InputDeviceInput'; 5 | 6 | export type InputDeviceInputRequest = MessageBase & { 7 | inputs: InputDeviceSourceValue[]; 8 | }; 9 | -------------------------------------------------------------------------------- /@xoutput/api/src/websocket/input/InputDeviceInputResponse.ts: -------------------------------------------------------------------------------- 1 | import { MessageBase } from '../MessageBase'; 2 | import { InputDeviceSourceValue } from './InputDeviceSourceValue'; 3 | import { InputDeviceTargetValue } from './InputDeviceTargetValue'; 4 | 5 | export const InputDeviceInputResponseType = 'InputDeviceInputFeedback'; 6 | 7 | export type InputDeviceInputResponse = MessageBase & { 8 | sources: InputDeviceSourceValue[]; 9 | targets: InputDeviceTargetValue[]; 10 | }; 11 | -------------------------------------------------------------------------------- /@xoutput/api/src/websocket/input/InputDeviceSourceValue.ts: -------------------------------------------------------------------------------- 1 | export type InputDeviceSourceValue = { 2 | id: number; 3 | value: number; 4 | }; 5 | -------------------------------------------------------------------------------- /@xoutput/api/src/websocket/input/InputDeviceTargetValue.ts: -------------------------------------------------------------------------------- 1 | export type InputDeviceTargetValue = { 2 | id: number; 3 | value: number; 4 | }; 5 | -------------------------------------------------------------------------------- /@xoutput/api/src/websocket/xbox/XboxFeedbackResponse.ts: -------------------------------------------------------------------------------- 1 | import { MessageBase } from '../MessageBase'; 2 | 3 | export const XboxFeedbackResponseType = 'XboxFeedback'; 4 | 5 | export interface XboxFeedbackResponse extends MessageBase { 6 | smallForceFeedback: number; 7 | bigForceFeedback: number; 8 | ledNumber: number; 9 | } 10 | -------------------------------------------------------------------------------- /@xoutput/api/src/websocket/xbox/XboxInputRequest.ts: -------------------------------------------------------------------------------- 1 | import { MessageBase } from '../MessageBase'; 2 | 3 | export const XboxInputRequestType = 'XboxInput'; 4 | 5 | export interface XboxInputRequest extends MessageBase { 6 | a: boolean; 7 | b: boolean; 8 | x: boolean; 9 | y: boolean; 10 | l1: boolean; 11 | l3: boolean; 12 | r1: boolean; 13 | r3: boolean; 14 | start: boolean; 15 | back: boolean; 16 | home: boolean; 17 | up: boolean; 18 | down: boolean; 19 | left: boolean; 20 | right: boolean; 21 | lX: number; 22 | lY: number; 23 | rX: number; 24 | rY: number; 25 | l2: number; 26 | r2: number; 27 | } 28 | -------------------------------------------------------------------------------- /@xoutput/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2021", 4 | "module": "ES2020", 5 | "lib": ["ES2021"], 6 | "moduleResolution": "node", 7 | "noEmit": true, 8 | 9 | "strict": true, 10 | "strictNullChecks": false, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "forceConsistentCasingInFileNames": true 15 | }, 16 | "exclude": ["./lib", "node_modules"], 17 | "include": ["src"] 18 | } 19 | -------------------------------------------------------------------------------- /@xoutput/api/vite.config.js: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { defineConfig } from 'vite'; 3 | import dts from 'vite-plugin-dts'; 4 | 5 | export default defineConfig({ 6 | build: { 7 | outDir: resolve(__dirname, 'lib'), 8 | lib: { 9 | entry: resolve(__dirname, 'src/index.ts'), 10 | name: '@xoutput/api', 11 | fileName: 'index', 12 | }, 13 | rollupOptions: { 14 | output: { 15 | sourcemapExcludeSources: true, 16 | }, 17 | }, 18 | sourcemap: true, 19 | target: 'es2021', 20 | minify: false, 21 | }, 22 | plugins: [dts({ 23 | rollupTypes: true, 24 | })], 25 | }); -------------------------------------------------------------------------------- /@xoutput/client/README.md: -------------------------------------------------------------------------------- 1 | # XOutput client 2 | 3 | This project contains the TypeScript client logic for communication between the [server](../../XOutput.Server) and the clients. 4 | It uses the [shared api](../api). 5 | -------------------------------------------------------------------------------- /@xoutput/client/eslint.config.cjs: -------------------------------------------------------------------------------- 1 | const react = require('eslint-plugin-react'); 2 | const globals = require('globals'); 3 | 4 | module.exports = [ 5 | { 6 | files: ['src/**/*.{js,jsx,mjs,cjs,ts,tsx}'], 7 | languageOptions: { 8 | globals: { 9 | ...globals.browser, 10 | }, 11 | }, 12 | } 13 | ]; -------------------------------------------------------------------------------- /@xoutput/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@xoutput/client", 3 | "type": "module", 4 | "version": "4.0.0", 5 | "description": "XOutput TypeScript client", 6 | "main": "lib/index.umd.cjs", 7 | "browser": "lib/index.js", 8 | "types": "lib/index.d.ts", 9 | "files": [ 10 | "lib" 11 | ], 12 | "scripts": { 13 | "build": "tsc && vite build", 14 | "watch": "vite build --watch", 15 | "lint": "eslint", 16 | "fix-format": "eslint --fix && prettier --write \"src/**/*.{ts,tsx}\"", 17 | "test": "echo \"No test specified\"" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/csutorasa/XOutput.git" 22 | }, 23 | "keywords": [ 24 | "XOutput" 25 | ], 26 | "homepage": "https://github.com/csutorasa/XOutput#readme", 27 | "url": "https://github.com/csutorasa/XOutput/issues", 28 | "author": "Ármin Csutorás", 29 | "license": "MIT", 30 | "dependencies": { 31 | "@xoutput/api": "workspace:*" 32 | }, 33 | "devDependencies": { 34 | "@typescript-eslint/eslint-plugin": "^6.10.0", 35 | "@typescript-eslint/parser": "^6.10.0", 36 | "eslint": "^8.53.0", 37 | "eslint-plugin-react": "^7.33.2", 38 | "prettier": "^3.0.3", 39 | "typescript": "^5.2.2", 40 | "vite": "^4.5.0", 41 | "vite-plugin-dts": "^3.6.3" 42 | }, 43 | "engines": { 44 | "node": ">=20.0.0", 45 | "npm": "use-pnpm-instead", 46 | "pnpm": ">=8.0.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /@xoutput/client/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './rest/http'; 2 | export * from './rest/emulation/EmulatedControllersClient'; 3 | export * from './rest/emulation/EmulationClient'; 4 | export * from './rest/help/InfoClient'; 5 | export * from './rest/input/InputsClient'; 6 | export * from './rest/mapping/MappedControllersClient'; 7 | export * from './rest/notifications/NotificationClient'; 8 | 9 | export * from './websocket/websocket'; 10 | export * from './websocket/input/InputDeviceClient'; 11 | export * from './websocket/input/InputDeviceFeedbackClient'; 12 | -------------------------------------------------------------------------------- /@xoutput/client/src/rest/emulation/EmulatedControllersClient.ts: -------------------------------------------------------------------------------- 1 | import { EmulatedControllerInfo } from '@xoutput/api'; 2 | import { http } from '../http'; 3 | 4 | export const emulatedControllersClient = { 5 | getControllers() { 6 | return http.get('/emulated/controllers'); 7 | }, 8 | deleteController(id: string) { 9 | return http.delete(`/emulated/controllers/${id}`); 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /@xoutput/client/src/rest/emulation/EmulationClient.ts: -------------------------------------------------------------------------------- 1 | import { EmulatorResponse } from '@xoutput/api'; 2 | import { http } from '../http'; 3 | 4 | export const emulationClient = { 5 | getInfo(): Promise { 6 | return http.get('/emulators'); 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /@xoutput/client/src/rest/help/InfoClient.ts: -------------------------------------------------------------------------------- 1 | import { InfoResponse } from '@xoutput/api'; 2 | import { http } from '../http'; 3 | 4 | export const infoClient = { 5 | getInfo(): Promise { 6 | return http.get('/info'); 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /@xoutput/client/src/rest/input/InputsClient.ts: -------------------------------------------------------------------------------- 1 | import { InputDeviceInfo } from '@xoutput/api'; 2 | import { http } from '../http'; 3 | 4 | export const inputsClient = { 5 | getInputs(): Promise { 6 | return http.get('/inputs'); 7 | }, 8 | getInput(id: string): Promise { 9 | return http.get(`/inputs/${id}`); 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /@xoutput/client/src/rest/mapping/MappedControllersClient.ts: -------------------------------------------------------------------------------- 1 | import { CreateMappedControllerRequest, MappedControllerInfo } from '@xoutput/api'; 2 | import { http } from '../http'; 3 | 4 | export const mappedControllersClient = { 5 | getControllers() { 6 | return http.get('/mapped/controllers'); 7 | }, 8 | createController(request: CreateMappedControllerRequest) { 9 | return http.put('/mapped/controllers', request); 10 | }, 11 | startController(id: string) { 12 | return http.put(`/mapped/controllers/${id}/active`); 13 | }, 14 | stopController(id: string) { 15 | return http.delete(`/mapped/controllers/${id}/active`); 16 | }, 17 | deleteController(id: string) { 18 | return http.delete(`/mapped/controllers/${id}`); 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /@xoutput/client/src/rest/notifications/NotificationClient.ts: -------------------------------------------------------------------------------- 1 | import { Notification } from '@xoutput/api'; 2 | import { http } from '../http'; 3 | 4 | export const notificationClient = { 5 | getNotifications(): Promise { 6 | return http.get('/notifications'); 7 | }, 8 | acknowledge(id: string): Promise { 9 | return http.put(`/notifications/${id}/acknowledge`); 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /@xoutput/client/src/websocket/ds4/Ds4DeviceClient.ts: -------------------------------------------------------------------------------- 1 | import { Ds4InputRequest, Emulators, Ds4FeedbackResponseType, Ds4FeedbackResponse } from '@xoutput/api'; 2 | import { websocket, WebSocketSession } from '../websocket'; 3 | 4 | export type Ds4InputMessageSender = { 5 | sendInput: (input: Ds4InputRequest) => void; 6 | }; 7 | 8 | export const ds4DeviceClient = { 9 | connect(emulator: Emulators, onFeedback: (feedback: Ds4FeedbackResponse) => void): Promise> { 10 | return websocket 11 | .connect(`SonyDualShock4/${emulator}`, (data) => { 12 | if (data.type === Ds4FeedbackResponseType) { 13 | onFeedback(data as Ds4FeedbackResponse); 14 | } 15 | }) 16 | .then( 17 | (session) => 18 | ({ 19 | ...session, 20 | sendInput: (input: Ds4InputRequest) => session.sendMessage(input), 21 | }) as WebSocketSession 22 | ); 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /@xoutput/client/src/websocket/emulation/EmulatedControllerFeedbackClient.ts: -------------------------------------------------------------------------------- 1 | import { ControllerInputResponse, ControllerInputResponseType } from '@xoutput/api'; 2 | import { websocket, WebSocketSession } from '../websocket'; 3 | 4 | export const inputDeviceClient = { 5 | connect(id: string, onFeedback: (feedback: ControllerInputResponse) => void): Promise { 6 | return websocket.connect(`EmulatedController/${id}`, (data) => { 7 | if (data.type === ControllerInputResponseType) { 8 | onFeedback(data as ControllerInputResponse); 9 | } 10 | }); 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /@xoutput/client/src/websocket/input/InputDeviceClient.ts: -------------------------------------------------------------------------------- 1 | import { 2 | InputDeviceDetailsRequest, 3 | InputDeviceFeedbackResponse, 4 | InputDeviceFeedbackResponseType, 5 | InputDeviceInputRequest, 6 | } from '@xoutput/api'; 7 | import { websocket, WebSocketSession } from '../websocket'; 8 | 9 | export type InputDeviceMessageSender = { 10 | sendInput: (input: InputDeviceInputRequest) => void; 11 | }; 12 | 13 | export const inputDeviceClient = { 14 | connect( 15 | details: InputDeviceDetailsRequest, 16 | onFeedback: (feedback: InputDeviceFeedbackResponse) => void 17 | ): Promise> { 18 | return websocket 19 | .connect(`InputDevice`, (data) => { 20 | if (data.type === InputDeviceFeedbackResponseType) { 21 | onFeedback(data as InputDeviceFeedbackResponse); 22 | } 23 | }) 24 | .then((session) => { 25 | session.sendMessage(details); 26 | return session; 27 | }) 28 | .then( 29 | (session) => { 30 | const enhancedSession = session as WebSocketSession; 31 | enhancedSession.sendInput = (input: InputDeviceInputRequest) => session.sendMessage(input); 32 | return enhancedSession; 33 | } 34 | ); 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /@xoutput/client/src/websocket/input/InputDeviceFeedbackClient.ts: -------------------------------------------------------------------------------- 1 | import { InputDeviceInputResponse, InputDeviceInputResponseType } from '@xoutput/api'; 2 | import { websocket, WebSocketSession } from '../websocket'; 3 | 4 | export const inputDeviceFeedbackClient = { 5 | connect(id: string, onFeedback: (feedback: InputDeviceInputResponse) => void): Promise { 6 | return websocket.connect(`InputDevice/${id}`, (data) => { 7 | if (data.type === InputDeviceInputResponseType) { 8 | onFeedback(data as InputDeviceInputResponse); 9 | } 10 | }); 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /@xoutput/client/src/websocket/mapping/MappedControllerFeedbackClient.ts: -------------------------------------------------------------------------------- 1 | import { ControllerInputResponse, ControllerInputResponseType } from '@xoutput/api'; 2 | import { websocket, WebSocketSession } from '../websocket'; 3 | 4 | export const inputDeviceClient = { 5 | connect(id: string, onFeedback: (feedback: ControllerInputResponse) => void): Promise { 6 | return websocket.connect(`MappedController/${id}`, (data) => { 7 | if (data.type === ControllerInputResponseType) { 8 | onFeedback(data as ControllerInputResponse); 9 | } 10 | }); 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /@xoutput/client/src/websocket/xbox/XboxDeviceClient.ts: -------------------------------------------------------------------------------- 1 | import { XboxInputRequest, Emulators, XboxFeedbackResponseType, XboxFeedbackResponse } from '@xoutput/api'; 2 | import { websocket, WebSocketSession } from '../websocket'; 3 | 4 | export type XboxInputMessageSender = { 5 | sendInput: (input: XboxInputRequest) => void; 6 | }; 7 | 8 | export const xboxDeviceClient = { 9 | connect(emulator: Emulators, onFeedback: (feedback: XboxFeedbackResponse) => void): Promise> { 10 | return websocket 11 | .connect(`MicrosoftXbox360/${emulator}`, (data) => { 12 | if (data.type === XboxFeedbackResponseType) { 13 | onFeedback(data as XboxFeedbackResponse); 14 | } 15 | }) 16 | .then( 17 | (session) => { 18 | const enhancedSession = session as WebSocketSession; 19 | enhancedSession.sendInput = (input: XboxInputRequest) => session.sendMessage(input); 20 | return enhancedSession; 21 | } 22 | ); 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /@xoutput/client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2021", 4 | "module": "ES2020", 5 | "lib": ["ES2021", "DOM"], 6 | "moduleResolution": "node", 7 | "noEmit": true, 8 | 9 | "strict": true, 10 | "strictNullChecks": false, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "forceConsistentCasingInFileNames": true 15 | }, 16 | "exclude": ["./lib", "node_modules"], 17 | "include": ["src"] 18 | } 19 | -------------------------------------------------------------------------------- /@xoutput/client/vite.config.js: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { defineConfig } from 'vite'; 3 | import dts from 'vite-plugin-dts'; 4 | 5 | export default defineConfig({ 6 | build: { 7 | outDir: resolve(__dirname, 'lib'), 8 | lib: { 9 | entry: resolve(__dirname, 'src/index.ts'), 10 | name: '@xoutput/client', 11 | fileName: 'index', 12 | }, 13 | rollupOptions: { 14 | output: { 15 | sourcemapExcludeSources: true, 16 | }, 17 | }, 18 | sourcemap: true, 19 | target: 'es2021', 20 | minify: false, 21 | }, 22 | plugins: [dts({ 23 | rollupTypes: true, 24 | })], 25 | }); -------------------------------------------------------------------------------- /@xoutput/webapp/README.md: -------------------------------------------------------------------------------- 1 | # @xoutput/webapp 2 | 3 | This is the main web application for the [XOutput server](../../XOutput.Server). 4 | 5 | The application enables configuring the server and creating controllers. 6 | -------------------------------------------------------------------------------- /@xoutput/webapp/eslint.config.js: -------------------------------------------------------------------------------- 1 | const react = require('eslint-plugin-react'); 2 | const globals = require('globals'); 3 | 4 | module.exports = [ 5 | { 6 | files: ['src/**/*.{js,jsx,mjs,cjs,ts,tsx}'], 7 | plugins: { 8 | react, 9 | }, 10 | languageOptions: { 11 | parserOptions: { 12 | ecmaFeatures: { 13 | jsx: true, 14 | }, 15 | }, 16 | globals: { 17 | ...globals.browser, 18 | }, 19 | }, 20 | rules: { 21 | "no-restricted-imports": [ 22 | "error", 23 | { 24 | "paths": ["@mui/material", "@mui/icons-material"] 25 | } 26 | ], 27 | "react/prop-types": "off", 28 | }, 29 | }, 30 | ]; -------------------------------------------------------------------------------- /@xoutput/webapp/src/events/eventholder.ts: -------------------------------------------------------------------------------- 1 | import { UIInputFlow } from './base'; 2 | import { MouseEvent, Touch } from 'react'; 3 | 4 | export class EventHolder { 5 | private mouseFlow: UIInputFlow; 6 | private touchFlows: { [key: number]: UIInputFlow } = {}; 7 | 8 | mouseAdd(flow: UIInputFlow, event: MouseEvent) { 9 | if (this.mouseFlow) { 10 | this.mouseFlow.end(); 11 | } 12 | this.mouseFlow = flow; 13 | flow.start(event); 14 | } 15 | 16 | mouseMove(event: MouseEvent) { 17 | if (this.mouseFlow) { 18 | this.mouseFlow.move(event); 19 | } 20 | } 21 | 22 | mouseEnd() { 23 | if (this.mouseFlow) { 24 | this.mouseFlow.end(); 25 | this.mouseFlow = null; 26 | } 27 | } 28 | 29 | touchAdd(flow: UIInputFlow, event: Touch) { 30 | const identifier: number = event.identifier; 31 | this.touchFlows[identifier] = flow; 32 | flow.start(event); 33 | } 34 | 35 | touchMove(event: Touch) { 36 | const identifier: number = event.identifier; 37 | if (identifier in this.touchFlows) { 38 | const flow = this.touchFlows[identifier]; 39 | flow.move(event); 40 | } 41 | } 42 | 43 | touchEnd(event: Touch) { 44 | const identifier: number = event.identifier; 45 | const flow = this.touchFlows[identifier]; 46 | if (flow) { 47 | flow.end(); 48 | delete this.touchFlows[identifier]; 49 | } 50 | } 51 | 52 | touchEndAll() { 53 | for (const key in Object.keys(this.touchFlows)) { 54 | const flow = this.touchFlows[key]; 55 | flow.end(); 56 | delete this.touchFlows[key]; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csutorasa/XOutput/6a0e0988fee7efb73d6096a7e74bbd11b9181c0e/@xoutput/webapp/src/favicon.ico -------------------------------------------------------------------------------- /@xoutput/webapp/src/files.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.json' { 2 | const content: unknown; 3 | export default content; 4 | } 5 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/gamepad/GamepadService.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter, EventListener } from '../utils/EventEmitter'; 2 | import { GamepadReader } from './GamepadReader'; 3 | 4 | class GamepadService { 5 | private gamepads: Record = {}; 6 | private changeEmitter: EventEmitter = new EventEmitter(); 7 | 8 | isAvailable(): boolean { 9 | return typeof navigator.getGamepads === 'function'; 10 | } 11 | 12 | start() { 13 | if (!this.isAvailable()) { 14 | return; 15 | } 16 | window.addEventListener('gamepadconnected', (e: GamepadEvent) => { 17 | this.gamepads[e.gamepad.id] = new GamepadReader(e.gamepad); 18 | this.changeEmitter.emit(this.getGamepads()); 19 | }); 20 | window.addEventListener('gamepaddisconnected', (e: GamepadEvent) => { 21 | delete this.gamepads[e.gamepad.id]; 22 | this.changeEmitter.emit(this.getGamepads()); 23 | }); 24 | } 25 | 26 | addChangeListener(listener: EventListener) { 27 | this.changeEmitter.addListener(listener); 28 | } 29 | 30 | removeChangeListener(listener: EventListener) { 31 | this.changeEmitter.removeListener(listener); 32 | } 33 | 34 | getGamepads(): GamepadReader[] { 35 | return Object.values(this.gamepads).filter((gamepad) => gamepad.gamepad.connected); 36 | } 37 | 38 | getGamepad(id: string): GamepadReader { 39 | return this.gamepads[id]; 40 | } 41 | } 42 | 43 | export const gamepadService = new GamepadService(); 44 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | XOutput 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/index.tsx: -------------------------------------------------------------------------------- 1 | import './index.scss'; 2 | 3 | import React from 'react'; 4 | import { createRoot } from 'react-dom/client'; 5 | import { RootElement } from './ui/RootElement'; 6 | import { gamepadService } from './gamepad/GamepadService'; 7 | import 'typeface-roboto'; 8 | 9 | gamepadService.start(); 10 | 11 | function disableResize() { 12 | const meta = document.createElement('meta'); 13 | meta.name = 'viewport'; 14 | meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'; 15 | document.getElementsByTagName('head')[0].appendChild(meta); 16 | } 17 | 18 | disableResize(); 19 | const element = document.getElementById('root'); 20 | const root = createRoot(element); 21 | root.render(); 22 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/queries/useEmulatedControllersQuery.ts: -------------------------------------------------------------------------------- 1 | import { useQuery, UseQueryResult } from 'react-query'; 2 | import { emulatedControllersClient } from '@xoutput/client'; 3 | import { EmulatedControllerInfo } from '@xoutput/api'; 4 | 5 | export const useEmulatedControllersQuery = (): UseQueryResult => { 6 | return useQuery('get-emulated-controllers', () => emulatedControllersClient.getControllers()); 7 | }; 8 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/queries/useInputsQuery.ts: -------------------------------------------------------------------------------- 1 | import { useQuery, UseQueryResult } from 'react-query'; 2 | import { inputsClient } from '@xoutput/client'; 3 | import { InputDeviceInfo } from '@xoutput/api'; 4 | 5 | export const useInputQuery = (id: string): UseQueryResult => { 6 | return useQuery('get-input', () => inputsClient.getInput(id)); 7 | }; 8 | 9 | export const useInputsQuery = (): UseQueryResult => { 10 | return useQuery('get-inputs', () => inputsClient.getInputs()); 11 | }; 12 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/queries/useMappedControllersQuery.ts: -------------------------------------------------------------------------------- 1 | import { useQuery, UseQueryResult } from 'react-query'; 2 | import { mappedControllersClient } from '@xoutput/client'; 3 | import { MappedControllerInfo } from '@xoutput/api'; 4 | 5 | export const useMappedControllersQuery = (): UseQueryResult => { 6 | return useQuery('get-mapped-controllers', () => mappedControllersClient.getControllers()); 7 | }; 8 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/queries/useNotificationsQuery.ts: -------------------------------------------------------------------------------- 1 | import { useQuery, useQueryClient, UseQueryResult } from 'react-query'; 2 | import { notificationClient } from '@xoutput/client'; 3 | import { Notification } from '@xoutput/api'; 4 | 5 | export const useNotificationsQuery = (): UseQueryResult => { 6 | return useQuery('get-notifications', () => notificationClient.getNotifications()); 7 | }; 8 | 9 | export const resetNotificationsQuery = (): Promise => { 10 | const client = useQueryClient(); 11 | return client.invalidateQueries('get-notifications'); 12 | }; 13 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/translation/English.json: -------------------------------------------------------------------------------- 1 | { 2 | "ActiveControllers": "Active controllers", 3 | "OnlineDevices": "Online devices", 4 | "Notifications": "Notifications", 5 | "Share": "Share", 6 | "Fullscreen": "Fullscreen", 7 | "MicrosoftXbox360": "Microsoft Xbox 360", 8 | "SonyDualShock4": "Sony Dualshock 4", 9 | "WebDevice": "Web device", 10 | "LocalDevice": "Local device", 11 | "InputDevices": "Input devices", 12 | "Mouse": "Mouse", 13 | "Keyboard": "Keyboard", 14 | "DPads": "DPads", 15 | "DPad": "DPad", 16 | "Axes": "Axes", 17 | "Buttons": "Buttons", 18 | "Sliders": "Sliders", 19 | "ForceFeedbacks": "Force feedbacks", 20 | "Test": "Test", 21 | "BigMotor": "Big motor", 22 | "SmallMotor": "Small motor", 23 | "input.name": "Name", 24 | "input.id": "XOutput ID", 25 | "input.hardwareid": "Hardware ID", 26 | "input.hidguardian": "Enable HidGuardian", 27 | "input.forcefeedback.notimplemented": "Force feedback driver is missing or corrupt for {0}", 28 | "input.instance.duplication": "More devices share that same ID of {0}", 29 | "hidguardian.registry.noaccess": "No access to write registry for HidGuardian (try running the application as admin)", 30 | "version.needsupgrade": "There is a new version available {0}", 31 | "version.error": "Failed to get the latest version" 32 | } 33 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/translation/Hungarian.json: -------------------------------------------------------------------------------- 1 | { 2 | "ActiveControllers": "Aktív vezérlők", 3 | "OnlineDevices": "Online eszközök", 4 | "Notifications": "Értesítések", 5 | "Share": "Megosztás", 6 | "Fullscreen": "Teljes képernyő", 7 | "MicrosoftXbox360": "Microsoft Xbox 360", 8 | "SonyDualShock4": "Sony Dualshock 4", 9 | "WebDevice": "Web eszköz", 10 | "LocalDevice": "Helyi eszköz", 11 | "InputDevices": "Bemeneti eszközök", 12 | "Mouse": "Egér", 13 | "Keyboard": "Bilentyűzet", 14 | "DPads": "DPadok", 15 | "DPad": "DPad", 16 | "Axes": "Tengelyek", 17 | "Buttons": "Gombok", 18 | "Sliders": "Csúszkák", 19 | "ForceFeedbacks": "Rezgő motorok", 20 | "Test": "Teszt", 21 | "BigMotor": "Nagy motor", 22 | "SmallMotor": "Kis motor", 23 | "input.name": "Név", 24 | "input.id": "XOutput azonosító", 25 | "input.hardwareid": "Hardware azonosító", 26 | "input.hidguardian": "HidGuardian engedélyezése", 27 | "input.forcefeedback.notimplemented": "A rezgő funkció drivere hiányzik vagy korrupt a {0} eszköznél.", 28 | "input.instance.duplication": "Több eszköz is használja a {0} azonosítót", 29 | "hidguardian.registry.noaccess": "Nincs jogosultsága a HidGuardian beállításait a registry-be írni (próbálja adminisztátorként futtatni az alkalmazást)", 30 | "version.needsupgrade": "Újabb verzió elérhető: {0}", 31 | "version.error": "A legfrissebb verzió lekérdezése sikertelen" 32 | } 33 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/translation/Translation.ts: -------------------------------------------------------------------------------- 1 | import * as hungarian from './Hungarian.json'; 2 | import * as english from './English.json'; 3 | 4 | class TranslationService { 5 | private data: { [key: string]: string }; 6 | 7 | constructor() { 8 | switch (navigator.language.slice(0, 2)) { 9 | case 'hu': 10 | this.setLanguage('Hungarian'); 11 | break; 12 | default: 13 | this.setLanguage('English'); 14 | break; 15 | } 16 | } 17 | 18 | setLanguage(language: string) { 19 | if (language === 'Hungarian') { 20 | this.data = hungarian.default as { [key: string]: string }; 21 | } else { 22 | this.data = english.default as { [key: string]: string }; 23 | } 24 | } 25 | 26 | translate(key: string, parameters: string[] = []): string { 27 | const text = this.data[key] || key; 28 | return parameters.reduce((previous, current, i: number) => { 29 | return previous.replace(`{${i}}`, current); 30 | }, text); 31 | } 32 | } 33 | 34 | export const Translation = new TranslationService(); 35 | export default Translation; 36 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/ui/MainMenuListItem.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import ListItemButton from '@mui/material/ListItemButton'; 4 | 5 | export type MainMenuListItemProps = { 6 | onClick: () => void; 7 | path: string; 8 | children: ReactNode; 9 | }; 10 | 11 | const MainMenuListItemComponent = ({ path, onClick, children }: MainMenuListItemProps) => { 12 | return ( 13 | (onClick ? onClick() : null)} selected={path === location.pathname}> 14 | {children} 15 | 16 | ); 17 | }; 18 | 19 | export const MainMenuListItem = MainMenuListItemComponent; 20 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/ui/RootElement.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import green from '@mui/material/colors/green'; 3 | import { HashRouter } from 'react-router-dom'; 4 | import { Router } from './Router'; 5 | import { createTheme, ThemeProvider } from '@mui/material/styles'; 6 | import { QueryClient, QueryClientProvider } from 'react-query'; 7 | 8 | const productionTheme = createTheme({}); 9 | 10 | const developmentTheme = createTheme({ 11 | palette: { 12 | primary: green, 13 | }, 14 | }); 15 | 16 | const theme = process.env.NODE_ENV !== 'production' ? developmentTheme : productionTheme; 17 | 18 | const queryClient = new QueryClient(); 19 | 20 | export const RootElement = () => ( 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | export default RootElement; 30 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/ui/TranslatedText.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Translation } from '../translation/Translation'; 3 | 4 | export type TextTranslatedTextProps = { 5 | text: string; 6 | }; 7 | 8 | export type ChildrenTranslatedTextProps = { 9 | children: string; 10 | }; 11 | 12 | export type TranslatedTextProps = TextTranslatedTextProps | ChildrenTranslatedTextProps; 13 | 14 | function hasText(props: TranslatedTextProps): props is TextTranslatedTextProps { 15 | return !!(props as TextTranslatedTextProps).text; 16 | } 17 | 18 | export const TranslatedText = (props: TranslatedTextProps) => { 19 | let translationKey: string; 20 | if (hasText(props)) { 21 | translationKey = props.text; 22 | } else { 23 | translationKey = props.children; 24 | } 25 | const translatedText = Translation.translate(translationKey); 26 | return <>{translatedText}; 27 | }; 28 | 29 | export const TT = TranslatedText; 30 | export default TT; 31 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/ui/components/Asnyc.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactElement } from 'react'; 2 | import Typography from '@mui/material/Typography'; 3 | import CircularProgress from '@mui/material/CircularProgress'; 4 | 5 | export type AsyncProps = { 6 | isLoading: boolean; 7 | isSuccess: boolean; 8 | error: unknown; 9 | children: () => ReactElement; 10 | size?: number | string; 11 | }; 12 | 13 | export const Async = ({ size, isLoading, isSuccess, error, children }: AsyncProps) => { 14 | if (isLoading) { 15 | return ( 16 |
17 | 18 |
19 | ); 20 | } 21 | if (isSuccess) { 22 | return children(); 23 | } 24 | 25 | return {error?.toString()}; 26 | }; 27 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/ui/components/Placeholder.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const PlaceHolder = () =>
; 4 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/ui/emulation/EmulatedControllers.tsx: -------------------------------------------------------------------------------- 1 | import Card from '@mui/material/Card'; 2 | import React from 'react'; 3 | import { useEmulatedControllersQuery } from '../../queries/useEmulatedControllersQuery'; 4 | import { Async } from '../components/Asnyc'; 5 | 6 | const EmulatedControllersComponent = () => { 7 | const { data: controllers, isLoading, isSuccess, error } = useEmulatedControllersQuery(); 8 | 9 | return ( 10 | 11 | {() => ( 12 | <> 13 | {controllers.map((c) => ( 14 | 15 |
{c.name}
16 |
17 | ))} 18 | 19 | )} 20 |
21 | ); 22 | }; 23 | 24 | export const EmulatedControllers = EmulatedControllersComponent; 25 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/ui/emulation/common.ts: -------------------------------------------------------------------------------- 1 | import { EventHolder } from '../../events/eventholder'; 2 | import { WebSocketSession } from '@xoutput/client'; 3 | 4 | export interface CommonProps { 5 | eventHolder: EventHolder; 6 | websocket: WebSocketSession; 7 | } 8 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/ui/emulation/square.tsx: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties, ReactNode } from 'react'; 2 | 3 | export type SquareProp = { 4 | style: CSSProperties; 5 | children: ReactNode; 6 | }; 7 | 8 | export const Square = ({ style, children }: SquareProp) => { 9 | return ( 10 |
11 | {children} 12 |
13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/ui/input/GamepadValue.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | 3 | export type GamepadValueProps = { 4 | index: number; 5 | type: 'button' | 'axis' | 'slider' | 'dpad'; 6 | valueGetter: () => number; 7 | }; 8 | 9 | const GamepadValueComponent = ({ index, type, valueGetter }: GamepadValueProps) => { 10 | const [value, setValue] = useState(0); 11 | 12 | useEffect(() => { 13 | const intervalId = setInterval(() => { 14 | setValue(valueGetter()); 15 | }, 10); 16 | return () => { 17 | clearInterval(intervalId); 18 | }; 19 | }, []); 20 | 21 | return ( 22 |
23 | {type === 'button' ? 'B' : 'A'} 24 | {index} 25 |
{value.toFixed(2)} 26 |
27 | ); 28 | }; 29 | 30 | export const GamepadValue = GamepadValueComponent; 31 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/ui/input/InputDevices.tsx: -------------------------------------------------------------------------------- 1 | import Card from '@mui/material/Card'; 2 | import Typography from '@mui/material/Typography'; 3 | import React from 'react'; 4 | import { Link } from 'react-router-dom'; 5 | import { useInputsQuery } from '../../queries/useInputsQuery'; 6 | import { Async } from '../components/Asnyc'; 7 | 8 | const InputDevicesComponent = () => { 9 | const { data: inputs, isLoading, isSuccess, error } = useInputsQuery(); 10 | 11 | return ( 12 | 13 | {() => ( 14 | <> 15 | {inputs.map((i) => ( 16 | 17 | 18 | 19 | {i.name} ({i.id}) 20 | 21 | 22 |
{i.deviceApi}
23 | {i.sources.filter((s) => s.type.startsWith('Axis')).length} axes {i.sources.filter((s) => s.type === 'Button').length} buttons 24 |
25 | ))} 26 | 27 | )} 28 |
29 | ); 30 | }; 31 | 32 | export const InputDevices = InputDevicesComponent; 33 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/ui/input/InputReader.tsx: -------------------------------------------------------------------------------- 1 | import Typography from '@mui/material/Typography'; 2 | import Alert from '@mui/material/Alert'; 3 | import React, { useEffect, useState } from 'react'; 4 | import { gamepadService } from '../../gamepad/GamepadService'; 5 | import Translation from '../../translation/Translation'; 6 | import { GamepadReader } from '../../gamepad/GamepadReader'; 7 | import { Gamepad } from './Gamepad'; 8 | 9 | const InputReaderComponent = () => { 10 | if (!gamepadService.isAvailable()) { 11 | return ( 12 | <> 13 | The browser is not compatible with the Gampad API. 14 |
15 | Please check compatiblity{' '} 16 | here. 17 | 18 | ); 19 | } 20 | const [gamepads, setGamepads] = useState(gamepadService.getGamepads()); 21 | 22 | useEffect(() => { 23 | const listener = (gamepads: GamepadReader[]) => { 24 | setGamepads(gamepads); 25 | }; 26 | gamepadService.addChangeListener(listener); 27 | return () => { 28 | gamepadService.removeChangeListener(listener); 29 | }; 30 | }); 31 | 32 | return ( 33 | <> 34 | {Translation.translate('InputReader')} 35 | If you cannot see your controller, press a button or move an axis. 36 | {gamepads.map((gamepad) => ( 37 | 38 | ))} 39 | 40 | ); 41 | }; 42 | 43 | export const InputReader = InputReaderComponent; 44 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/ui/mapping/MappedControllers.tsx: -------------------------------------------------------------------------------- 1 | import Card from '@mui/material/Card'; 2 | import React from 'react'; 3 | import { useMappedControllersQuery } from '../../queries/useMappedControllersQuery'; 4 | import { Async } from '../components/Asnyc'; 5 | 6 | const MappedControllersComponent = () => { 7 | const { data: controllers, isLoading, isSuccess, error } = useMappedControllersQuery(); 8 | 9 | return ( 10 | 11 | {() => ( 12 | <> 13 | {controllers.map((c) => ( 14 | 15 |
{c.name}
16 |
17 | ))} 18 | 19 | )} 20 |
21 | ); 22 | }; 23 | 24 | export const MappedControllers = MappedControllersComponent; 25 | -------------------------------------------------------------------------------- /@xoutput/webapp/src/utils/EventEmitter.ts: -------------------------------------------------------------------------------- 1 | export type EventListener = (event: T) => void; 2 | 3 | export class EventEmitter { 4 | private listeners: EventListener[] = []; 5 | 6 | addListener(listener: EventListener): void { 7 | this.listeners.push(listener); 8 | } 9 | 10 | removeListener(listener: EventListener): void { 11 | this.listeners = this.listeners.filter((l) => l !== listener); 12 | } 13 | 14 | emit(event: T): void { 15 | this.listeners.forEach((listener) => listener(event)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /@xoutput/webapp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2021", 4 | "module": "ES2020", 5 | "lib": ["ES2021", "DOM"], 6 | "moduleResolution": "node", 7 | "noEmit": true, 8 | "jsx": "react", 9 | 10 | "strict": true, 11 | "strictNullChecks": false, 12 | //"noUnusedLocals": true, 13 | //"noUnusedParameters": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "forceConsistentCasingInFileNames": true, 16 | 17 | "allowSyntheticDefaultImports": true 18 | }, 19 | "exclude": ["./webapp", "node_modules"], 20 | "include": ["src"] 21 | } 22 | -------------------------------------------------------------------------------- /@xoutput/webapp/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { resolve, join } from 'path'; 3 | import react from '@vitejs/plugin-react'; 4 | 5 | export default defineConfig({ 6 | root: join(__dirname, 'src'), 7 | build: { 8 | outDir: resolve(__dirname, 'webapp'), 9 | target: 'es2021', 10 | sourcemap: true, 11 | }, 12 | plugins: [react()], 13 | server: { 14 | port: 8080, 15 | open: true, 16 | proxy: { 17 | '/api': 'http://localhost:8000', 18 | '/websocket': { 19 | target: 'ws://localhost:8000', 20 | ws: true, 21 | }, 22 | }, 23 | } 24 | }); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Ármin Csutorás 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /XOutput.Api/Common/DeviceTypes.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Common 2 | { 3 | public enum DeviceTypes 4 | { 5 | MicrosoftXbox360, 6 | SonyDualShock4 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /XOutput.Api/Common/Emulators.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Common 2 | { 3 | public enum Emulators 4 | { 5 | SCPToolkit, 6 | ViGEm, 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /XOutput.Api/Common/Input/InputDeviceApi.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Common.Input 2 | { 3 | public enum InputDeviceApi 4 | { 5 | WindowsApi, 6 | DirectInput, 7 | RawInput, 8 | GamepadApi, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /XOutput.Api/Common/Input/InputDeviceSource.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Common.Input 2 | { 3 | public class InputDeviceSource 4 | { 5 | public int Id { get; set; } 6 | public string Name { get; set; } 7 | public string Type { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /XOutput.Api/Common/Input/InputDeviceTarget.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Common.Input 2 | { 3 | public class InputDeviceTarget 4 | { 5 | public int Id { get; set; } 6 | public string Name { get; set; } 7 | public string Type { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /XOutput.Api/Common/SourceTypes.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace XOutput.Common.Devices 4 | { 5 | [Flags] 6 | public enum SourceTypes 7 | { 8 | None = 0, 9 | Button = 1, 10 | Slider = 2, 11 | Dpad = 4, 12 | AxisX = 8, 13 | AxisY = 16, 14 | AxisZ = 32, 15 | Axis = AxisX | AxisY | AxisZ, 16 | } 17 | 18 | public static class SourceTypesExtension 19 | { 20 | public static bool IsAxis(this SourceTypes type) 21 | { 22 | return SourceTypes.Axis.HasFlag(type) && type != SourceTypes.None; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /XOutput.Api/Common/TargetTypes.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Common.Devices 2 | { 3 | public enum TargetTypes 4 | { 5 | ForceFeedback 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /XOutput.Api/README.md: -------------------------------------------------------------------------------- 1 | # XOutput API 2 | 3 | This project contains the C# types for communication between the [server](../XOutput.Server) and the clients. -------------------------------------------------------------------------------- /XOutput.Api/Rest/Emulation/EmulatedControllerInfo.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Rest.Emulation 2 | { 3 | public class EmulatedControllerInfo 4 | { 5 | public string Id { get; set; } 6 | public string Address { get; set; } 7 | public string Name { get; set; } 8 | public string DeviceType { get; set; } 9 | public string Emulator { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /XOutput.Api/Rest/Emulation/ListEmulatorsResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace XOutput.Rest.Emulation 4 | { 5 | public class EmulatorResponse 6 | { 7 | public bool Installed { get; set; } 8 | public List SupportedDeviceTypes { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /XOutput.Api/Rest/Help/InfoResponse.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Rest.Help 2 | { 3 | public class InfoResponse 4 | { 5 | public string Version { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /XOutput.Api/Rest/Input/InputDeviceInfo.cs: -------------------------------------------------------------------------------- 1 |  2 | using System.Collections.Generic; 3 | using XOutput.Common.Input; 4 | 5 | namespace XOutput.Rest.Input 6 | { 7 | public class InputDeviceInfo 8 | { 9 | public string Id { get; set; } 10 | public string Name { get; set; } 11 | public string DeviceApi { get; set; } 12 | public IEnumerable Sources { get; set; } 13 | public IEnumerable Targets { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /XOutput.Api/Rest/Mapping/CreateMappedControllerRequest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace XOutput.Rest.Emulation 4 | { 5 | public class CreateControllerRequest 6 | { 7 | public string Name { get; set; } 8 | public string DeviceType { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /XOutput.Api/Rest/Mapping/MappedControllerInfo.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Rest.Mapping 2 | { 3 | public class MappedControllerInfo 4 | { 5 | public string Id { get; set; } 6 | public string Name { get; set; } 7 | public string DeviceType { get; set; } 8 | public string Emulator { get; set; } 9 | public bool Active { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /XOutput.Api/Rest/Notifications/Notification.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace XOutput.Rest.Notifications 4 | { 5 | public class Notification 6 | { 7 | public const string Information = "Info"; 8 | public const string Warning = "Warn"; 9 | public const string Error = "Error"; 10 | 11 | public string Id { get; set; } 12 | public bool Acknowledged { get; set; } 13 | public string CreatedAt { get; set; } 14 | public string Key { get; set; } 15 | public string Level { get; set; } 16 | public List Parameters { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /XOutput.Api/Serialization/MessageReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text.Json; 5 | using XOutput.Websocket; 6 | 7 | namespace XOutput.Serialization 8 | { 9 | public class MessageReader 10 | { 11 | private readonly Dictionary mapping; 12 | private readonly JsonSerializerOptions serializerOptions = new JsonSerializerOptions 13 | { 14 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase 15 | }; 16 | 17 | public MessageReader(Dictionary mapping) 18 | { 19 | this.mapping = mapping; 20 | foreach (var type in mapping.Values) 21 | { 22 | if (!typeof(MessageBase).IsAssignableFrom(type)) 23 | { 24 | throw new ArgumentException("Invalid mapping"); 25 | } 26 | } 27 | } 28 | 29 | public MessageBase ReadString(string input) 30 | { 31 | var message = JsonSerializer.Deserialize(input, serializerOptions); 32 | if (!mapping.ContainsKey(message.Type)) 33 | { 34 | return message; 35 | } 36 | var type = mapping[message.Type]; 37 | return JsonSerializer.Deserialize(input, type, serializerOptions) as MessageBase; 38 | } 39 | 40 | public MessageBase Read(StreamReader input) 41 | { 42 | string text = input.ReadToEnd(); 43 | return ReadString(text); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /XOutput.Api/Serialization/MessageWriter.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text.Json; 3 | using XOutput.Websocket; 4 | 5 | namespace XOutput.Serialization 6 | { 7 | public class MessageWriter 8 | { 9 | private readonly JsonSerializerOptions serializerOptions = new JsonSerializerOptions 10 | { 11 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase 12 | }; 13 | 14 | public string GetString(MessageBase message) 15 | { 16 | return JsonSerializer.Serialize(message, message.GetType(), serializerOptions); 17 | } 18 | 19 | public void Write(MessageBase message, Stream output) 20 | { 21 | var bytes = JsonSerializer.SerializeToUtf8Bytes(message, serializerOptions); 22 | output.Write(bytes); 23 | output.Flush(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /XOutput.Api/Websocket/Common/DebugRequest.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Websocket.Common 2 | { 3 | public class DebugRequest : MessageBase 4 | { 5 | public const string MessageType = "Debug"; 6 | 7 | public string Data { get; set; } 8 | public DebugRequest() 9 | { 10 | Type = MessageType; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /XOutput.Api/Websocket/Common/PingRequest.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Websocket.Common 2 | { 3 | public class PingRequest : MessageBase 4 | { 5 | public const string MessageType = "Ping"; 6 | 7 | public long Timestamp { get; set; } 8 | public PingRequest() 9 | { 10 | Type = MessageType; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /XOutput.Api/Websocket/Common/PongResponse.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Websocket.Common 2 | { 3 | public class PongResponse : MessageBase 4 | { 5 | public const string MessageType = "Pong"; 6 | 7 | public long Timestamp { get; set; } 8 | public PongResponse() 9 | { 10 | Type = MessageType; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /XOutput.Api/Websocket/Ds4/Ds4FeedbackResponse.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Websocket.Ds4 2 | { 3 | public class Ds4FeedbackResponse : MessageBase 4 | { 5 | public const string MessageType = "Ds4Feedback"; 6 | 7 | public Ds4FeedbackResponse() 8 | { 9 | Type = MessageType; 10 | } 11 | 12 | public double SmallForceFeedback { get; set; } 13 | public double BigForceFeedback { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /XOutput.Api/Websocket/Ds4/Ds4InputRequest.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Websocket.Ds4 2 | { 3 | public class Ds4InputRequest : MessageBase 4 | { 5 | public const string MessageType = "Ds4Input"; 6 | 7 | public Ds4InputRequest() 8 | { 9 | Type = MessageType; 10 | } 11 | 12 | public bool? Circle { get; set; } 13 | public bool? Cross { get; set; } 14 | public bool? Triangle { get; set; } 15 | public bool? Square { get; set; } 16 | public bool? L1 { get; set; } 17 | public bool? L3 { get; set; } 18 | public bool? R1 { get; set; } 19 | public bool? R3 { get; set; } 20 | public bool? Options { get; set; } 21 | public bool? Share { get; set; } 22 | public bool? Ps { get; set; } 23 | public bool? Up { get; set; } 24 | public bool? Down { get; set; } 25 | public bool? Left { get; set; } 26 | public bool? Right { get; set; } 27 | public double? LX { get; set; } 28 | public double? LY { get; set; } 29 | public double? RX { get; set; } 30 | public double? RY { get; set; } 31 | public double? L2 { get; set; } 32 | public double? R2 { get; set; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /XOutput.Api/Websocket/Emulation/ControllerInputResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace XOutput.Websocket.Emulation 4 | { 5 | public class ControllerInputResponse : MessageBase 6 | { 7 | public const string MessageType = "ControllerInputFeedback"; 8 | public List Sources { get; set; } 9 | public List Targets { get; set; } 10 | public ControllerInputResponse() 11 | { 12 | Type = MessageType; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /XOutput.Api/Websocket/Emulation/ControllerSourceValue.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Websocket.Emulation 2 | { 3 | public class ControllerSourceValue 4 | { 5 | public string Id { get; set; } 6 | public double Value { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /XOutput.Api/Websocket/Emulation/ControllerTargetValue.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Websocket.Emulation 2 | { 3 | public class ControllerTargetValue 4 | { 5 | public string Id { get; set; } 6 | public double Value { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /XOutput.Api/Websocket/Input/InputDeviceDetailsRequest.cs: -------------------------------------------------------------------------------- 1 |  2 | using System.Collections.Generic; 3 | using XOutput.Common.Input; 4 | 5 | namespace XOutput.Websocket.Input 6 | { 7 | public class InputDeviceDetailsRequest : MessageBase 8 | { 9 | public const string MessageType = "InputDeviceDetails"; 10 | public string Id { get; set; } 11 | public string Name { get; set; } 12 | public string HardwareId { get; set; } 13 | public List Sources { get; set; } 14 | public List Targets { get; set; } 15 | public string InputApi { get; set; } 16 | public InputDeviceDetailsRequest() 17 | { 18 | Type = MessageType; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /XOutput.Api/Websocket/Input/InputDeviceFeedbackResponse.cs: -------------------------------------------------------------------------------- 1 |  2 | using System.Collections.Generic; 3 | 4 | namespace XOutput.Websocket.Input 5 | { 6 | public class InputDeviceFeedbackResponse : MessageBase 7 | { 8 | public const string MessageType = "InputDeviceFeedback"; 9 | public List Targets { get; set; } 10 | public InputDeviceFeedbackResponse() 11 | { 12 | Type = MessageType; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /XOutput.Api/Websocket/Input/InputDeviceInputRequest.cs: -------------------------------------------------------------------------------- 1 |  2 | using System.Collections.Generic; 3 | 4 | namespace XOutput.Websocket.Input 5 | { 6 | public class InputDeviceInputRequest : MessageBase 7 | { 8 | public const string MessageType = "InputDeviceInput"; 9 | public List Inputs { get; set; } 10 | public InputDeviceInputRequest() 11 | { 12 | Type = MessageType; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /XOutput.Api/Websocket/Input/InputDeviceInputResponse.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace XOutput.Websocket.Input 4 | { 5 | public class InputDeviceInputResponse : MessageBase 6 | { 7 | public const string MessageType = "InputDeviceInputFeedback"; 8 | public List Sources { get; set; } 9 | public List Targets { get; set; } 10 | public InputDeviceInputResponse() 11 | { 12 | Type = MessageType; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /XOutput.Api/Websocket/Input/InputDeviceSourceValue.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Websocket.Input 2 | { 3 | public class InputDeviceSourceValue 4 | { 5 | public int Id { get; set; } 6 | public double Value { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /XOutput.Api/Websocket/Input/InputDeviceTargetValue.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Websocket.Input 2 | { 3 | public class InputDeviceTargetValue 4 | { 5 | public int Id { get; set; } 6 | public double Value { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /XOutput.Api/Websocket/MessageBase.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Websocket 2 | { 3 | public class MessageBase 4 | { 5 | public string Type { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /XOutput.Api/Websocket/Xbox/XboxFeedbackResponse.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Websocket.Xbox 2 | { 3 | public class XboxFeedbackResponse : MessageBase 4 | { 5 | public const string MessageType = "XboxFeedback"; 6 | 7 | public XboxFeedbackResponse() 8 | { 9 | Type = MessageType; 10 | } 11 | 12 | public double SmallForceFeedback { get; set; } 13 | public double BigForceFeedback { get; set; } 14 | public int LedNumber { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /XOutput.Api/Websocket/Xbox/XboxInputRequest.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Websocket.Xbox 2 | { 3 | public class XboxInputRequest : MessageBase 4 | { 5 | public const string MessageType = "XboxInput"; 6 | 7 | public XboxInputRequest() 8 | { 9 | Type = MessageType; 10 | } 11 | 12 | public bool? A { get; set; } 13 | public bool? B { get; set; } 14 | public bool? X { get; set; } 15 | public bool? Y { get; set; } 16 | public bool? L1 { get; set; } 17 | public bool? L3 { get; set; } 18 | public bool? R1 { get; set; } 19 | public bool? R3 { get; set; } 20 | public bool? Start { get; set; } 21 | public bool? Back { get; set; } 22 | public bool? Home { get; set; } 23 | public bool? Up { get; set; } 24 | public bool? Down { get; set; } 25 | public bool? Left { get; set; } 26 | public bool? Right { get; set; } 27 | public double? LX { get; set; } 28 | public double? LY { get; set; } 29 | public double? RX { get; set; } 30 | public double? RY { get; set; } 31 | public double? L2 { get; set; } 32 | public double? R2 { get; set; } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /XOutput.Api/XOutput.Api.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | library 5 | net7.0;net6.0 6 | XOutput 7 | Ármin Csutorás 8 | XOutput.Api 9 | XOutput API 10 | This library contains the types for communication between the XOutput servers and the clients. 11 | content/LICENSE 12 | Copyright (c) 2023 Ármin Csutorás 13 | https://github.com/csutorasa/XOutput 14 | https://github.com/csutorasa/XOutput 15 | git 16 | true 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /XOutput.ApiTests/Serialization/MessageWriterTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.IO; 3 | using System.Text; 4 | using XOutput.Websocket; 5 | using XOutput.Websocket.Xbox; 6 | 7 | namespace XOutput.Serialization.Tests 8 | { 9 | [TestClass()] 10 | public class MessageWriterTests 11 | { 12 | private readonly MessageWriter writer = new MessageWriter(); 13 | 14 | [TestMethod] 15 | public void ForceFeedbackTest() 16 | { 17 | var input = new XboxFeedbackResponse 18 | { 19 | SmallForceFeedback = 0, 20 | BigForceFeedback = 1, 21 | LedNumber = 1, 22 | }; 23 | var message = writer.GetString(input); 24 | Assert.AreEqual("{\"smallForceFeedback\":0,\"bigForceFeedback\":1,\"ledNumber\":1,\"type\":\"XboxFeedback\"}", message); 25 | } 26 | 27 | [TestMethod] 28 | public void UnknownMessageStreamTest() 29 | { 30 | var message = new MessageBase { Type = "test" }; 31 | using MemoryStream ms = new MemoryStream(1024); 32 | writer.Write(message, ms); 33 | int length = (int)ms.Position; 34 | ms.Seek(0, SeekOrigin.Begin); 35 | byte[] buffer = new byte[length]; 36 | ms.Read(buffer, 0, length); 37 | string output = Encoding.UTF8.GetString(buffer); 38 | Assert.AreEqual("{\"type\":\"test\"}", output); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /XOutput.ApiTests/XOutput.ApiTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0;net6.0 5 | false 6 | XOutput 7 | Copyright (c) 2023 Ármin Csutorás 8 | 9 | 10 | 11 | 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | all 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /XOutput.App/App.xaml: -------------------------------------------------------------------------------- 1 |  8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /XOutput.App/AppConfiguration.cs: -------------------------------------------------------------------------------- 1 | using XOutput.Configuration; 2 | using XOutput.DependencyInjection; 3 | using XOutput.Rest; 4 | using XOutput.Rest.Help; 5 | 6 | namespace XOutput.App 7 | { 8 | public static class AppConfiguration 9 | { 10 | [ResolverMethod] 11 | public static IHttpClientProvider HttpClientProvider() 12 | { 13 | return new DynamicHttpClientProvider(); 14 | } 15 | 16 | [ResolverMethod] 17 | public static RegistryModifierService GetRegistryModifierService() 18 | { 19 | return new RegistryModifierService(); 20 | } 21 | 22 | [ResolverMethod] 23 | public static InfoClient InfoClient(IHttpClientProvider httpClientProvider) 24 | { 25 | return new InfoClient(httpClientProvider); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /XOutput.App/Configuration/AppConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using XOutput.Configuration; 3 | 4 | namespace XOutput.App.Configuration 5 | { 6 | [ConfigurationPath("conf/app")] 7 | public class AppConfig : ConfigurationBase, IEquatable 8 | { 9 | public const string Path = "conf/app"; 10 | 11 | public bool Minimized { get; set; } 12 | public string Language { get; set; } 13 | public string ServerUrl { get; set; } 14 | public bool AutoConnect { get; set; } 15 | 16 | public AppConfig(string language) 17 | { 18 | Minimized = false; 19 | Language = language; 20 | ServerUrl = null; 21 | AutoConnect = false; 22 | } 23 | 24 | public bool Equals(AppConfig other) 25 | { 26 | return Equals(Minimized, other.Minimized) && 27 | Equals(Language, other.Language); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /XOutput.App/Devices/DPadDirection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace XOutput.App.Devices 5 | { 6 | [Flags] 7 | public enum DPadDirection 8 | { 9 | None = 0, 10 | Up = 1 << 0, 11 | Left = 1 << 1, 12 | Down = 1 << 2, 13 | Right = 1 << 3 14 | } 15 | 16 | public static class DPadHelper 17 | { 18 | public static DPadDirection[] Values => values; 19 | 20 | private static DPadDirection[] values = Enum.GetValues(typeof(DPadDirection)).OfType().Where(d => d != DPadDirection.None).ToArray(); 21 | 22 | public static DPadDirection GetDirection(bool up, bool down, bool left, bool right) 23 | { 24 | DPadDirection value = DPadDirection.None; 25 | if (up) 26 | { 27 | value |= DPadDirection.Up; 28 | } 29 | else if (down) 30 | { 31 | value |= DPadDirection.Down; 32 | } 33 | 34 | if (right) 35 | { 36 | value |= DPadDirection.Right; 37 | } 38 | else if (left) 39 | { 40 | value |= DPadDirection.Left; 41 | } 42 | 43 | return value; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /XOutput.App/Devices/Input/DeviceConnectedEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace XOutput.App.Devices.Input 4 | { 5 | public delegate void DeviceConnectedHandler(object sender, DeviceConnectedEventArgs e); 6 | 7 | public class DeviceConnectedEventArgs : EventArgs 8 | { 9 | public IInputDevice Device { get; private set; } 10 | 11 | public DeviceConnectedEventArgs(IInputDevice device) 12 | { 13 | Device = device; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /XOutput.App/Devices/Input/DeviceDisconnectedEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace XOutput.App.Devices.Input 4 | { 5 | public delegate void DeviceDisconnectedHandler(object sender, DeviceDisconnectedEventArgs e); 6 | 7 | public class DeviceDisconnectedEventArgs : EventArgs 8 | { 9 | public IInputDevice Device { get; private set; } 10 | 11 | public DeviceDisconnectedEventArgs(IInputDevice device) 12 | { 13 | Device = device; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /XOutput.App/Devices/Input/DeviceInputChangedEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace XOutput.App.Devices.Input 6 | { 7 | public delegate void DeviceInputChangedHandler(object sender, DeviceInputChangedEventArgs e); 8 | 9 | public class DeviceInputChangedEventArgs : EventArgs 10 | { 11 | public IInputDevice Device => device; 12 | public IEnumerable ChangedValues => changedValues; 13 | 14 | protected IInputDevice device; 15 | protected IEnumerable changedValues; 16 | 17 | public DeviceInputChangedEventArgs(IInputDevice device) 18 | { 19 | this.device = device; 20 | changedValues = new InputSource[0]; 21 | } 22 | 23 | public void Refresh(IEnumerable changedValues) 24 | { 25 | this.changedValues = changedValues; 26 | } 27 | 28 | public bool HasValueChanged(InputSource type) 29 | { 30 | return changedValues.Contains(type); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /XOutput.App/Devices/Input/ForceFeedbackTarget.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.App.Devices.Input 2 | { 3 | public class ForceFeedbackTarget 4 | { 5 | public string DisplayName => name; 6 | public IInputDevice InputDevice => inputDevice; 7 | public int Offset => offset; 8 | public double Value { get; set; } 9 | 10 | protected IInputDevice inputDevice; 11 | protected string name; 12 | protected int offset; 13 | 14 | public ForceFeedbackTarget(IInputDevice inputDevice, string name, int offset) 15 | { 16 | this.inputDevice = inputDevice; 17 | this.name = name; 18 | this.offset = offset; 19 | } 20 | 21 | public override string ToString() 22 | { 23 | return name; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /XOutput.App/Devices/Input/IInputDevice.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace XOutput.App.Devices.Input 5 | { 6 | public interface IInputDevice : IDisposable 7 | { 8 | event DeviceInputChangedHandler InputChanged; 9 | IEnumerable Sources { get; } 10 | IEnumerable ForceFeedbacks { get; } 11 | InputConfig InputConfiguration { get; set; } 12 | InputDeviceMethod InputMethod { get; } 13 | string DisplayName { get; } 14 | string InterfacePath { get; } 15 | string UniqueId { get; } 16 | string HardwareID { get; } 17 | bool Running { get; } 18 | void Start(); 19 | void Stop(); 20 | InputSource FindSource(int offset); 21 | ForceFeedbackTarget FindTarget(int offset); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /XOutput.App/Devices/Input/IInputDeviceProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace XOutput.App.Devices.Input 5 | { 6 | public interface IInputDeviceProvider : IDisposable 7 | { 8 | event DeviceConnectedHandler Connected; 9 | event DeviceDisconnectedHandler Disconnected; 10 | bool Enabled { get; set; } 11 | void SearchDevices(); 12 | IEnumerable GetActiveDevices(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /XOutput.App/Devices/Input/IgnoredDevicesConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using XOutput.Configuration; 4 | 5 | namespace XOutput.App.Devices.Input 6 | { 7 | public class IgnoredDevicesConfig : ConfigurationBase, IEquatable 8 | { 9 | public List IgnoredHardwareIds { get; set; } 10 | 11 | public IgnoredDevicesConfig() 12 | { 13 | IgnoredHardwareIds = new List(); 14 | } 15 | 16 | public bool Equals(IgnoredDevicesConfig other) 17 | { 18 | return Equals(IgnoredHardwareIds, other.IgnoredHardwareIds); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /XOutput.App/Devices/Input/InputConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using XOutput.Configuration; 5 | 6 | namespace XOutput.App.Devices.Input 7 | { 8 | public class InputConfig : ConfigurationBase, IEquatable 9 | { 10 | public bool Autostart { get; set; } 11 | 12 | public List Sources { get; set; } 13 | 14 | public InputConfig() 15 | { 16 | Autostart = false; 17 | Sources = new List(); 18 | } 19 | 20 | public InputConfig(IInputDevice device) 21 | { 22 | Autostart = device.Running; 23 | Sources = device.Sources.Select(s => new InputSourceConfig 24 | { 25 | Offset = s.Offset, 26 | Deadzone = 0, 27 | }).ToList(); 28 | } 29 | 30 | public bool Equals(InputConfig other) 31 | { 32 | return Equals(Autostart, other.Autostart) && 33 | Equals(Sources, other.Sources); 34 | } 35 | } 36 | 37 | public class InputSourceConfig 38 | { 39 | public int Offset { get; set; } 40 | public double Deadzone { get; set; } 41 | 42 | public InputSourceConfig() { 43 | Deadzone = 0; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /XOutput.App/Devices/Input/InputConfigManager.cs: -------------------------------------------------------------------------------- 1 | using XOutput.Configuration; 2 | using XOutput.DependencyInjection; 3 | 4 | namespace XOutput.App.Devices.Input 5 | { 6 | public class InputConfigManager 7 | { 8 | private readonly ConfigurationManager configurationManager; 9 | 10 | [ResolverMethod] 11 | public InputConfigManager(ConfigurationManager configurationManager) 12 | { 13 | this.configurationManager = configurationManager; 14 | } 15 | 16 | public InputConfig LoadConfig(IInputDevice device) 17 | { 18 | return configurationManager.Load($"conf/{device.UniqueId}/{device.InputMethod}", () => new InputConfig(device)); 19 | } 20 | 21 | public void SaveConfig(IInputDevice device) 22 | { 23 | configurationManager.Save($"conf/{device.UniqueId}/{device.InputMethod}", device.InputConfiguration); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /XOutput.App/Devices/Input/InputDeviceMethod.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.App.Devices.Input 2 | { 3 | public enum InputDeviceMethod 4 | { 5 | WindowsApi, 6 | DirectInput, 7 | RawInput, 8 | Websocket, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /XOutput.App/Devices/Input/Keyboard/KeyboardSource.cs: -------------------------------------------------------------------------------- 1 | using XOutput.Common.Devices; 2 | 3 | namespace XOutput.App.Devices.Input.Keyboard 4 | { 5 | public class KeyboardSource : InputSource 6 | { 7 | 8 | public KeyboardSource(IInputDevice inputDevice, string name, int offset) : base(inputDevice, name, SourceTypes.Button, offset) 9 | { 10 | 11 | } 12 | 13 | 14 | internal bool Refresh(bool pressed) 15 | { 16 | return RefreshValue(pressed ? 1 : 0); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /XOutput.App/Devices/Input/Mouse/MouseButton.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.App.Devices.Input.Mouse 2 | { 3 | public enum MouseButton 4 | { 5 | Left = 0, 6 | Right = 1, 7 | Middle = 2, 8 | XButton1 = 3, 9 | XButton2 = 4, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /XOutput.App/Devices/Input/Mouse/MouseSource.cs: -------------------------------------------------------------------------------- 1 | using XOutput.Common.Devices; 2 | 3 | namespace XOutput.App.Devices.Input.Mouse 4 | { 5 | public class MouseSource : InputSource 6 | { 7 | 8 | public MouseSource(IInputDevice inputDevice, string name, int offset) : base(inputDevice, name, SourceTypes.Button, offset) 9 | { 10 | 11 | } 12 | 13 | 14 | internal bool Refresh(bool pressed) 15 | { 16 | return RefreshValue(pressed ? 1 : 0); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /XOutput.App/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "XOutput.App": { 4 | "commandName": "Project", 5 | "nativeDebugging": false 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /XOutput.App/README.md: -------------------------------------------------------------------------------- 1 | # XOutput application 2 | 3 | This is the Windows GUI application, which can read the inputs from the various input devices. 4 | It sends the data to the [server](../XOutput.Server) application. -------------------------------------------------------------------------------- /XOutput.App/Resources/Translations/English.json: -------------------------------------------------------------------------------- 1 | { 2 | "MainMenu": { 3 | "General": "General", 4 | "WindowsApiKeyboard": "Windows API keyboard", 5 | "WindowsApiMouse": "Windows API mouse", 6 | "DirectInput": "Direct input", 7 | "RawInput": "Raw input", 8 | "XInput": "XInput" 9 | }, 10 | "General": { 11 | "Title": "General settings", 12 | "Language": "Language", 13 | "ServerUrl": "Server URL", 14 | "AutoConnect": "Connect to server on start", 15 | "Connect": "Connect", 16 | "InvalidUriConnectionError": "Invalid address", 17 | "FailedToConnectToServerError": "Failed to connect to server" 18 | }, 19 | "WindowsApiMouse": { 20 | "Title": "Windows API mouse", 21 | "Enabled": "Enabled" 22 | }, 23 | "WindowsApiKeyboard": { 24 | "Title": "Windows API keyboard", 25 | "Enabled": "Enabled" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /XOutput.App/Resources/Translations/Hungarian.json: -------------------------------------------------------------------------------- 1 | { 2 | "MainMenu": { 3 | "General": "Általános", 4 | "WindowsApiKeyboard": "Windows API billentyűzet", 5 | "WindowsApiMouse": "Windows API egér", 6 | "DirectInput": "Direct input", 7 | "RawInput": "Raw input", 8 | "XInput": "XInput" 9 | }, 10 | "General": { 11 | "Title": "Általános beállítások", 12 | "Language": "Nyelv", 13 | "ServerUrl": "Server címe", 14 | "AutoConnect": "Kapcsolódás a szerverhez induláskor", 15 | "Connect": "Kapcsolódás", 16 | "InvalidUriConnectionError": "Érvénytelen cím", 17 | "FailedToConnectToServerError": "Nem sikerült kapcsolódni a szerverhez" 18 | }, 19 | "WindowsApiMouse": { 20 | "Title": "Windows API egér", 21 | "Enabled": "Engedélyezés" 22 | }, 23 | "WindowsApiKeyboard": { 24 | "Title": "Windows API billentyűzet", 25 | "Enabled": "Engedélyezés" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /XOutput.App/Resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csutorasa/XOutput/6a0e0988fee7efb73d6096a7e74bbd11b9181c0e/XOutput.App/Resources/icon.ico -------------------------------------------------------------------------------- /XOutput.App/UI/Component/Titled.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /XOutput.App/UI/Component/Titled.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | using System.Windows.Markup; 4 | 5 | namespace XOutput.App.UI.Component 6 | { 7 | [ContentProperty("Control")] 8 | public partial class Titled : UserControl 9 | { 10 | public readonly static DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(Titled)); 11 | public string Title 12 | { 13 | get => GetValue(TitleProperty) as string; 14 | set { SetValue(TitleProperty, value); } 15 | } 16 | public readonly static DependencyProperty ControlProperty = DependencyProperty.Register(nameof(Control), typeof(FrameworkElement), typeof(Titled)); 17 | public FrameworkElement Control 18 | { 19 | get => GetValue(ControlProperty) as FrameworkElement; 20 | set { SetValue(ControlProperty, value); } 21 | } 22 | public TranslationModel Translation => TranslationModel.Instance; 23 | 24 | public Titled() 25 | { 26 | InitializeComponent(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /XOutput.App/UI/Converter/TranslationConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Data; 4 | using XOutput.DependencyInjection; 5 | 6 | namespace XOutput.App.UI.Converter 7 | { 8 | /// 9 | /// Converts a text based on the language, and updates when the language changes. 10 | /// 11 | /// 12 | /// ().Translate(key); 28 | } 29 | 30 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 31 | { 32 | throw new NotImplementedException(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /XOutput.App/UI/IViewBase.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.App.UI 2 | { 3 | public interface IViewBase where VM : ViewModelBase where M : ModelBase 4 | { 5 | VM ViewModel { get; } 6 | } 7 | 8 | public static class DispoableViewBaseHelper 9 | { 10 | public static void DisposeViewModel(this IViewBase dispoableViewBase) where VM : DisposableViewModelBase where M : DisposableModelBase 11 | { 12 | dispoableViewBase.ViewModel.Dispose(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /XOutput.App/UI/ModelBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | 4 | namespace XOutput.App.UI 5 | { 6 | public abstract class ModelBase : INotifyPropertyChanged 7 | { 8 | public event PropertyChangedEventHandler PropertyChanged; 9 | 10 | /// 11 | /// Invokes the property changed event 12 | /// 13 | /// Name of the property that changed 14 | protected void OnPropertyChanged(string name) 15 | { 16 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); 17 | } 18 | } 19 | 20 | public abstract class DisposableModelBase : ModelBase, IDisposable 21 | { 22 | protected bool disposed = false; 23 | 24 | public void Dispose() 25 | { 26 | Dispose(true); 27 | GC.SuppressFinalize(this); 28 | } 29 | 30 | protected virtual void Dispose(bool disposing) 31 | { 32 | if (disposed) 33 | { 34 | return; 35 | } 36 | if (disposing) 37 | { 38 | 39 | } 40 | disposed = true; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /XOutput.App/UI/TranslationModel.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.App.UI 2 | { 3 | public class TranslationModel : ModelBase 4 | { 5 | public static TranslationModel Instance => instance; 6 | private static TranslationModel instance = new TranslationModel(); 7 | 8 | private string language; 9 | public string Language 10 | { 11 | get => language; 12 | set 13 | { 14 | if (language != value) 15 | { 16 | language = value; 17 | OnPropertyChanged(nameof(Language)); 18 | } 19 | } 20 | } 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/DirectInputPanel.xaml: -------------------------------------------------------------------------------- 1 |  9 | Direct input 10 | 11 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/DirectInputPanel.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | using XOutput.DependencyInjection; 3 | 4 | namespace XOutput.App.UI.View 5 | { 6 | public partial class DirectInputPanel : Grid, IViewBase 7 | { 8 | public DirectInputPanelViewModel ViewModel => viewModel; 9 | 10 | private readonly DirectInputPanelViewModel viewModel; 11 | 12 | [ResolverMethod] 13 | public DirectInputPanel(DirectInputPanelViewModel viewModel) 14 | { 15 | this.viewModel = viewModel; 16 | DataContext = viewModel; 17 | InitializeComponent(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/DirectInputPanelModel.cs: -------------------------------------------------------------------------------- 1 | using XOutput.DependencyInjection; 2 | 3 | namespace XOutput.App.UI.View 4 | { 5 | public class DirectInputPanelModel : ModelBase 6 | { 7 | [ResolverMethod] 8 | public DirectInputPanelModel() 9 | { 10 | 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/DirectInputPanelViewModel.cs: -------------------------------------------------------------------------------- 1 | using XOutput.DependencyInjection; 2 | 3 | namespace XOutput.App.UI.View 4 | { 5 | public class DirectInputPanelViewModel : ViewModelBase 6 | { 7 | [ResolverMethod] 8 | public DirectInputPanelViewModel(DirectInputPanelModel model) : base(model) 9 | { 10 | 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/GeneralPanel.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | using XOutput.DependencyInjection; 3 | 4 | namespace XOutput.App.UI.View 5 | { 6 | public partial class GeneralPanel : Grid, IViewBase 7 | { 8 | public GeneralPanelViewModel ViewModel => viewModel; 9 | 10 | private readonly GeneralPanelViewModel viewModel; 11 | 12 | [ResolverMethod] 13 | public GeneralPanel(GeneralPanelViewModel viewModel) 14 | { 15 | this.viewModel = viewModel; 16 | DataContext = viewModel; 17 | InitializeComponent(); 18 | } 19 | 20 | private async void Connect_Click(object sender, System.Windows.RoutedEventArgs e) 21 | { 22 | await viewModel.Connect(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/InputView/DirectInputDeviceModel.cs: -------------------------------------------------------------------------------- 1 | using XOutput.DependencyInjection; 2 | 3 | namespace XOutput.App.UI.View.InputView 4 | { 5 | public class DirectInputDeviceModel : DisposableModelBase 6 | { 7 | [ResolverMethod] 8 | public DirectInputDeviceModel() 9 | { 10 | 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/InputView/DirectInputDeviceView.xaml: -------------------------------------------------------------------------------- 1 |  9 | Direct input 10 | 11 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/InputView/DirectInputDeviceView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | using XOutput.App.Devices.Input.DirectInput; 3 | using XOutput.DependencyInjection; 4 | 5 | namespace XOutput.App.UI.View.InputView 6 | { 7 | public partial class DirectInputDeviceView : Grid, IViewBase 8 | { 9 | public DirectInputDeviceViewModel ViewModel => viewModel; 10 | 11 | private readonly DirectInputDeviceViewModel viewModel; 12 | 13 | protected bool disposed = false; 14 | 15 | public DirectInputDeviceView(DirectInputDevice inputDevice) 16 | { 17 | viewModel = ApplicationContext.Global.Resolve(); 18 | DataContext = viewModel; 19 | InitializeComponent(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/InputView/DirectInputDeviceViewModel.cs: -------------------------------------------------------------------------------- 1 | using XOutput.DependencyInjection; 2 | 3 | namespace XOutput.App.UI.View.InputView 4 | { 5 | public class DirectInputDeviceViewModel : DisposableViewModelBase 6 | { 7 | [ResolverMethod] 8 | public DirectInputDeviceViewModel(DirectInputDeviceModel model) : base(model) 9 | { 10 | 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/InputView/RawInputDeviceModel.cs: -------------------------------------------------------------------------------- 1 | using XOutput.DependencyInjection; 2 | 3 | namespace XOutput.App.UI.View.InputView 4 | { 5 | public class RawInputDeviceModel : DisposableModelBase 6 | { 7 | [ResolverMethod] 8 | public RawInputDeviceModel() 9 | { 10 | 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/InputView/RawInputDeviceView.xaml: -------------------------------------------------------------------------------- 1 |  9 | Raw input 10 | 11 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/InputView/RawInputDeviceView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | using XOutput.App.Devices.Input.RawInput; 3 | using XOutput.DependencyInjection; 4 | 5 | namespace XOutput.App.UI.View.InputView 6 | { 7 | public partial class RawInputDeviceView : Grid, IViewBase 8 | { 9 | public RawInputDeviceViewModel ViewModel => viewModel; 10 | 11 | private readonly RawInputDeviceViewModel viewModel; 12 | 13 | protected bool disposed = false; 14 | 15 | public RawInputDeviceView(RawInputDevice inputDevice) 16 | { 17 | viewModel = ApplicationContext.Global.Resolve(); 18 | DataContext = viewModel; 19 | InitializeComponent(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/InputView/RawInputDeviceViewModel.cs: -------------------------------------------------------------------------------- 1 | using XOutput.DependencyInjection; 2 | 3 | namespace XOutput.App.UI.View.InputView 4 | { 5 | public class RawInputDeviceViewModel : DisposableViewModelBase 6 | { 7 | [ResolverMethod] 8 | public RawInputDeviceViewModel(RawInputDeviceModel model) : base(model) 9 | { 10 | 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/RawInputPanel.xaml: -------------------------------------------------------------------------------- 1 |  9 | Raw input 10 | 11 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/RawInputPanel.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | using XOutput.DependencyInjection; 3 | 4 | namespace XOutput.App.UI.View 5 | { 6 | public partial class RawInputPanel : Grid, IViewBase 7 | { 8 | public RawInputPanelViewModel ViewModel => viewModel; 9 | 10 | private readonly RawInputPanelViewModel viewModel; 11 | 12 | [ResolverMethod] 13 | public RawInputPanel(RawInputPanelViewModel viewModel) 14 | { 15 | this.viewModel = viewModel; 16 | DataContext = viewModel; 17 | InitializeComponent(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/RawInputPanelModel.cs: -------------------------------------------------------------------------------- 1 | using XOutput.DependencyInjection; 2 | 3 | namespace XOutput.App.UI.View 4 | { 5 | public class RawInputPanelModel : ModelBase 6 | { 7 | [ResolverMethod] 8 | public RawInputPanelModel() 9 | { 10 | 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/RawInputPanelViewModel.cs: -------------------------------------------------------------------------------- 1 | using XOutput.DependencyInjection; 2 | 3 | namespace XOutput.App.UI.View 4 | { 5 | public class RawInputPanelViewModel : ViewModelBase 6 | { 7 | [ResolverMethod] 8 | public RawInputPanelViewModel(RawInputPanelModel model) : base(model) 9 | { 10 | 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/WindowsApiKeyboardPanel.xaml: -------------------------------------------------------------------------------- 1 |  9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/WindowsApiKeyboardPanel.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | using XOutput.DependencyInjection; 3 | 4 | namespace XOutput.App.UI.View 5 | { 6 | public partial class WindowsApiKeyboardPanel : Grid, IViewBase 7 | { 8 | public WindowsApiKeyboardPanelViewModel ViewModel => viewModel; 9 | 10 | private readonly WindowsApiKeyboardPanelViewModel viewModel; 11 | 12 | [ResolverMethod] 13 | public WindowsApiKeyboardPanel(WindowsApiKeyboardPanelViewModel viewModel) 14 | { 15 | this.viewModel = viewModel; 16 | DataContext = viewModel; 17 | InitializeComponent(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/WindowsApiKeyboardPanelModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using XOutput.App.Devices.Input.Keyboard; 3 | using XOutput.DependencyInjection; 4 | 5 | namespace XOutput.App.UI.View 6 | { 7 | public class WindowsApiKeyboardPanelModel : ModelBase 8 | { 9 | private bool enabled; 10 | public bool Enabled 11 | { 12 | get => enabled; 13 | set 14 | { 15 | if (enabled != value) 16 | { 17 | enabled = value; 18 | OnPropertyChanged(nameof(Enabled)); 19 | } 20 | } 21 | } 22 | 23 | private readonly ObservableCollection pressedButtons = new ObservableCollection(); 24 | public ObservableCollection PressedButtons => pressedButtons; 25 | 26 | [ResolverMethod] 27 | public WindowsApiKeyboardPanelModel() 28 | { 29 | 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/WindowsApiMousePanel.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | using XOutput.DependencyInjection; 3 | 4 | namespace XOutput.App.UI.View 5 | { 6 | public partial class WindowsApiMousePanel : Grid, IViewBase 7 | { 8 | public WindowsApiMousePanelViewModel ViewModel => viewModel; 9 | 10 | private readonly WindowsApiMousePanelViewModel viewModel; 11 | 12 | [ResolverMethod] 13 | public WindowsApiMousePanel(WindowsApiMousePanelViewModel viewModel) 14 | { 15 | this.viewModel = viewModel; 16 | DataContext = viewModel; 17 | InitializeComponent(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/XInputPanel.xaml: -------------------------------------------------------------------------------- 1 |  9 | Xinput 10 | 11 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/XInputPanel.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | using XOutput.DependencyInjection; 3 | 4 | namespace XOutput.App.UI.View 5 | { 6 | public partial class XInputPanel : Grid, IViewBase 7 | { 8 | public XInputPanelViewModel ViewModel => viewModel; 9 | 10 | private readonly XInputPanelViewModel viewModel; 11 | 12 | [ResolverMethod] 13 | public XInputPanel(XInputPanelViewModel viewModel) 14 | { 15 | this.viewModel = viewModel; 16 | DataContext = viewModel; 17 | InitializeComponent(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/XInputPanelModel.cs: -------------------------------------------------------------------------------- 1 | using XOutput.DependencyInjection; 2 | 3 | namespace XOutput.App.UI.View 4 | { 5 | public class XInputPanelModel : ModelBase 6 | { 7 | [ResolverMethod] 8 | public XInputPanelModel() 9 | { 10 | 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /XOutput.App/UI/View/XInputPanelViewModel.cs: -------------------------------------------------------------------------------- 1 | using XOutput.DependencyInjection; 2 | 3 | namespace XOutput.App.UI.View 4 | { 5 | public class XInputPanelViewModel : ViewModelBase 6 | { 7 | [ResolverMethod] 8 | public XInputPanelViewModel(XInputPanelModel model) : base(model) 9 | { 10 | 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /XOutput.App/UI/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace XOutput.App.UI 4 | { 5 | public abstract class ViewModelBase where M : ModelBase 6 | { 7 | private readonly M model; 8 | public M Model => model; 9 | public TranslationModel Translation => TranslationModel.Instance; 10 | 11 | protected ViewModelBase(M model) 12 | { 13 | this.model = model; 14 | } 15 | } 16 | 17 | public abstract class DisposableViewModelBase : ViewModelBase, IDisposable where M : DisposableModelBase, IDisposable 18 | { 19 | protected bool disposed = false; 20 | 21 | protected DisposableViewModelBase(M model) : base(model) 22 | { 23 | 24 | } 25 | 26 | public void Dispose() 27 | { 28 | Dispose(true); 29 | GC.SuppressFinalize(this); 30 | } 31 | 32 | protected virtual void Dispose(bool disposing) 33 | { 34 | if (disposed) 35 | { 36 | return; 37 | } 38 | if (disposing) 39 | { 40 | Model.Dispose(); 41 | } 42 | disposed = true; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /XOutput.App/nlog.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /XOutput.AppTests/DPadDirectionTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using XOutput.App.Devices; 3 | 4 | namespace XOutput.Devices.Tests 5 | { 6 | [TestClass] 7 | public class DPadDirectionTests 8 | { 9 | [DataRow(false, false, false, false, DPadDirection.None)] 10 | [DataRow(true, false, false, false, DPadDirection.Up)] 11 | [DataRow(false, true, false, false, DPadDirection.Down)] 12 | [DataRow(false, false, true, false, DPadDirection.Left)] 13 | [DataRow(false, false, false, true, DPadDirection.Right)] 14 | [DataRow(true, false, true, false, DPadDirection.Up | DPadDirection.Left)] 15 | [DataTestMethod] 16 | public void GetDirectionTest(bool up, bool down, bool left, bool right, DPadDirection direction) 17 | { 18 | DPadDirection dPad = DPadHelper.GetDirection(up, down, left, right); 19 | Assert.AreEqual(direction, dPad); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /XOutput.AppTests/SourceTypesTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using XOutput.Common.Devices; 3 | 4 | namespace XOutput.Devices.Tests 5 | { 6 | [TestClass] 7 | public class InputMapperTests 8 | { 9 | [DataRow(SourceTypes.AxisX, true)] 10 | [DataRow(SourceTypes.AxisY, true)] 11 | [DataRow(SourceTypes.AxisZ, true)] 12 | [DataRow(SourceTypes.Axis, true)] 13 | [DataRow(SourceTypes.Button, false)] 14 | [DataRow(SourceTypes.None, false)] 15 | [DataRow(SourceTypes.Dpad, false)] 16 | [DataRow(SourceTypes.Slider, false)] 17 | [DataTestMethod] 18 | public void IsAxisTest(SourceTypes type, bool isAxis) 19 | { 20 | Assert.AreEqual(isAxis, type.IsAxis()); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /XOutput.AppTests/XOutput.AppTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0-windows 5 | false 6 | XOutput.App 7 | Copyright (c) 2023 Ármin Csutorás 8 | 9 | 10 | 11 | 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | all 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /XOutput.Client/ClientConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using XOutput.DependencyInjection; 4 | using XOutput.Serialization; 5 | using XOutput.Websocket.Common; 6 | using XOutput.Websocket.Ds4; 7 | using XOutput.Websocket.Emulation; 8 | using XOutput.Websocket.Input; 9 | using XOutput.Websocket.Xbox; 10 | 11 | namespace XOutput.Client 12 | { 13 | public static class ClientConfiguration 14 | { 15 | [ResolverMethod] 16 | public static MessageReader MessageReader() 17 | { 18 | Dictionary deserializationMapping = new Dictionary 19 | { 20 | { DebugRequest.MessageType, typeof(DebugRequest) }, 21 | { PingRequest.MessageType, typeof(PingRequest) }, 22 | { PongResponse.MessageType, typeof(PongResponse) }, 23 | { XboxFeedbackResponse.MessageType, typeof(XboxFeedbackResponse) }, 24 | { Ds4FeedbackResponse.MessageType, typeof(Ds4FeedbackResponse) }, 25 | { InputDeviceFeedbackResponse.MessageType, typeof(InputDeviceFeedbackResponse) }, 26 | { InputDeviceInputResponse.MessageType, typeof(InputDeviceInputResponse) }, 27 | { ControllerInputResponse.MessageType, typeof(ControllerInputResponse) }, 28 | }; 29 | return new MessageReader(deserializationMapping); 30 | } 31 | 32 | [ResolverMethod] 33 | public static MessageWriter MessageWriter() 34 | { 35 | return new MessageWriter(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /XOutput.Client/README.md: -------------------------------------------------------------------------------- 1 | # XOutput client 2 | 3 | This project contains the C# client logic for communication between the [server](../XOutput.Server) and the clients. 4 | It uses the [shared api](../XOutput.Api). 5 | -------------------------------------------------------------------------------- /XOutput.Client/Rest/Emulation/EmulatedContollersClient.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace XOutput.Rest.Emulation 5 | { 6 | public class EmulatedContollersClient : HttpJsonClient 7 | { 8 | public EmulatedContollersClient(IHttpClientProvider clientProvider) : base(clientProvider) 9 | { 10 | 11 | } 12 | 13 | public Task> GetControllers(int timeoutMillis = 1000) 14 | { 15 | return GetAsync>("/api/emulated/controllers", CreateToken(timeoutMillis)); 16 | } 17 | 18 | public Task DeleteController(string id, int timeoutMillis = 1000) 19 | { 20 | return DeleteAsync($"/api/emulated/controllers/{id}", CreateToken(timeoutMillis)); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /XOutput.Client/Rest/Emulation/EmulationClient.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace XOutput.Rest.Emulation 5 | { 6 | public class EmulationClient : HttpJsonClient 7 | { 8 | public EmulationClient(IHttpClientProvider clientProvider) : base(clientProvider) 9 | { 10 | 11 | } 12 | 13 | public Task> GetEmulators(int timeoutMillis = 1000) 14 | { 15 | return GetAsync>("/api/emulators", CreateToken(timeoutMillis)); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /XOutput.Client/Rest/Help/InfoClient.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace XOutput.Rest.Help 4 | { 5 | public class InfoClient : HttpJsonClient 6 | { 7 | public InfoClient(IHttpClientProvider clientProvider) : base(clientProvider) 8 | { 9 | 10 | } 11 | 12 | public Task GetInfoAsync(int timeoutMillis = 1000) 13 | { 14 | return GetAsync("info", CreateToken(timeoutMillis)); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /XOutput.Client/Rest/HttpClientProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | 5 | namespace XOutput.Rest 6 | { 7 | public interface IHttpClientProvider 8 | { 9 | public HttpClient Client { get; } 10 | } 11 | 12 | public class StaticHttpClientProvider : IHttpClientProvider 13 | { 14 | public HttpClient Client { get; private set; } 15 | 16 | public StaticHttpClientProvider(Uri baseAddress) 17 | { 18 | Client = new HttpClient { BaseAddress = baseAddress }; 19 | } 20 | 21 | public StaticHttpClientProvider(HttpClient client) 22 | { 23 | Client = client; 24 | } 25 | } 26 | 27 | public class DynamicHttpClientProvider : IHttpClientProvider 28 | { 29 | public HttpClient Client { get; set; } 30 | 31 | public void SetBaseAddress(Uri baseAddress) 32 | { 33 | if (Client == null) 34 | { 35 | Client = new HttpClient(); 36 | } 37 | Client.BaseAddress = baseAddress; 38 | } 39 | } 40 | 41 | public class ClientHttpException : Exception { 42 | public HttpStatusCode StatusCode { get; private set; } 43 | public byte[] Content { get; private set; } 44 | 45 | public ClientHttpException(HttpStatusCode statusCode, byte[] content) { 46 | StatusCode = statusCode; 47 | Content = content; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /XOutput.Client/Rest/Input/InputsClient.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace XOutput.Rest.Input 5 | { 6 | public class InputsClient : HttpJsonClient 7 | { 8 | public InputsClient(IHttpClientProvider clientProvider) : base(clientProvider) 9 | { 10 | 11 | } 12 | 13 | public Task> GetInputsAsync(int timeoutMillis = 1000) 14 | { 15 | return GetAsync>("api/inputs", CreateToken(timeoutMillis)); 16 | } 17 | 18 | public Task GetInputAsync(string id, int timeoutMillis = 1000) 19 | { 20 | return GetAsync("api/inputs/" + id, CreateToken(timeoutMillis)); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /XOutput.Client/Rest/Mapping/MappedContollersClient.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using XOutput.Rest.Emulation; 4 | 5 | namespace XOutput.Rest.Mapping 6 | { 7 | public class MappedContollersClient : HttpJsonClient 8 | { 9 | public MappedContollersClient(IHttpClientProvider clientProvider) : base(clientProvider) 10 | { 11 | 12 | } 13 | 14 | public Task> GetControllers(int timeoutMillis = 1000) 15 | { 16 | return GetAsync>("/api/mapped/controllers", CreateToken(timeoutMillis)); 17 | } 18 | 19 | public Task CreateController(CreateControllerRequest request, int timeoutMillis = 1000) 20 | { 21 | return PutAsync("/api/mapped/controllers", request, CreateToken(timeoutMillis)); 22 | } 23 | 24 | public Task StartController(string id, int timeoutMillis = 1000) 25 | { 26 | return PutAsync($"/api/mapped/controllers/{id}/active", CreateToken(timeoutMillis)); 27 | } 28 | 29 | public Task StopController(string id, int timeoutMillis = 1000) 30 | { 31 | return DeleteAsync($"/api/mapped/controllers/{id}/active", CreateToken(timeoutMillis)); 32 | } 33 | 34 | 35 | public Task DeleteController(string id, int timeoutMillis = 1000) 36 | { 37 | return DeleteAsync($"/api/mapped/controllers/{id}", CreateToken(timeoutMillis)); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /XOutput.Client/Rest/Notifications/NotificationClient.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace XOutput.Rest.Notifications 5 | { 6 | public class NotificationClient : HttpJsonClient 7 | { 8 | public NotificationClient(IHttpClientProvider clientProvider) : base(clientProvider) 9 | { 10 | 11 | } 12 | 13 | public Task> GetNotificationsAsync(int timeoutMillis = 1000) 14 | { 15 | return GetAsync>("api/notifications", CreateToken(timeoutMillis)); 16 | } 17 | 18 | public Task AcknowledgeAsync(string id, int timeoutMillis = 1000) 19 | { 20 | return PutAsync("api/notifications/" + id + "/acknowledge", CreateToken(timeoutMillis)); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /XOutput.Client/XOutput.Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0;net6.0 5 | library 6 | XOutput 7 | Ármin Csutorás 8 | XOutput.Client 9 | XOutput Client 10 | This library contains the client logic for communication between XOutput servers and clients. 11 | content/LICENSE 12 | Copyright (c) 2023 Ármin Csutorás 13 | https://github.com/csutorasa/XOutput 14 | https://github.com/csutorasa/XOutput 15 | git 16 | true 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /XOutput.Core/Configuration/ConfigurationBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace XOutput.Configuration 5 | { 6 | public class ConfigurationBase 7 | { 8 | [JsonIgnore] 9 | public string FilePath { get; internal set; } 10 | } 11 | 12 | public class ConfigurationPathAttribute : Attribute 13 | { 14 | public string Path { get; private set; } 15 | public ConfigurationPathAttribute(string path) 16 | { 17 | Path = path; 18 | } 19 | } 20 | 21 | public static class ConfigurationHelper 22 | { 23 | public static bool AreSame(this T a, T b) where T : ConfigurationBase, IEquatable 24 | { 25 | if (ReferenceEquals(a, b)) 26 | { 27 | return true; 28 | } 29 | if (a == null || b == null) 30 | { 31 | return false; 32 | } 33 | if (a.GetType() == b.GetType()) 34 | { 35 | return a.Equals(b); 36 | } 37 | return false; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /XOutput.Core/Configuration/JsonConfigurationManager.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text.Json; 3 | 4 | namespace XOutput.Configuration 5 | { 6 | public class JsonConfigurationManager : ConfigurationManager 7 | { 8 | protected override void WriteConfiguration(StreamWriter writer, T configuration) 9 | { 10 | string text = JsonSerializer.Serialize(configuration); 11 | writer.Write(text); 12 | writer.Flush(); 13 | } 14 | 15 | protected override T ReadConfiguration(StreamReader reader) 16 | { 17 | string text = reader.ReadToEnd(); 18 | return JsonSerializer.Deserialize(text); 19 | } 20 | 21 | protected override string GetFilePath(string path) 22 | { 23 | return path + ".json"; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /XOutput.Core/CoreConfiguration.cs: -------------------------------------------------------------------------------- 1 | using XOutput.Configuration; 2 | using XOutput.DependencyInjection; 3 | using XOutput.External; 4 | using XOutput.Versioning; 5 | using XOutput.Websocket; 6 | 7 | namespace XOutput 8 | { 9 | public static class CoreConfiguration 10 | { 11 | [ResolverMethod] 12 | public static ConfigurationManager GetConfigurationManager() 13 | { 14 | return new JsonConfigurationManager(); 15 | } 16 | 17 | [ResolverMethod] 18 | public static CommandRunner GetCommandRunner() 19 | { 20 | return new CommandRunner(); 21 | } 22 | 23 | [ResolverMethod] 24 | public static WebSocketHelper GetWebSocketHelper() 25 | { 26 | return new WebSocketHelper(); 27 | } 28 | 29 | [ResolverMethod] 30 | public static IVersionGetter GetIVersionGetter() 31 | { 32 | return new GithubVersionGetter(); 33 | } 34 | 35 | [ResolverMethod] 36 | public static UpdateChecker GetUpdateChecker(IVersionGetter versionGetter) 37 | { 38 | return new UpdateChecker(versionGetter); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /XOutput.Core/DependencyInjection/Dependency.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | 4 | namespace XOutput.DependencyInjection 5 | { 6 | [AttributeUsage(AttributeTargets.Parameter, Inherited = false, AllowMultiple = false)] 7 | public sealed class DependencyAttribute : Attribute 8 | { 9 | public bool Required { get; private set; } 10 | public DependencyAttribute(bool required = true) 11 | { 12 | Required = required; 13 | } 14 | } 15 | 16 | public class Dependency 17 | { 18 | public Type Type { get; set; } 19 | public bool Required { get; set; } 20 | public bool IsEnumerable => typeof(IEnumerable).IsAssignableFrom(Type) && Type.GenericTypeArguments.Length == 1; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /XOutput.Core/DependencyInjection/MultipleValuesFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | 5 | namespace XOutput.DependencyInjection 6 | { 7 | [Serializable] 8 | public sealed class MultipleValuesFoundException : Exception 9 | { 10 | private readonly List resolvers; 11 | public List Resolvers => resolvers; 12 | private MultipleValuesFoundException() { } 13 | 14 | private MultipleValuesFoundException(string message) : base(message) { } 15 | 16 | private MultipleValuesFoundException(string message, Exception innerException) : base(message, innerException) { } 17 | 18 | public MultipleValuesFoundException(Type type, List resolvers) : this($"Multiple values found for {type.FullName}") 19 | { 20 | this.resolvers = resolvers; 21 | } 22 | 23 | private MultipleValuesFoundException(SerializationInfo info, StreamingContext context) : base(info, context) 24 | { 25 | 26 | } 27 | 28 | public override void GetObjectData(SerializationInfo info, StreamingContext context) 29 | { 30 | base.GetObjectData(info, context); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /XOutput.Core/DependencyInjection/NoValueFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace XOutput.DependencyInjection 5 | { 6 | [Serializable] 7 | public sealed class NoValueFoundException : Exception 8 | { 9 | public NoValueFoundException() { } 10 | 11 | public NoValueFoundException(string message) : base(message) { } 12 | 13 | public NoValueFoundException(string message, Exception innerException) : base(message, innerException) { } 14 | 15 | public NoValueFoundException(Type type) : this($"No value found for {type.FullName}") { } 16 | 17 | private NoValueFoundException(SerializationInfo info, StreamingContext context) : base(info, context) 18 | { 19 | 20 | } 21 | 22 | public override void GetObjectData(SerializationInfo info, StreamingContext context) 23 | { 24 | base.GetObjectData(info, context); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /XOutput.Core/DependencyInjection/ResolverMethod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace XOutput.DependencyInjection 4 | { 5 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor, Inherited = false, AllowMultiple = false)] 6 | public sealed class ResolverMethodAttribute : Attribute 7 | { 8 | public Scope Scope { get; private set; } 9 | public ResolverMethodAttribute(Scope scope = Scope.Singleton) 10 | { 11 | Scope = scope; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /XOutput.Core/DependencyInjection/Scope.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.DependencyInjection 2 | { 3 | public enum Scope 4 | { 5 | Singleton, 6 | Prototype 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /XOutput.Core/DependencyInjection/TypeFinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace XOutput.DependencyInjection 6 | { 7 | class TypeFinder 8 | { 9 | public Type[] GetAllTypes(Func assemblyFilter = null, Func typeFilter = null) 10 | { 11 | if (assemblyFilter != null) 12 | { 13 | return GetAllTypes(typeFilter, AppDomain.CurrentDomain.GetAssemblies().Where(assemblyFilter).ToArray()); 14 | } 15 | return GetAllTypes(typeFilter, AppDomain.CurrentDomain.GetAssemblies().ToArray()); 16 | } 17 | 18 | public Type[] GetAllTypes(Func typeFilter, params Assembly[] assemblies) 19 | { 20 | if (typeFilter != null) 21 | { 22 | return assemblies.SelectMany(a => a.GetTypes()).Where(typeFilter).ToArray(); 23 | } 24 | return assemblies.SelectMany(a => a.GetTypes()).ToArray(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /XOutput.Core/Exceptions/SafeCallResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace XOutput.Exceptions 4 | { 5 | public class SafeCallResult 6 | { 7 | public Exception Error { get; protected set; } 8 | 9 | public bool HasError => Error != null; 10 | 11 | protected SafeCallResult(Exception exception) 12 | { 13 | Error = exception; 14 | } 15 | 16 | public static SafeCallResult CreateSuccess() 17 | { 18 | return new SafeCallResult(null); 19 | } 20 | 21 | public static SafeCallResult CreateError(Exception exception) 22 | { 23 | return new SafeCallResult(exception); 24 | } 25 | } 26 | public sealed class SafeCallResult : SafeCallResult 27 | { 28 | public T Result { get; private set; } 29 | 30 | private SafeCallResult(T result, Exception exception) : base(exception) 31 | { 32 | Result = result; 33 | } 34 | 35 | public static SafeCallResult CreateSuccess(T result) 36 | { 37 | return new SafeCallResult(result, null); 38 | } 39 | 40 | public new static SafeCallResult CreateError(Exception exception) 41 | { 42 | return new SafeCallResult(default(T), exception); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /XOutput.Core/External/ProcessErrorException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.Serialization; 4 | 5 | namespace XOutput.External 6 | { 7 | [Serializable] 8 | public sealed class ProcessErrorException : Exception 9 | { 10 | public Process Process { get; private set; } 11 | 12 | public ProcessErrorException() { } 13 | 14 | public ProcessErrorException(string message) : base(message) { } 15 | 16 | public ProcessErrorException(string message, Exception innerException) : base(message, innerException) { } 17 | 18 | public ProcessErrorException(Process process) : this($"Process failed with exit code {process.ExitCode}") 19 | { 20 | Process = process; 21 | } 22 | 23 | private ProcessErrorException(SerializationInfo info, StreamingContext context) : base(info, context) 24 | { 25 | 26 | } 27 | 28 | public override void GetObjectData(SerializationInfo info, StreamingContext context) 29 | { 30 | base.GetObjectData(info, context); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /XOutput.Core/External/ProcessHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace XOutput.External 4 | { 5 | public static class ProcessHelper 6 | { 7 | public static Process Parent(this Process process) 8 | { 9 | /*var pid = process.Id; 10 | var processName = Process.GetProcessById(pid).ProcessName; 11 | var processesByName = Process.GetProcessesByName(processName); 12 | string processIndexedName = null; 13 | 14 | for (var index = 0; index < processesByName.Length; index++) { 15 | var name = index == 0 ? processName : processName + "#" + index; 16 | var processId = new PerformanceCounter("Process", "ID Process", name); 17 | if ((int) processId.NextValue() == pid) { 18 | processIndexedName = name; 19 | break; 20 | } 21 | } 22 | 23 | if (processIndexedName != null) { 24 | var parentId = new PerformanceCounter("Process", "Creating Process ID", processIndexedName); 25 | return Process.GetProcessById((int) parentId.NextValue()); 26 | }*/ 27 | return null; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /XOutput.Core/Notifications/NotificationItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace XOutput.Notifications 5 | { 6 | public class NotificationItem 7 | { 8 | public string Id { get; set; } 9 | public string Key { get; set; } 10 | public bool Acknowledged { get; set; } 11 | public NotificationTypes NotificationType { get; set; } 12 | public List Parameters { get; set; } 13 | public DateTime CreatedAt { get; set; } 14 | public DateTime Timeout { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /XOutput.Core/Notifications/NotificationTypes.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Notifications 2 | { 3 | public enum NotificationTypes 4 | { 5 | Information, 6 | Warning, 7 | Error, 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /XOutput.Core/Notifications/Notifications.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Notifications 2 | { 3 | public static class Notifications 4 | { 5 | public const string ForceFeedbackNotImplemented = "input.forcefeedback.notimplemented"; 6 | public const string DirectInputInstanceIdDuplication = "input.instance.duplication"; 7 | public const string HidGuardianRegistry = "hidguardian.registry.noaccess"; 8 | public const string NeedsVersionUpgrade = "version.needsupgrade"; 9 | public const string VersionCheckError = "version.error"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /XOutput.Core/Number/NumberHelper.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Number 2 | { 3 | public static class NumberHelper 4 | { 5 | public static bool DoubleEquals(this double a, double b, double acceptDifference = 0.00001) 6 | { 7 | return System.Math.Abs(a - b) < acceptDifference; 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /XOutput.Core/README.md: -------------------------------------------------------------------------------- 1 | # XOutput core 2 | 3 | This package contains common utilities between all XOutput projects. 4 | -------------------------------------------------------------------------------- /XOutput.Core/Resources/AssemblyResourceManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using System.Text; 5 | 6 | namespace XOutput.Resources 7 | { 8 | public static class AssemblyResourceManager 9 | { 10 | public static Stream GetResourceStream(string resource) 11 | { 12 | return GetResourceStream(resource, AppDomain.CurrentDomain.GetAssemblies()); 13 | } 14 | 15 | public static string GetResourceString(string resource) 16 | { 17 | return GetResourceString(resource, Encoding.UTF8, AppDomain.CurrentDomain.GetAssemblies()); 18 | 19 | } 20 | 21 | public static string GetResourceString(string resource, Encoding encoding, params Assembly[] assemblies) 22 | { 23 | using (var stream = new StreamReader(GetResourceStream(resource, assemblies), encoding)) 24 | { 25 | return stream.ReadToEnd(); 26 | } 27 | } 28 | 29 | public static Stream GetResourceStream(string resource, params Assembly[] assemblies) 30 | { 31 | foreach (var assembly in assemblies) 32 | { 33 | var stream = assembly.GetManifestResourceStream(resource); 34 | if (stream != null) 35 | { 36 | return stream; 37 | } 38 | } 39 | throw new ArgumentException($"Cannot find resource {resource}"); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /XOutput.Core/Threading/ThreadResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace XOutput.Threading 4 | { 5 | public class ThreadResult 6 | { 7 | public Exception Error { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /XOutput.Core/Threading/WindowHandle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace XOutput.Threading 4 | { 5 | public static class WindowHandleStore 6 | { 7 | public static event WindowEvent WindowEvent; 8 | public static IntPtr Handle { get; set; } 9 | 10 | public static void HandleEvent(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) 11 | { 12 | var args = new WindowEventArgs(hwnd, msg, wParam, lParam, handled); 13 | WindowEvent?.Invoke(Handle, args); 14 | handled = args.Handled; 15 | } 16 | } 17 | 18 | public delegate void WindowEvent(object sender, WindowEventArgs args); 19 | 20 | public class WindowEventArgs { 21 | public IntPtr Hwnd { get; private set; } 22 | public int Msg { get; private set; } 23 | public IntPtr WParam { get; private set; } 24 | public IntPtr LParam { get; private set; } 25 | public bool Handled { get; set; } 26 | 27 | internal WindowEventArgs(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, bool handled) 28 | { 29 | Hwnd = hwnd; 30 | Msg = msg; 31 | WParam = wParam; 32 | LParam = lParam; 33 | Handled = handled; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /XOutput.Core/Versioning/IVersionGetter.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace XOutput.Versioning 4 | { 5 | public interface IVersionGetter 6 | { 7 | Task GetLatestReleaseAsync(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /XOutput.Core/Versioning/UpdateChecker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace XOutput.Versioning 5 | { 6 | public sealed class UpdateChecker 7 | { 8 | private readonly IVersionGetter versionGetter; 9 | 10 | public UpdateChecker(IVersionGetter versionGetter) 11 | { 12 | this.versionGetter = versionGetter; 13 | } 14 | 15 | /// 16 | /// Compares the current version with the latest release. 17 | /// 18 | /// 19 | public async Task CompareReleaseAsync(string appVersion) 20 | { 21 | VersionCompare compare; 22 | try 23 | { 24 | string latestRelease = await versionGetter.GetLatestReleaseAsync(); 25 | compare = Version.Compare(appVersion, latestRelease); 26 | } 27 | catch (Exception) 28 | { 29 | compare = new VersionCompare { 30 | Result = VersionCompareValues.Error, 31 | CurrentVersion = appVersion, 32 | LatestVersion = null, 33 | }; 34 | } 35 | return await Task.Run(() => compare); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /XOutput.Core/XOutput.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | library 5 | net7.0;net6.0 6 | XOutput 7 | XOutput.Core 8 | Copyright (c) 2023 Ármin Csutorás 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /XOutput.CoreTests/External/CommandRunnerTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Diagnostics; 3 | 4 | namespace XOutput.External.Tests 5 | { 6 | [TestClass()] 7 | public class CommandRunnerTests 8 | { 9 | CommandRunner commandRunner = new CommandRunner(); 10 | 11 | [TestMethod] 12 | public void CreateProcessTest() 13 | { 14 | Process process = commandRunner.CreatePowershell("echo \"asd\""); 15 | Assert.AreEqual("powershell", process.StartInfo.FileName); 16 | Assert.AreEqual("-Command \"echo \\\"asd\\\"\"", process.StartInfo.Arguments); 17 | Assert.IsTrue(process.StartInfo.CreateNoWindow); 18 | Assert.IsFalse(process.StartInfo.UseShellExecute); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /XOutput.CoreTests/Number/NumberHelperTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | 4 | namespace XOutput.Number.Tests 5 | { 6 | [TestClass()] 7 | public class NumberHelperTests 8 | { 9 | [DataRow(5, 5, true)] 10 | [DataRow(5, 5.0000001, true)] 11 | [DataRow(5, 5.1, false)] 12 | [DataRow(4.9, 5, false)] 13 | [DataTestMethod] 14 | public void DoubleEquals(double a, double b, bool equals) 15 | { 16 | Assert.AreEqual(equals, NumberHelper.DoubleEquals(a, b)); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /XOutput.CoreTests/Threading/ThreadCreatorTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Threading; 3 | 4 | namespace XOutput.Threading.Tests 5 | { 6 | [TestClass()] 7 | public class ThreadCreatorTests 8 | { 9 | [TestMethod] 10 | [Timeout(1000)] 11 | public void Create() 12 | { 13 | ThreadContext context = ThreadCreator.Create("test", ThreadAction, true); 14 | ThreadResult result = context.Start().Cancel().Wait(); 15 | Assert.IsNull(result.Error); 16 | } 17 | 18 | private void ThreadAction(CancellationToken token) 19 | { 20 | Assert.AreEqual("test", Thread.CurrentThread.Name); 21 | Assert.IsTrue(Thread.CurrentThread.IsBackground); 22 | while (!token.IsCancellationRequested) 23 | { 24 | Thread.Sleep(0); 25 | } 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /XOutput.CoreTests/XOutput.CoreTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | net7.0;net6.0 6 | XOutput 7 | Copyright (c) 2023 Ármin Csutorás 8 | 9 | 10 | 11 | 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | all 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /XOutput.Emulation/Ds4/Ds4Device.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using XOutput.Common; 3 | 4 | namespace XOutput.Emulation.Ds4 5 | { 6 | public abstract class Ds4Device : IDevice 7 | { 8 | public string Id { get; } = Guid.NewGuid().ToString(); 9 | public DeviceTypes DeviceType => DeviceTypes.SonyDualShock4; 10 | public abstract Emulators Emulator { get; } 11 | 12 | public event Ds4FeedbackEvent FeedbackEvent; 13 | public event DeviceDisconnectedEventHandler Closed; 14 | 15 | protected void InvokeFeedbackEvent(Ds4FeedbackEventArgs args) 16 | { 17 | FeedbackEvent?.Invoke(this, args); 18 | } 19 | 20 | protected void InvokeClosedEvent(DeviceDisconnectedEventArgs args) 21 | { 22 | Closed?.Invoke(this, args); 23 | } 24 | public abstract void SendInput(Ds4Input input); 25 | public abstract void Close(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /XOutput.Emulation/Ds4/Ds4FeedbackEvent.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Emulation.Ds4 2 | { 3 | public delegate void Ds4FeedbackEvent(object sender, Ds4FeedbackEventArgs args); 4 | 5 | public class Ds4FeedbackEventArgs 6 | { 7 | public double Small { get; set; } 8 | public double Large { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /XOutput.Emulation/Ds4/Ds4Input.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Emulation.Ds4 2 | { 3 | public class Ds4Input 4 | { 5 | public bool? Circle { get; set; } 6 | public bool? Cross { get; set; } 7 | public bool? Triangle { get; set; } 8 | public bool? Square { get; set; } 9 | public bool? L1 { get; set; } 10 | public bool? L3 { get; set; } 11 | public bool? R1 { get; set; } 12 | public bool? R3 { get; set; } 13 | public bool? Options { get; set; } 14 | public bool? Share { get; set; } 15 | public bool? Ps { get; set; } 16 | public bool? Up { get; set; } 17 | public bool? Down { get; set; } 18 | public bool? Left { get; set; } 19 | public bool? Right { get; set; } 20 | public double? LX { get; set; } 21 | public double? LY { get; set; } 22 | public double? RX { get; set; } 23 | public double? RY { get; set; } 24 | public double? L2 { get; set; } 25 | public double? R2 { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /XOutput.Emulation/Ds4/IDs4Emulator.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Emulation.Ds4 2 | { 3 | public interface IDs4Emulator : IEmulator 4 | { 5 | Ds4Device CreateDs4Device(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /XOutput.Emulation/EmulatorService.cs: -------------------------------------------------------------------------------- 1 |  2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using XOutput.Common; 5 | using XOutput.DependencyInjection; 6 | using XOutput.Emulation.Ds4; 7 | using XOutput.Emulation.Xbox; 8 | 9 | namespace XOutput.Emulation 10 | { 11 | public class EmulatorService 12 | { 13 | private readonly List emulators; 14 | 15 | [ResolverMethod] 16 | public EmulatorService(List emulators) 17 | { 18 | this.emulators = emulators; 19 | } 20 | 21 | public T FindEmulator(DeviceTypes deviceType, Emulators emulator) where T : IEmulator 22 | { 23 | return emulators 24 | .Where(e => e.Emulator == emulator) 25 | .Where(e => e.SupportedDeviceTypes.Contains(deviceType)) 26 | .OfType() 27 | .First(); 28 | } 29 | 30 | public IXboxEmulator FindBestXboxEmulator() 31 | { 32 | var vigem = emulators.Find(emulator => emulator.Installed && emulator.Emulator == Emulators.ViGEm); 33 | if (vigem != null) { 34 | return (IXboxEmulator) vigem; 35 | } 36 | return (IXboxEmulator) emulators.Find(emulator => emulator.Installed && emulator.Emulator == Emulators.SCPToolkit); 37 | } 38 | 39 | public IDs4Emulator FindBestDs4Emulator() 40 | { 41 | return (IDs4Emulator) emulators.Find(emulator => emulator.Installed && emulator.Emulator == Emulators.ViGEm); 42 | } 43 | 44 | public List GetEmulators() 45 | { 46 | return emulators.ToList(); 47 | } 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /XOutput.Emulation/IDevice.cs: -------------------------------------------------------------------------------- 1 | using XOutput.Common; 2 | 3 | namespace XOutput.Emulation 4 | { 5 | public interface IDevice 6 | { 7 | event DeviceDisconnectedEventHandler Closed; 8 | string Id { get; } 9 | DeviceTypes DeviceType { get; } 10 | Emulators Emulator { get; } 11 | void Close(); 12 | } 13 | 14 | public delegate void DeviceDisconnectedEventHandler(object sender, DeviceDisconnectedEventArgs args); 15 | 16 | public class DeviceDisconnectedEventArgs 17 | { 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /XOutput.Emulation/IEmulator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using XOutput.Common; 3 | 4 | namespace XOutput.Emulation 5 | { 6 | public interface IEmulator 7 | { 8 | bool Installed { get; } 9 | Emulators Emulator { get; } 10 | IEnumerable SupportedDeviceTypes { get; } 11 | void Close(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /XOutput.Emulation/NetworkDeviceInfo.cs: -------------------------------------------------------------------------------- 1 | using XOutput.Common; 2 | 3 | namespace XOutput.Emulation 4 | { 5 | public class NetworkDeviceInfo 6 | { 7 | public IDevice Device { get; set; } 8 | 9 | public string IPAddress { get; set; } 10 | 11 | public DeviceTypes DeviceType { get; set; } 12 | 13 | public Emulators Emulator { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /XOutput.Emulation/NetworkDeviceInfoService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using XOutput.DependencyInjection; 4 | 5 | namespace XOutput.Emulation 6 | { 7 | public class EmulatedControllersService 8 | { 9 | private readonly List connectedDevices = new List(); 10 | 11 | [ResolverMethod] 12 | public EmulatedControllersService() 13 | { 14 | 15 | } 16 | 17 | public void Add(NetworkDeviceInfo deviceInfo) 18 | { 19 | connectedDevices.Add(deviceInfo); 20 | } 21 | 22 | public bool StopAndRemove(string id) 23 | { 24 | var device = connectedDevices.Where(di => di.Device.Id == id).FirstOrDefault(); 25 | if (device == null) { 26 | return false; 27 | } 28 | device.Device.Close(); 29 | connectedDevices.Remove(device); 30 | return true; 31 | } 32 | 33 | public IEnumerable GetConnectedDevices() => connectedDevices.ToList(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /XOutput.Emulation/README.md: -------------------------------------------------------------------------------- 1 | # XOutput emulation 2 | 3 | This project contains the logic for the controller emulation. 4 | 5 | ## Supported emulation softwares 6 | 7 | | Name | XBox controller | DS4 controller | 8 | | ------------------------------------------------------------- | --------------------------- | -------------- | 9 | | [ViGEm](https://github.com/ViGEm/ViGEmBus/releases) | Yes | Yes | 10 | | [SCP Toolkit](https://github.com/nefarius/ScpServer/releases) | Yes, without force feedback | No | 11 | -------------------------------------------------------------------------------- /XOutput.Emulation/SCPToolkit/ScpEmulator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using XOutput.Common; 5 | using XOutput.DependencyInjection; 6 | using XOutput.Emulation.Xbox; 7 | 8 | namespace XOutput.Emulation.SCPToolkit 9 | { 10 | public class ScpEmulator : IXboxEmulator 11 | { 12 | public bool Installed { get; private set; } 13 | 14 | public Emulators Emulator => Emulators.SCPToolkit; 15 | 16 | public IEnumerable SupportedDeviceTypes { get; } = new DeviceTypes[] { DeviceTypes.MicrosoftXbox360 }; 17 | 18 | private int counter = 0; 19 | private ScpClient client; 20 | 21 | [ResolverMethod] 22 | public ScpEmulator() 23 | { 24 | Installed = Initialize(); 25 | } 26 | 27 | public XboxDevice CreateXboxDevice() 28 | { 29 | int controllerIndex = Interlocked.Increment(ref counter); 30 | return new ScpDevice(controllerIndex, client); 31 | } 32 | 33 | public void Close() 34 | { 35 | Installed = false; 36 | client.UnplugAll(); 37 | client.Dispose(); 38 | } 39 | 40 | private bool Initialize() 41 | { 42 | try 43 | { 44 | client = new ScpClient(); 45 | return true; 46 | } 47 | catch (Exception) 48 | { 49 | return false; 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /XOutput.Emulation/XOutput.Emulation.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | library 5 | net7.0 6 | XOutput.Emulation 7 | XOutput.Emulation 8 | Copyright (c) 2023 Ármin Csutorás 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /XOutput.Emulation/Xbox/IXboxEmulator.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Emulation.Xbox 2 | { 3 | public interface IXboxEmulator : IEmulator 4 | { 5 | XboxDevice CreateXboxDevice(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /XOutput.Emulation/Xbox/XboxDevice.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using XOutput.Common; 3 | 4 | namespace XOutput.Emulation.Xbox 5 | { 6 | public abstract class XboxDevice : IDevice 7 | { 8 | 9 | public string Id { get; } = Guid.NewGuid().ToString(); 10 | public DeviceTypes DeviceType => DeviceTypes.MicrosoftXbox360; 11 | public abstract Emulators Emulator { get; } 12 | 13 | public event XboxFeedbackEvent FeedbackEvent; 14 | public event DeviceDisconnectedEventHandler Closed; 15 | 16 | protected void InvokeFeedbackEvent(XboxFeedbackEventArgs args) 17 | { 18 | FeedbackEvent?.Invoke(this, args); 19 | } 20 | 21 | protected void InvokeClosedEvent(DeviceDisconnectedEventArgs args) 22 | { 23 | Closed?.Invoke(this, args); 24 | } 25 | public abstract void SendInput(XboxInput input); 26 | public abstract void Close(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /XOutput.Emulation/Xbox/XboxFeedbackEvent.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Emulation.Xbox 2 | { 3 | public delegate void XboxFeedbackEvent(object sender, XboxFeedbackEventArgs args); 4 | 5 | public class XboxFeedbackEventArgs 6 | { 7 | public double Small { get; set; } 8 | public double Large { get; set; } 9 | public int LedNumber { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /XOutput.Emulation/Xbox/XboxInput.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Emulation.Xbox 2 | { 3 | public class XboxInput 4 | { 5 | public bool? A { get; set; } 6 | public bool? B { get; set; } 7 | public bool? X { get; set; } 8 | public bool? Y { get; set; } 9 | public bool? L1 { get; set; } 10 | public bool? L3 { get; set; } 11 | public bool? R1 { get; set; } 12 | public bool? R3 { get; set; } 13 | public bool? Start { get; set; } 14 | public bool? Back { get; set; } 15 | public bool? Home { get; set; } 16 | public bool? Up { get; set; } 17 | public bool? Down { get; set; } 18 | public bool? Left { get; set; } 19 | public bool? Right { get; set; } 20 | public double? LX { get; set; } 21 | public double? LY { get; set; } 22 | public double? RX { get; set; } 23 | public double? RY { get; set; } 24 | public double? L2 { get; set; } 25 | public double? R2 { get; set; } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /XOutput.Mapping/Controller/ControllerConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using XOutput.Mapping.Mapper; 3 | 4 | namespace XOutput.Mapping.Controller 5 | { 6 | public class ControllerConfig 7 | { 8 | public string DisplayName { get; set; } 9 | private Dictionary mapping = new Dictionary(); 10 | public Dictionary InputMapping 11 | { 12 | get => mapping; 13 | set 14 | { 15 | if (value != mapping && value != null) 16 | { 17 | mapping = value; 18 | } 19 | } 20 | } 21 | private List forceFeedback = new List(); 22 | public List ForceFeedbackMapping 23 | { 24 | get => forceFeedback; 25 | set 26 | { 27 | if (value != forceFeedback && value != null) 28 | { 29 | forceFeedback = value; 30 | } 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /XOutput.Mapping/Controller/IMappedController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using XOutput.Emulation; 3 | 4 | namespace XOutput.Mapping.Controller 5 | { 6 | public interface IMappedController { 7 | string Id { get; } 8 | string Name { get; } 9 | IDevice Device { get; } 10 | 11 | Dictionary GetSources(); 12 | Dictionary GetTargets(); 13 | void Stop(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /XOutput.Mapping/Input/InputDeviceFeedbackEvent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace XOutput.Mapping.Input 4 | { 5 | public delegate void InputDeviceFeedback(object sender, InputDeviceFeedbackEventArgs args); 6 | 7 | public class InputDeviceFeedbackEventArgs 8 | { 9 | public IEnumerable Targets { get; private set; } 10 | 11 | public InputDeviceFeedbackEventArgs(IEnumerable targets) 12 | { 13 | Targets = targets; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /XOutput.Mapping/Input/InputDeviceInputChangedEvent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace XOutput.Mapping.Input 4 | { 5 | public delegate void InputDeviceInputChanged(object sender, InputDeviceInputChangedEventArgs args); 6 | 7 | public class InputDeviceInputChangedEventArgs 8 | { 9 | public ISet ChangedSources => changedSources; 10 | private readonly ISet changedSources; 11 | 12 | public InputDeviceInputChangedEventArgs(ISet changedSources) 13 | { 14 | this.changedSources = changedSources; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /XOutput.Mapping/Input/InputDeviceSourceWithValue.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | using XOutput.Common.Devices; 4 | using XOutput.Common.Input; 5 | 6 | namespace XOutput.Mapping.Input 7 | { 8 | public class InputDeviceSourceWithValue 9 | { 10 | public int Id { get; set; } 11 | public string Name { get; set; } 12 | public SourceTypes Type { get; set; } 13 | public double Value { get; set; } 14 | 15 | public static InputDeviceSourceWithValue Create(InputDeviceSource source) { 16 | SourceTypes type = (SourceTypes) Enum.Parse(typeof(SourceTypes), source.Type); 17 | return new InputDeviceSourceWithValue { 18 | Id = source.Id, 19 | Name = source.Name, 20 | Type = type, 21 | Value = 0, 22 | }; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /XOutput.Mapping/Input/InputDeviceTargetWithValue.cs: -------------------------------------------------------------------------------- 1 | using XOutput.Common.Input; 2 | 3 | namespace XOutput.Mapping.Input 4 | { 5 | public class InputDeviceTargetWithValue 6 | { 7 | public int Id { get; set; } 8 | public string Name { get; set; } 9 | public double Value { get; set; } 10 | 11 | public static InputDeviceTargetWithValue Create(InputDeviceTarget source) { 12 | return new InputDeviceTargetWithValue { 13 | Id = source.Id, 14 | Name = source.Name, 15 | Value = 0, 16 | }; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /XOutput.Mapping/Input/InputDevices.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using XOutput.Common.Input; 4 | using XOutput.DependencyInjection; 5 | 6 | namespace XOutput.Mapping.Input 7 | { 8 | public class InputDevices 9 | { 10 | private readonly List devices = new List(); 11 | private readonly object sync = new object(); 12 | 13 | [ResolverMethod] 14 | public InputDevices() 15 | { 16 | 17 | } 18 | 19 | public InputDevice Create(string id, string name, InputDeviceApi deviceApi, List sources, List targets) 20 | { 21 | var device = new InputDevice(id, name, deviceApi, sources, targets); 22 | lock (sync) 23 | { 24 | devices.Add(device); 25 | } 26 | return device; 27 | } 28 | 29 | public InputDevice Find(string id) 30 | { 31 | return devices.FirstOrDefault(d => d.Id == id); 32 | } 33 | 34 | public List FindAll() 35 | { 36 | return devices.ToList(); 37 | } 38 | 39 | public bool Remove(InputDevice device) 40 | { 41 | lock (sync) 42 | { 43 | return devices.Remove(device); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /XOutput.Mapping/Mapper/ForceFeedbackMapper.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Mapping.Mapper 2 | { 3 | public class ForceFeedbackMapper : Mapper 4 | { 5 | public bool Big { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /XOutput.Mapping/Mapper/InputMapperCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace XOutput.Mapping.Mapper 6 | { 7 | public class InputMapperCollection 8 | { 9 | public List Mappers { get; set; } 10 | 11 | public double CenterPoint { get; set; } 12 | 13 | public InputMapperCollection(params InputMapper[] data) : this(new List(data), 0) 14 | { 15 | 16 | } 17 | 18 | public InputMapperCollection(List mappers, double centerPoint) 19 | { 20 | Mappers = mappers; 21 | CenterPoint = centerPoint; 22 | } 23 | 24 | public double GetValue(IEnumerable values) 25 | { 26 | return values.Aggregate(CenterPoint, (acc, v) => acc + DiffFromCenter(v)); 27 | } 28 | 29 | private double DiffFromCenter(double value) 30 | { 31 | double difference = value - CenterPoint; 32 | if (Math.Abs(difference) < 0.0001) 33 | { 34 | return 0; 35 | } 36 | return difference; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /XOutput.Mapping/Mapper/Mapper.cs: -------------------------------------------------------------------------------- 1 | namespace XOutput.Mapping.Mapper 2 | { 3 | public abstract class Mapper 4 | { 5 | public string Device { get; set; } 6 | public int InputId { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /XOutput.Mapping/README.md: -------------------------------------------------------------------------------- 1 | # XOutput mapping 2 | 3 | This project contains the main logic for mapping input devices and handling contollers. -------------------------------------------------------------------------------- /XOutput.Mapping/XOutput.Mapping.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | library 5 | net7.0 6 | XOutput.Mapping 7 | XOutput.Mapping 8 | Copyright (c) 2023 Ármin Csutorás 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /XOutput.MappingTests/Mapper/InputMapperCollectionTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace XOutput.Mapping.Mapper.Tests 4 | { 5 | [TestClass] 6 | public class InputMapperCollectionTests 7 | { 8 | [DataRow(new double[] { 0 }, 0, 0)] 9 | [DataRow(new double[] { 0, 0 }, 0, 0)] 10 | [DataRow(new double[] { 0, 1 }, 0, 1)] 11 | [DataRow(new double[] { 0.5, 0.5 }, 0.5, 0.5)] 12 | [DataRow(new double[] { 0, 0.5 }, 0.5, 0)] 13 | [DataRow(new double[] { 0 }, 0.5, 0)] 14 | [DataRow(new double[] { 1 }, 0.25, 1)] 15 | [DataTestMethod] 16 | public void MapperTest(double[] values, double centerValue, double mappedValue) 17 | { 18 | var mapper = new InputMapperCollection 19 | { 20 | CenterPoint = centerValue, 21 | }; 22 | double result = mapper.GetValue(values); 23 | Assert.AreEqual(mappedValue, result); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /XOutput.MappingTests/Mapper/InputMapperTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace XOutput.Mapping.Mapper.Tests 4 | { 5 | [TestClass] 6 | public class InputMapperTests 7 | { 8 | [DataRow(0, 0, 0, 0)] 9 | [DataRow(0, 0, 1, 0)] 10 | [DataRow(0, 1, 0, 0)] 11 | [DataRow(0, 1, 0.4, 0.4)] 12 | [DataRow(0, 0.5, 0.25, 0.5)] 13 | [DataRow(0.25, 0.75, 0.5, 0.5)] 14 | [DataTestMethod] 15 | public void MapperTest(double min, double max, double value, double mappedValue) 16 | { 17 | var mapper = new InputMapper 18 | { 19 | MinValue = min, 20 | MaxValue = max, 21 | }; 22 | double result = mapper.GetValue(value); 23 | Assert.AreEqual(mappedValue, result); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /XOutput.MappingTests/XOutput.MappingTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | net7.0 6 | XOutput.Devices 7 | Copyright (c) 2023 Ármin Csutorás 8 | 9 | 10 | 11 | 12 | runtime; build; native; contentfiles; analyzers; buildtransitive 13 | all 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /XOutput.Server/Configuration/ServerConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace XOutput.Configuration 5 | { 6 | [ConfigurationPath("conf/server")] 7 | public class ServerConfig : ConfigurationBase, IEquatable 8 | { 9 | public List Urls { get; set; } 10 | 11 | public ServerConfig() 12 | { 13 | Urls = new List(); 14 | } 15 | 16 | public bool Equals(ServerConfig other) 17 | { 18 | return Equals(Urls, other.Urls); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /XOutput.Server/HttpServer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | using NLog; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using XOutput.Configuration; 8 | using XOutput.DependencyInjection; 9 | 10 | namespace XOutput 11 | { 12 | public class HttpServer 13 | { 14 | private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); 15 | private readonly ServerConfig config; 16 | 17 | [ResolverMethod] 18 | public HttpServer(ConfigurationManager configurationManager) { 19 | config = configurationManager.Load(() => new ServerConfig { 20 | Urls = new List { "*:8000", "localhost:8000" }, 21 | }); 22 | logger.Info($"Server config loaded with urls: {string.Join(", ", config.Urls)}"); 23 | } 24 | 25 | public void Run() { 26 | using var host = Host.CreateDefaultBuilder(Array.Empty()) 27 | .ConfigureWebHostDefaults(webBuilder => 28 | { 29 | webBuilder.UseUrls(config.Urls.Select(url => "http://" + url).ToArray()); 30 | webBuilder.UseStartup(); 31 | }) 32 | .Build(); 33 | host.Run(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /XOutput.Server/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:62970", 7 | "sslPort": 44384 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "XOutput.Server": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /XOutput.Server/README.md: -------------------------------------------------------------------------------- 1 | # XOutput server 2 | 3 | This is the server application where data can be sent from clients to initiate and controll the newly created controllers. -------------------------------------------------------------------------------- /XOutput.Server/Rest/Emulation/EmulatorsController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using XOutput.DependencyInjection; 5 | using XOutput.Emulation; 6 | 7 | namespace XOutput.Rest.Emulation 8 | { 9 | public class EmulatorsController : Controller 10 | { 11 | private readonly EmulatorService emulatorService; 12 | 13 | [ResolverMethod] 14 | public EmulatorsController(EmulatorService emulatorService) 15 | { 16 | this.emulatorService = emulatorService; 17 | } 18 | 19 | [HttpGet] 20 | [Route("/api/emulators")] 21 | public ActionResult> ListEmulators() 22 | { 23 | var emulators = emulatorService.GetEmulators(); 24 | 25 | return emulators.ToDictionary(e => e.Emulator.ToString(), e => new EmulatorResponse 26 | { 27 | Installed = e.Installed, 28 | SupportedDeviceTypes = e.SupportedDeviceTypes.Select(x => x.ToString()).ToList() 29 | }); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /XOutput.Server/Rest/Help/InfoController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System.Reflection; 3 | using XOutput.DependencyInjection; 4 | 5 | namespace XOutput.Rest.Help 6 | { 7 | public class InfoController : Controller 8 | { 9 | private readonly string version; 10 | 11 | [ResolverMethod] 12 | public InfoController() 13 | { 14 | version = Assembly.GetExecutingAssembly().GetName().Version.ToString(3); 15 | } 16 | 17 | [HttpGet] 18 | [Route("/api/info")] 19 | public ActionResult ListNotifications() 20 | { 21 | return new InfoResponse 22 | { 23 | Version = version, 24 | }; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /XOutput.Server/ServerConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using XOutput.DependencyInjection; 4 | using XOutput.Serialization; 5 | using XOutput.Websocket.Common; 6 | using XOutput.Websocket.Ds4; 7 | using XOutput.Websocket.Input; 8 | using XOutput.Websocket.Xbox; 9 | 10 | namespace XOutput 11 | { 12 | public static class ServerConfiguration 13 | { 14 | [ResolverMethod] 15 | public static MessageReader MessageReader() 16 | { 17 | Dictionary deserializationMapping = new Dictionary 18 | { 19 | { DebugRequest.MessageType, typeof(DebugRequest) }, 20 | { PingRequest.MessageType, typeof(PingRequest) }, 21 | { PongResponse.MessageType, typeof(PongResponse) }, 22 | { XboxInputRequest.MessageType, typeof(XboxInputRequest) }, 23 | { Ds4InputRequest.MessageType, typeof(Ds4InputRequest) }, 24 | { InputDeviceDetailsRequest.MessageType, typeof(InputDeviceDetailsRequest) }, 25 | { InputDeviceInputRequest.MessageType, typeof(InputDeviceInputRequest) }, 26 | }; 27 | return new MessageReader(deserializationMapping); 28 | } 29 | 30 | [ResolverMethod] 31 | public static MessageWriter MessageWriter() 32 | { 33 | return new MessageWriter(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /XOutput.Server/Websocket/CloseFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace XOutput.Websocket 4 | { 5 | public delegate Task CloseFunction(); 6 | } 7 | -------------------------------------------------------------------------------- /XOutput.Server/Websocket/Emulation/EmulatedControllerFeedbackHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using XOutput.Mapping.Controller; 3 | using XOutput.Threading; 4 | 5 | namespace XOutput.Websocket.Emulation 6 | { 7 | class EmulatedControllerFeedbackHandler : MessageHandler 8 | { 9 | private IMappedController emulatedController; 10 | private ThreadContext threadContext; 11 | 12 | public EmulatedControllerFeedbackHandler(CloseFunction closeFunction, SenderFunction senderFunction, IMappedController emulatedController) : base(closeFunction, senderFunction) 13 | { 14 | this.emulatedController = emulatedController; 15 | threadContext = ThreadCreator.CreateLoop($"{emulatedController.Id} input device report thread", SendFeedback, 20); 16 | } 17 | 18 | private void SendFeedback() 19 | { 20 | senderFunction(new ControllerInputResponse 21 | { 22 | Sources = emulatedController.GetSources().Select(s => new ControllerSourceValue { 23 | Id = s.Key, 24 | Value = s.Value, 25 | }).ToList(), 26 | Targets = emulatedController.GetTargets().Select(t => new ControllerTargetValue { 27 | Id = t.Key, 28 | Value = t.Value, 29 | }).ToList(), 30 | }); 31 | } 32 | 33 | public override void Close() 34 | { 35 | base.Close(); 36 | threadContext.Cancel(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /XOutput.Server/Websocket/Emulation/EmulatedControllerFeedbackWebSocketHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using System.Text.RegularExpressions; 3 | using XOutput.DependencyInjection; 4 | using XOutput.Mapping.Controller; 5 | 6 | namespace XOutput.Websocket.Emulation 7 | { 8 | class EmulatedControllerFeedbackWebSocketHandler : IWebSocketHandler 9 | { 10 | private static readonly Regex PathRegex = new Regex($"{IWebSocketHandler.WebsocketBasePath}/EmulatedController/([-A-Za-z0-9]+)"); 11 | private readonly MappedControllers emulatedControllers; 12 | 13 | [ResolverMethod] 14 | public EmulatedControllerFeedbackWebSocketHandler(MappedControllers emulatedControllers) 15 | { 16 | this.emulatedControllers = emulatedControllers; 17 | } 18 | 19 | public bool CanHandle(HttpContext context) 20 | { 21 | return PathRegex.IsMatch(context.Request.Path.Value); 22 | } 23 | 24 | public IMessageHandler CreateHandler(HttpContext context, CloseFunction closeFunction, SenderFunction sendFunction) 25 | { 26 | string id = PathRegex.Match(context.Request.Path.Value).Groups[1].Value; 27 | var emulatedController = emulatedControllers.Find(id); 28 | return new EmulatedControllerFeedbackHandler(closeFunction, sendFunction, emulatedController); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /XOutput.Server/Websocket/IWebSocketHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using System.Collections.Generic; 3 | 4 | namespace XOutput.Websocket 5 | { 6 | public interface IWebSocketHandler 7 | { 8 | public const string WebsocketBasePath = "/websocket"; 9 | bool CanHandle(HttpContext context); 10 | IMessageHandler CreateHandler(HttpContext context, CloseFunction closeFunction, SenderFunction sendFunction); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /XOutput.Server/Websocket/Input/InputDeviceFeedbackHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using XOutput.Mapping.Input; 3 | using XOutput.Threading; 4 | 5 | namespace XOutput.Websocket.Input 6 | { 7 | class InputDeviceFeedbackHandler : MessageHandler 8 | { 9 | private InputDevice device; 10 | private ThreadContext threadContext; 11 | 12 | public InputDeviceFeedbackHandler(CloseFunction closeFunction, SenderFunction senderFunction, InputDevice inputDevice) : base(closeFunction, senderFunction) 13 | { 14 | this.device = inputDevice; 15 | threadContext = ThreadCreator.CreateLoop($"{device.Id} input device report thread", SendFeedback, 20); 16 | threadContext.Start(); 17 | } 18 | 19 | private void SendFeedback() 20 | { 21 | senderFunction(new InputDeviceInputResponse 22 | { 23 | Sources = device.FindAllSources().Select(s => new InputDeviceSourceValue { 24 | Id = s.Id, 25 | Value = s.Value, 26 | }).ToList(), 27 | Targets = device.FindAllTargets().Select(s => new InputDeviceTargetValue { 28 | Id = s.Id, 29 | Value = s.Value, 30 | }).ToList(), 31 | }); 32 | } 33 | 34 | public override void Close() 35 | { 36 | base.Close(); 37 | threadContext.Cancel(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /XOutput.Server/Websocket/Input/InputDeviceFeedbackWebSocketHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using System.Text.RegularExpressions; 3 | using XOutput.DependencyInjection; 4 | using XOutput.Mapping.Input; 5 | 6 | namespace XOutput.Websocket.Input 7 | { 8 | class InputDeviceFeedbackWebSocketHandler : IWebSocketHandler 9 | { 10 | private static readonly Regex PathRegex = new Regex($"{IWebSocketHandler.WebsocketBasePath}/InputDevice/([-A-Za-z0-9]+)"); 11 | private readonly InputDevices inputDevices; 12 | 13 | [ResolverMethod] 14 | public InputDeviceFeedbackWebSocketHandler(InputDevices inputDevices) 15 | { 16 | this.inputDevices = inputDevices; 17 | } 18 | 19 | public bool CanHandle(HttpContext context) 20 | { 21 | return PathRegex.IsMatch(context.Request.Path.Value); 22 | } 23 | 24 | public IMessageHandler CreateHandler(HttpContext context, CloseFunction closeFunction, SenderFunction sendFunction) 25 | { 26 | string id = PathRegex.Match(context.Request.Path.Value).Groups[1].Value; 27 | var device = inputDevices.Find(id); 28 | return new InputDeviceFeedbackHandler(closeFunction, sendFunction, device); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /XOutput.Server/Websocket/Input/InputDeviceWebSocketHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using XOutput.DependencyInjection; 3 | using XOutput.Mapping.Input; 4 | 5 | namespace XOutput.Websocket.Input 6 | { 7 | class InputDeviceWebSocketHandler : IWebSocketHandler 8 | { 9 | private readonly InputDevices inputDevices; 10 | 11 | [ResolverMethod] 12 | public InputDeviceWebSocketHandler(InputDevices inputDevices) 13 | { 14 | this.inputDevices = inputDevices; 15 | } 16 | 17 | public bool CanHandle(HttpContext context) 18 | { 19 | return context.Request.Path.Value == $"{IWebSocketHandler.WebsocketBasePath}/InputDevice"; 20 | } 21 | 22 | public IMessageHandler CreateHandler(HttpContext context, CloseFunction closeFunction, SenderFunction sendFunction) 23 | { 24 | return new InputDeviceMessageHandler(closeFunction, sendFunction, inputDevices); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /XOutput.Server/Websocket/Mapping/MappedControllerFeedbackHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using XOutput.Mapping.Controller; 3 | using XOutput.Threading; 4 | using XOutput.Websocket.Emulation; 5 | 6 | namespace XOutput.Websocket.Mapping 7 | { 8 | class MappedControllerFeedbackHandler : MessageHandler 9 | { private IMappedController emulatedController; 10 | private ThreadContext threadContext; 11 | 12 | public MappedControllerFeedbackHandler(CloseFunction closeFunction, SenderFunction senderFunction, IMappedController emulatedController) : base(closeFunction, senderFunction) 13 | { 14 | this.emulatedController = emulatedController; 15 | threadContext = ThreadCreator.CreateLoop($"{emulatedController.Id} input device report thread", SendFeedback, 20); 16 | } 17 | 18 | private void SendFeedback() 19 | { 20 | senderFunction(new ControllerInputResponse 21 | { 22 | Sources = emulatedController.GetSources().Select(s => new ControllerSourceValue { 23 | Id = s.Key, 24 | Value = s.Value, 25 | }).ToList(), 26 | Targets = emulatedController.GetTargets().Select(t => new ControllerTargetValue { 27 | Id = t.Key, 28 | Value = t.Value, 29 | }).ToList(), 30 | }); 31 | } 32 | 33 | public override void Close() 34 | { 35 | base.Close(); 36 | threadContext.Cancel(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /XOutput.Server/Websocket/Mapping/MappedControllerFeedbackWebSocketHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Http; 2 | using System.Text.RegularExpressions; 3 | using XOutput.DependencyInjection; 4 | using XOutput.Mapping.Controller; 5 | 6 | namespace XOutput.Websocket.Mapping 7 | { 8 | class MappedControllerFeedbackWebSocketHandler : IWebSocketHandler 9 | { 10 | private static readonly Regex PathRegex = new Regex($"{IWebSocketHandler.WebsocketBasePath}/MappedController/([-A-Za-z0-9]+)"); 11 | private readonly MappedControllers emulatedControllers; 12 | 13 | [ResolverMethod] 14 | public MappedControllerFeedbackWebSocketHandler(MappedControllers emulatedControllers) 15 | { 16 | this.emulatedControllers = emulatedControllers; 17 | } 18 | 19 | public bool CanHandle(HttpContext context) 20 | { 21 | return PathRegex.IsMatch(context.Request.Path.Value); 22 | } 23 | 24 | public IMessageHandler CreateHandler(HttpContext context, CloseFunction closeFunction, SenderFunction sendFunction) 25 | { 26 | string id = PathRegex.Match(context.Request.Path.Value).Groups[1].Value; 27 | var emulatedController = emulatedControllers.Find(id); 28 | return new MappedControllerFeedbackHandler(closeFunction, sendFunction, emulatedController); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /XOutput.Server/Websocket/SenderFunction.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace XOutput.Websocket 4 | { 5 | public delegate Task SenderFunction(MessageBase message); 6 | 7 | public delegate Task SenderFunction(T message) where T : MessageBase; 8 | } 9 | -------------------------------------------------------------------------------- /XOutput.Server/XOutput.Server.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | Exe 6 | XOutput 7 | Ármin Csutorás 8 | XOutput.Server 9 | en 10 | Copyright (c) 2023 Ármin Csutorás 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | false 25 | 26 | 27 | -------------------------------------------------------------------------------- /XOutput.Server/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /XOutput.Server/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /XOutput.Server/nlog.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /bin/README.md: -------------------------------------------------------------------------------- 1 | # Bin 2 | 3 | For PowerShell scripts you might need to set the execution policy. 4 | To enable running powershell script from the current process use: 5 | 6 | ```powershell 7 | Set-ExecutionPolicy Bypass -Scope Process -Force 8 | ``` 9 | 10 | ## install 11 | 12 | This registers a task, which upon logging into Windows, will start the Server application as administrator. 13 | 14 | *This needs to be run as administrator.* 15 | 16 | ## uninstall 17 | 18 | This unregisters the installed task. 19 | 20 | *This needs to be run as administrator.* 21 | 22 | ## error-log 23 | 24 | Reads the Windows logs and writes it to `Windows EventLog errors.txt`. 25 | It makes tracing crashes easier. 26 | 27 | *This needs to be run as administrator.* 28 | -------------------------------------------------------------------------------- /bin/error-log.ps1: -------------------------------------------------------------------------------- 1 | Get-EventLog Application -EntryType Error -After ([DateTime]::Today.AddDays(-1)) | Format-Table -Wrap -Autosize | Out-File (Join-Path $PSScriptRoot "Windows EventLog errors.txt") -------------------------------------------------------------------------------- /bin/install.bat: -------------------------------------------------------------------------------- 1 | @schtasks /create /f /sc onstart /rl highest /tn "XOutput" /tr "%~dp0XOutput.Server.exe" 2 | @schtasks /run /tn "XOutput" -------------------------------------------------------------------------------- /bin/install.ps1: -------------------------------------------------------------------------------- 1 | $exepath = Join-Path $PSScriptRoot "XOutput.Server.exe" 2 | schtasks.exe /create /f /sc onstart /rl highest /tn "XOutput" /tr $exepath 3 | schtasks.exe /run /tn "XOutput" -------------------------------------------------------------------------------- /bin/uninstall.bat: -------------------------------------------------------------------------------- 1 | @schtasks /end /tn "XOutput" 2 | @schtasks /delete /f /tn "XOutput" -------------------------------------------------------------------------------- /bin/uninstall.ps1: -------------------------------------------------------------------------------- 1 | schtasks.exe /end /tn "XOutput" 2 | schtasks.exe /delete /f /tn "XOutput" -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Developer documentation 2 | 3 | - [server](server.md) 4 | - [client-project](client-project.md) 5 | - [desktop-client](desktop-client.md) 6 | - [browser-client](browser-client.md) -------------------------------------------------------------------------------- /docs/browser-client.md: -------------------------------------------------------------------------------- 1 | # Browser client 2 | 3 | The browser client can be found in the [web directory](../@xoutput). 4 | 5 | ## Prerequisites 6 | 7 | The web application requires Node 20 to build it, you can [download it](https://nodejs.org/en/download/). 8 | 9 | [`pnpm`](https://pnpm.io/) is used instead of `npm`, so that should be installed. 10 | 11 | ```shell 12 | npm install --global pnpm 13 | ``` 14 | 15 | Install dependencies and build the application. 16 | 17 | ```shell 18 | pnpm --recursive install 19 | pnpm --recursive run build 20 | ``` 21 | 22 | For development, there is a development server and watch builds. 23 | 24 | ```shell 25 | pnpm --recursive run watch 26 | ``` 27 | 28 | -------------------------------------------------------------------------------- /docs/client-project.md: -------------------------------------------------------------------------------- 1 | # Client project 2 | 3 | **THE PROJECT LIBRARIES ARE NOT PUBLSIHED YET, AS THEY ARE NOT FINAL** 4 | 5 | The client projects enable **developers** to create a new type of input. 6 | It can be used to integrate with the server application. 7 | 8 | ## Browser client library usage 9 | 10 | Use the following command to install the client. 11 | 12 | ```shell 13 | npm install @xoutput/client 14 | ``` 15 | 16 | Or alternatively you can install the api, and build your own client. 17 | 18 | ```shell 19 | npm install @xoutput/api 20 | ``` 21 | 22 | ## .NEt client library usage 23 | 24 | The 4.x version library is built with .NET 6 and .NET 7, you will need to [download it](https://dotnet.microsoft.com/download). 25 | 26 | Use the following command to install the client. 27 | 28 | ```shell 29 | dotnet add package XOutput.Client 30 | ``` 31 | 32 | Or alternatively you can install the api, and build your own client. 33 | 34 | ```shell 35 | dotnet add package XOutput.Api 36 | ``` 37 | 38 | -------------------------------------------------------------------------------- /docs/desktop-client.md: -------------------------------------------------------------------------------- 1 | # Desktop client 2 | 3 | ## Prerequisites 4 | 5 | The 4.x version client is built with .NET 7, you will need to [download it](https://dotnet.microsoft.com/download). 6 | 7 | | Action | Required dependency | 8 | | ------------------------------ | -------------------- | 9 | | Running the reader application | .NET Desktop Runtime | 10 | | Building the application | SDK | 11 | 12 | The SDK is only compatible with Windows. 13 | 14 | ```shell 15 | dotnet restore 16 | dotnet run 17 | ``` 18 | 19 | ### Reader application 20 | 21 | The reader application is a GUI Windows application which can read input values. 22 | 23 | | Interface | Devices | Requirements | 24 | | ------------ | ----------------------------- | --------------------------------------- | 25 | | Windows API | Mouse and keyboard | None | 26 | | Raw input | Mouse, keyboard | USB device with drivers | 27 | | Direct input | Joysticks | DirectX compatible devices with drivers | 28 | | XInput | Joysticks | XInput compatible devices | 29 | -------------------------------------------------------------------------------- /docs/server.md: -------------------------------------------------------------------------------- 1 | # Server 2 | 3 | ## Prerequisites 4 | 5 | The 4.x version backend is built with .NET 7, you will need to [download it](https://dotnet.microsoft.com/download). 6 | 7 | | Action | Required dependency | 8 | | ------------------------------ | -------------------- | 9 | | Running the server application | ASP.NET Core Runtime | 10 | | Building the application | SDK | 11 | 12 | However these runtimes and SDKs are cross platform the application is only compatible with Windows. 13 | 14 | ```shell 15 | dotnet restore 16 | dotnet run 17 | ``` 18 | 19 | ## Server application 20 | 21 | The server application is responsible for: 22 | 23 | - collecting input from various sources 24 | - mapping these sources 25 | - emulating devices 26 | - configuring all the above 27 | 28 | It is recommended to create a Windows task from the server, 29 | so it can start at computer startup (with Administrator priviledges without UAC). 30 | Help can be found in the [bin directory](./bin). 31 | 32 | Server application might write the registry, therefore it needs administrator prividledges. 33 | Alternatively it can be started without admin access, but then when it tries to write the registry it will prompt UAC. 34 | -------------------------------------------------------------------------------- /latest.version: -------------------------------------------------------------------------------- 1 | 3.31 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xoutput", 3 | "version": "4.0.0", 4 | "description": "XOutput", 5 | "private": true, 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/csutorasa/XOutput.git" 9 | }, 10 | "homepage": "https://github.com/csutorasa/XOutput#readme", 11 | "url": "https://github.com/csutorasa/XOutput/issues", 12 | "author": "Ármin Csutorás", 13 | "license": "MIT", 14 | "scripts": { 15 | "prdocution": "pnpm --recursive --workspace-concurrency=1 run build" 16 | }, 17 | "engines": { 18 | "node": ">=20.0.0", 19 | "npm": "use-pnpm-instead", 20 | "pnpm": ">=8.0.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /pnpm-clean.ps1: -------------------------------------------------------------------------------- 1 | Remove-Item .\@xoutput\api\node_modules\ -Recurse 2 | Remove-Item .\@xoutput\api\lib\ -Recurse 3 | Remove-Item .\@xoutput\client\node_modules\ -Recurse 4 | Remove-Item .\@xoutput\client\lib\ -Recurse 5 | Remove-Item .\@xoutput\webapp\node_modules\ -Recurse -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | -------------------------------------------------------------------------------- /pnpm-workspaces.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - '@xoutput/**' --------------------------------------------------------------------------------