├── .gitattributes ├── .github └── workflows │ └── main.yaml ├── .gitignore ├── Changelog.md ├── LICENSE ├── README.md ├── SerialMouse.xcodeproj └── project.pbxproj └── SerialMouse ├── Info.plist ├── SerialMouse.cpp ├── SerialMouse.hpp └── package.tool /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | release: 8 | types: [published] 9 | 10 | env: 11 | PROJECT_TYPE: KEXT 12 | ACID32: 1 13 | 14 | jobs: 15 | build: 16 | name: Build 17 | runs-on: macos-latest 18 | env: 19 | JOB_TYPE: BUILD 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: actions/checkout@v4 23 | with: 24 | repository: acidanthera/MacKernelSDK 25 | path: MacKernelSDK 26 | - name: CI Bootstrap 27 | run: | 28 | src=$(/usr/bin/curl -Lfs https://raw.githubusercontent.com/acidanthera/ocbuild/master/ci-bootstrap.sh) && eval "$src" || exit 1 29 | 30 | - run: xcodebuild -jobs 1 -target Package -configuration Debug -arch ACID32 -arch x86_64 31 | - run: xcodebuild -jobs 1 -target Package -configuration Release -arch ACID32 -arch x86_64 32 | 33 | - name: Upload to Artifacts 34 | uses: actions/upload-artifact@v4 35 | with: 36 | name: Artifacts 37 | path: build/*/*.zip 38 | - name: Upload to Release 39 | if: github.event_name == 'release' 40 | uses: svenstaro/upload-release-action@e74ff71f7d8a4c4745b560a485cc5fdb9b5b999d 41 | with: 42 | repo_token: ${{ secrets.GITHUB_TOKEN }} 43 | file: build/*/*.zip 44 | tag: ${{ github.ref }} 45 | file_glob: true 46 | 47 | analyze-clang: 48 | name: Analyze Clang 49 | runs-on: macos-latest 50 | env: 51 | JOB_TYPE: ANALYZE 52 | steps: 53 | - uses: actions/checkout@v4 54 | - uses: actions/checkout@v4 55 | with: 56 | repository: acidanthera/MacKernelSDK 57 | path: MacKernelSDK 58 | - name: CI Bootstrap 59 | run: | 60 | src=$(/usr/bin/curl -Lfs https://raw.githubusercontent.com/acidanthera/ocbuild/master/ci-bootstrap.sh) && eval "$src" || exit 1 61 | 62 | - run: xcodebuild analyze -quiet -scheme Package -target Package -configuration Debug -arch ACID32 -arch x86_64 CLANG_ANALYZER_OUTPUT=plist-html CLANG_ANALYZER_OUTPUT_DIR="$(pwd)/clang-analyze" && [ "$(find clang-analyze -name "*.html")" = "" ] 63 | - run: xcodebuild clean -quiet -scheme Package 64 | - run: xcodebuild analyze -quiet -scheme Package -target Package -configuration Release -arch ACID32 -arch x86_64 CLANG_ANALYZER_OUTPUT=plist-html CLANG_ANALYZER_OUTPUT_DIR="$(pwd)/clang-analyze" && [ "$(find clang-analyze -name "*.html")" = "" ] 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | DerivedData 3 | xcuserdata 4 | project.xcworkspace 5 | build 6 | xcshareddata 7 | /MacKernelSDK 8 | /clang32 9 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | SerialMouse Changelog 2 | ============================ 3 | #### v1.0.2 4 | - Fixed crash during serial port shutdown 5 | - Fixed serial mouse data desync while reading data 6 | - Enabled support for other serial ports (USB) 7 | - Switched to MacKernelSDK and codebase cleanup 8 | 9 | #### v1.0.1 10 | - Fixed mouse ID check 11 | 12 | #### v1.0.0 13 | - Initial release 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2023 Goldfish64 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SerialMouse 2 | [![Build Status](https://github.com/Goldfish64/SerialMouse/workflows/CI/badge.svg?branch=master)](https://github.com/Goldfish64/SerialMouse/actions) 3 | 4 | Open source kernel extension for macOS enabling serial mice that use the Microsoft Serial Mouse protocol. 5 | 6 | ### Usage 7 | Mice need to be connected before the OS is booted or they will not be detected. There is no hotplug support for obvious reasons. 8 | 9 | ### Downloads 10 | Available on the [releases](https://github.com/Goldfish64/SerialMouse/releases) page. 11 | -------------------------------------------------------------------------------- /SerialMouse.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 413B3F7C2A09EC9300A098A7 /* package.tool in Resources */ = {isa = PBXBuildFile; fileRef = 413B3F7B2A09EC9300A098A7 /* package.tool */; }; 11 | 419249FB21C9AD4D0078848B /* SerialMouse.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 419249FA21C9AD4D0078848B /* SerialMouse.hpp */; }; 12 | 419249FD21C9AD4D0078848B /* SerialMouse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 419249FC21C9AD4D0078848B /* SerialMouse.cpp */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXContainerItemProxy section */ 16 | 413B3F792A09EC7D00A098A7 /* PBXContainerItemProxy */ = { 17 | isa = PBXContainerItemProxy; 18 | containerPortal = 419249EE21C9AD4D0078848B /* Project object */; 19 | proxyType = 1; 20 | remoteGlobalIDString = 419249F621C9AD4D0078848B; 21 | remoteInfo = SerialMouse; 22 | }; 23 | /* End PBXContainerItemProxy section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | 413B3F732A06EA6500A098A7 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 27 | 413B3F7B2A09EC9300A098A7 /* package.tool */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = package.tool; sourceTree = ""; }; 28 | 41896CAC2A0FEE3A00B0C828 /* Changelog.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Changelog.md; sourceTree = ""; }; 29 | 419249F721C9AD4D0078848B /* SerialMouse.kext */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SerialMouse.kext; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | 419249FA21C9AD4D0078848B /* SerialMouse.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SerialMouse.hpp; sourceTree = ""; }; 31 | 419249FC21C9AD4D0078848B /* SerialMouse.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SerialMouse.cpp; sourceTree = ""; }; 32 | 419249FE21C9AD4D0078848B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33 | /* End PBXFileReference section */ 34 | 35 | /* Begin PBXFrameworksBuildPhase section */ 36 | 419249F421C9AD4D0078848B /* Frameworks */ = { 37 | isa = PBXFrameworksBuildPhase; 38 | buildActionMask = 2147483647; 39 | files = ( 40 | ); 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | /* End PBXFrameworksBuildPhase section */ 44 | 45 | /* Begin PBXGroup section */ 46 | 419249ED21C9AD4D0078848B = { 47 | isa = PBXGroup; 48 | children = ( 49 | 419249F921C9AD4D0078848B /* SerialMouse */, 50 | 419249F821C9AD4D0078848B /* Products */, 51 | 41896CAC2A0FEE3A00B0C828 /* Changelog.md */, 52 | 413B3F732A06EA6500A098A7 /* README.md */, 53 | ); 54 | sourceTree = ""; 55 | }; 56 | 419249F821C9AD4D0078848B /* Products */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 419249F721C9AD4D0078848B /* SerialMouse.kext */, 60 | ); 61 | name = Products; 62 | sourceTree = ""; 63 | }; 64 | 419249F921C9AD4D0078848B /* SerialMouse */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 419249FA21C9AD4D0078848B /* SerialMouse.hpp */, 68 | 419249FC21C9AD4D0078848B /* SerialMouse.cpp */, 69 | 419249FE21C9AD4D0078848B /* Info.plist */, 70 | 413B3F7B2A09EC9300A098A7 /* package.tool */, 71 | ); 72 | path = SerialMouse; 73 | sourceTree = ""; 74 | }; 75 | /* End PBXGroup section */ 76 | 77 | /* Begin PBXHeadersBuildPhase section */ 78 | 419249F221C9AD4D0078848B /* Headers */ = { 79 | isa = PBXHeadersBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | 419249FB21C9AD4D0078848B /* SerialMouse.hpp in Headers */, 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | /* End PBXHeadersBuildPhase section */ 87 | 88 | /* Begin PBXLegacyTarget section */ 89 | 413B3F752A09EC6500A098A7 /* Package */ = { 90 | isa = PBXLegacyTarget; 91 | buildArgumentsString = "$(ACTION)"; 92 | buildConfigurationList = 413B3F782A09EC6600A098A7 /* Build configuration list for PBXLegacyTarget "Package" */; 93 | buildPhases = ( 94 | ); 95 | buildToolPath = "${SRCROOT}/SerialMouse/package.tool"; 96 | dependencies = ( 97 | 413B3F7A2A09EC7D00A098A7 /* PBXTargetDependency */, 98 | ); 99 | name = Package; 100 | passBuildSettingsInEnvironment = 1; 101 | productName = Package; 102 | }; 103 | /* End PBXLegacyTarget section */ 104 | 105 | /* Begin PBXNativeTarget section */ 106 | 419249F621C9AD4D0078848B /* SerialMouse */ = { 107 | isa = PBXNativeTarget; 108 | buildConfigurationList = 41924A0121C9AD4D0078848B /* Build configuration list for PBXNativeTarget "SerialMouse" */; 109 | buildPhases = ( 110 | 419249F221C9AD4D0078848B /* Headers */, 111 | 419249F321C9AD4D0078848B /* Sources */, 112 | 419249F421C9AD4D0078848B /* Frameworks */, 113 | 419249F521C9AD4D0078848B /* Resources */, 114 | 413B3F7D2A09ECCE00A098A7 /* Archive */, 115 | ); 116 | buildRules = ( 117 | ); 118 | dependencies = ( 119 | ); 120 | name = SerialMouse; 121 | productName = SerialMouse; 122 | productReference = 419249F721C9AD4D0078848B /* SerialMouse.kext */; 123 | productType = "com.apple.product-type.kernel-extension"; 124 | }; 125 | /* End PBXNativeTarget section */ 126 | 127 | /* Begin PBXProject section */ 128 | 419249EE21C9AD4D0078848B /* Project object */ = { 129 | isa = PBXProject; 130 | attributes = { 131 | LastUpgradeCheck = 1010; 132 | ORGANIZATIONNAME = Goldfish64; 133 | TargetAttributes = { 134 | 413B3F752A09EC6500A098A7 = { 135 | CreatedOnToolsVersion = 14.2; 136 | ProvisioningStyle = Automatic; 137 | }; 138 | 419249F621C9AD4D0078848B = { 139 | CreatedOnToolsVersion = 10.1; 140 | ProvisioningStyle = Manual; 141 | }; 142 | }; 143 | }; 144 | buildConfigurationList = 419249F121C9AD4D0078848B /* Build configuration list for PBXProject "SerialMouse" */; 145 | compatibilityVersion = "Xcode 3.2"; 146 | developmentRegion = en; 147 | hasScannedForEncodings = 0; 148 | knownRegions = ( 149 | en, 150 | ); 151 | mainGroup = 419249ED21C9AD4D0078848B; 152 | productRefGroup = 419249F821C9AD4D0078848B /* Products */; 153 | projectDirPath = ""; 154 | projectRoot = ""; 155 | targets = ( 156 | 413B3F752A09EC6500A098A7 /* Package */, 157 | 419249F621C9AD4D0078848B /* SerialMouse */, 158 | ); 159 | }; 160 | /* End PBXProject section */ 161 | 162 | /* Begin PBXResourcesBuildPhase section */ 163 | 419249F521C9AD4D0078848B /* Resources */ = { 164 | isa = PBXResourcesBuildPhase; 165 | buildActionMask = 2147483647; 166 | files = ( 167 | 413B3F7C2A09EC9300A098A7 /* package.tool in Resources */, 168 | ); 169 | runOnlyForDeploymentPostprocessing = 0; 170 | }; 171 | /* End PBXResourcesBuildPhase section */ 172 | 173 | /* Begin PBXShellScriptBuildPhase section */ 174 | 413B3F7D2A09ECCE00A098A7 /* Archive */ = { 175 | isa = PBXShellScriptBuildPhase; 176 | buildActionMask = 2147483647; 177 | files = ( 178 | ); 179 | inputFileListPaths = ( 180 | ); 181 | inputPaths = ( 182 | ); 183 | name = Archive; 184 | outputFileListPaths = ( 185 | ); 186 | outputPaths = ( 187 | ); 188 | runOnlyForDeploymentPostprocessing = 0; 189 | shellPath = /bin/sh; 190 | shellScript = "cd \"${TARGET_BUILD_DIR}\"\n\nstrip -S \"${EXECUTABLE_PATH}\"\nif [ \"$CONFIGURATION\" == \"Release\" ]; then\n strip -x -T \"${EXECUTABLE_PATH}\" &>/dev/null || strip -x \"${EXECUTABLE_PATH}\"\nfi\n\nif [ -z \"${OVERRIDE_PYTHON3}\" ]; then\n # Use whatever is in PATH\n OVERRIDE_PYTHON3=python3\nfi\n\nif [[ \"$ARCHS\" == *\"ACID32\"* ]]; then\n \"${OVERRIDE_PYTHON3}\" ${SRCROOT}/clang32/fix-macho32 \"${EXECUTABLE_PATH}\" || exit 1\nfi\n"; 191 | }; 192 | /* End PBXShellScriptBuildPhase section */ 193 | 194 | /* Begin PBXSourcesBuildPhase section */ 195 | 419249F321C9AD4D0078848B /* Sources */ = { 196 | isa = PBXSourcesBuildPhase; 197 | buildActionMask = 2147483647; 198 | files = ( 199 | 419249FD21C9AD4D0078848B /* SerialMouse.cpp in Sources */, 200 | ); 201 | runOnlyForDeploymentPostprocessing = 0; 202 | }; 203 | /* End PBXSourcesBuildPhase section */ 204 | 205 | /* Begin PBXTargetDependency section */ 206 | 413B3F7A2A09EC7D00A098A7 /* PBXTargetDependency */ = { 207 | isa = PBXTargetDependency; 208 | target = 419249F621C9AD4D0078848B /* SerialMouse */; 209 | targetProxy = 413B3F792A09EC7D00A098A7 /* PBXContainerItemProxy */; 210 | }; 211 | /* End PBXTargetDependency section */ 212 | 213 | /* Begin XCBuildConfiguration section */ 214 | 413B3F762A09EC6600A098A7 /* Debug */ = { 215 | isa = XCBuildConfiguration; 216 | buildSettings = { 217 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 218 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 219 | CODE_SIGN_STYLE = Automatic; 220 | COPY_PHASE_STRIP = NO; 221 | DEBUGGING_SYMBOLS = YES; 222 | DEBUG_INFORMATION_FORMAT = dwarf; 223 | GCC_GENERATE_DEBUGGING_SYMBOLS = YES; 224 | GCC_OPTIMIZATION_LEVEL = 0; 225 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 226 | MTL_FAST_MATH = YES; 227 | ONLY_ACTIVE_ARCH = YES; 228 | OTHER_CFLAGS = ""; 229 | OTHER_LDFLAGS = ""; 230 | PRODUCT_NAME = "$(TARGET_NAME)"; 231 | }; 232 | name = Debug; 233 | }; 234 | 413B3F772A09EC6600A098A7 /* Release */ = { 235 | isa = XCBuildConfiguration; 236 | buildSettings = { 237 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 238 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 239 | CODE_SIGN_STYLE = Automatic; 240 | COPY_PHASE_STRIP = NO; 241 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 242 | MTL_ENABLE_DEBUG_INFO = NO; 243 | MTL_FAST_MATH = YES; 244 | OTHER_CFLAGS = ""; 245 | OTHER_LDFLAGS = ""; 246 | PRODUCT_NAME = "$(TARGET_NAME)"; 247 | }; 248 | name = Release; 249 | }; 250 | 419249FF21C9AD4D0078848B /* Debug */ = { 251 | isa = XCBuildConfiguration; 252 | buildSettings = { 253 | ALWAYS_SEARCH_USER_PATHS = NO; 254 | CLANG_ANALYZER_NONNULL = YES; 255 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 256 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 257 | CLANG_ENABLE_MODULES = YES; 258 | CLANG_ENABLE_OBJC_ARC = YES; 259 | CLANG_ENABLE_OBJC_WEAK = YES; 260 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 261 | CLANG_WARN_BOOL_CONVERSION = YES; 262 | CLANG_WARN_COMMA = YES; 263 | CLANG_WARN_CONSTANT_CONVERSION = YES; 264 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 265 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 266 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 267 | CLANG_WARN_EMPTY_BODY = YES; 268 | CLANG_WARN_ENUM_CONVERSION = YES; 269 | CLANG_WARN_INFINITE_RECURSION = YES; 270 | CLANG_WARN_INT_CONVERSION = YES; 271 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 272 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 273 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 274 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 275 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 276 | CLANG_WARN_STRICT_PROTOTYPES = YES; 277 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 278 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 279 | CLANG_WARN_UNREACHABLE_CODE = YES; 280 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 281 | CODE_SIGN_IDENTITY = "-"; 282 | "COMPILER_INDEX_STORE_ENABLE[arch=ACID32]" = NO; 283 | DEBUG_INFORMATION_FORMAT = dwarf; 284 | ENABLE_STRICT_OBJC_MSGSEND = YES; 285 | ENABLE_TESTABILITY = YES; 286 | GCC_C_LANGUAGE_STANDARD = gnu11; 287 | GCC_DYNAMIC_NO_PIC = NO; 288 | GCC_NO_COMMON_BLOCKS = YES; 289 | GCC_OPTIMIZATION_LEVEL = 0; 290 | GCC_PREPROCESSOR_DEFINITIONS = ( 291 | "DEBUG=1", 292 | "$(inherited)", 293 | ); 294 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 295 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 296 | GCC_WARN_UNDECLARED_SELECTOR = YES; 297 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 298 | GCC_WARN_UNUSED_FUNCTION = YES; 299 | GCC_WARN_UNUSED_VARIABLE = YES; 300 | KERNEL_EXTENSION_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/MacKernelSDK/Headers"; 301 | KERNEL_FRAMEWORK_HEADERS = "$(PROJECT_DIR)/MacKernelSDK/Headers"; 302 | MACOSX_DEPLOYMENT_TARGET = 10.6; 303 | MODULE_VERSION = 1.0.2; 304 | SDKROOT = macosx; 305 | VALID_ARCHS = "x86_64 ACID32"; 306 | }; 307 | name = Debug; 308 | }; 309 | 41924A0021C9AD4D0078848B /* Release */ = { 310 | isa = XCBuildConfiguration; 311 | buildSettings = { 312 | ALWAYS_SEARCH_USER_PATHS = NO; 313 | CLANG_ANALYZER_NONNULL = YES; 314 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 315 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 316 | CLANG_ENABLE_MODULES = YES; 317 | CLANG_ENABLE_OBJC_ARC = YES; 318 | CLANG_ENABLE_OBJC_WEAK = YES; 319 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 320 | CLANG_WARN_BOOL_CONVERSION = YES; 321 | CLANG_WARN_COMMA = YES; 322 | CLANG_WARN_CONSTANT_CONVERSION = YES; 323 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 324 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 325 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 326 | CLANG_WARN_EMPTY_BODY = YES; 327 | CLANG_WARN_ENUM_CONVERSION = YES; 328 | CLANG_WARN_INFINITE_RECURSION = YES; 329 | CLANG_WARN_INT_CONVERSION = YES; 330 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 331 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 332 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 333 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 334 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 335 | CLANG_WARN_STRICT_PROTOTYPES = YES; 336 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 337 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 338 | CLANG_WARN_UNREACHABLE_CODE = YES; 339 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 340 | CODE_SIGN_IDENTITY = "-"; 341 | COMPILER_INDEX_STORE_ENABLE = YES; 342 | "COMPILER_INDEX_STORE_ENABLE[arch=ACID32]" = NO; 343 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 344 | ENABLE_NS_ASSERTIONS = NO; 345 | ENABLE_STRICT_OBJC_MSGSEND = YES; 346 | GCC_C_LANGUAGE_STANDARD = gnu11; 347 | GCC_NO_COMMON_BLOCKS = YES; 348 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 349 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 350 | GCC_WARN_UNDECLARED_SELECTOR = YES; 351 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 352 | GCC_WARN_UNUSED_FUNCTION = YES; 353 | GCC_WARN_UNUSED_VARIABLE = YES; 354 | KERNEL_EXTENSION_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/MacKernelSDK/Headers"; 355 | KERNEL_FRAMEWORK_HEADERS = "$(PROJECT_DIR)/MacKernelSDK/Headers"; 356 | MACOSX_DEPLOYMENT_TARGET = 10.6; 357 | MODULE_VERSION = 1.0.2; 358 | SDKROOT = macosx; 359 | VALID_ARCHS = "x86_64 ACID32"; 360 | }; 361 | name = Release; 362 | }; 363 | 41924A0221C9AD4D0078848B /* Debug */ = { 364 | isa = XCBuildConfiguration; 365 | buildSettings = { 366 | CC = "$(inherited)"; 367 | "CC[arch=ACID32]" = "$(PROJECT_DIR)/clang32/clang-12"; 368 | CODE_SIGN_IDENTITY = ""; 369 | CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO; 370 | CODE_SIGN_STYLE = Manual; 371 | COMBINE_HIDPI_IMAGES = YES; 372 | COPY_PHASE_STRIP = NO; 373 | CURRENT_PROJECT_VERSION = "$(MODULE_VERSION)"; 374 | CXX = "$(inherited)"; 375 | "CXX[arch=ACID32]" = "$(PROJECT_DIR)/clang32/clang-12"; 376 | INFOPLIST_FILE = SerialMouse/Info.plist; 377 | LDPLUSPLUS = "$(inherited)"; 378 | "LDPLUSPLUS[arch=ACID32]" = "$(PROJECT_DIR)/clang32/clang-12"; 379 | LIBRARY_SEARCH_PATHS = ( 380 | "$(inherited)", 381 | "$(PROJECT_DIR)/MacKernelSDK/Library/universal", 382 | ); 383 | MACOSX_DEPLOYMENT_TARGET = 10.6; 384 | "MACOSX_DEPLOYMENT_TARGET[arch=ACID32]" = 10.4; 385 | MARKETING_VERSION = "$(MODULE_VERSION)"; 386 | MODULE_NAME = fish.goldfish64.SerialMouse; 387 | OTHER_CFLAGS = ( 388 | "-mmmx", 389 | "-msse", 390 | "-msse2", 391 | "-msse3", 392 | "-mfpmath=sse", 393 | "-mssse3", 394 | "-ftree-vectorize", 395 | "-fno-non-call-exceptions", 396 | "-fno-builtin", 397 | "-fno-asynchronous-unwind-tables", 398 | "-Wno-unknown-warning-option", 399 | "-Wno-ossharedptr-misuse", 400 | "-Wno-vla", 401 | "-Wno-stdlibcxx-not-found", 402 | ); 403 | "OTHER_CFLAGS[arch=ACID32]" = ( 404 | "-mmmx", 405 | "-msse", 406 | "-msse2", 407 | "-mfpmath=sse", 408 | "-ftree-vectorize", 409 | "-fno-non-call-exceptions", 410 | "-fno-builtin", 411 | "-fno-asynchronous-unwind-tables", 412 | "-Wno-unknown-warning-option", 413 | "-Wno-ossharedptr-misuse", 414 | "-Wno-vla", 415 | "-Wno-stdlibcxx-not-found", 416 | "-static", 417 | "-fallow-unsupported", 418 | "-fno-jump-tables", 419 | "-fno-stack-protector", 420 | "-target", 421 | "i386-apple-macos10.4", 422 | ); 423 | OTHER_LDFLAGS = "-static"; 424 | "OTHER_LDFLAGS[arch=ACID32]" = ( 425 | "-static", 426 | "-target", 427 | "i386-apple-macos10.4", 428 | ); 429 | PRODUCT_BUNDLE_IDENTIFIER = fish.goldfish64.SerialMouse; 430 | PRODUCT_NAME = "$(TARGET_NAME)"; 431 | WRAPPER_EXTENSION = kext; 432 | }; 433 | name = Debug; 434 | }; 435 | 41924A0321C9AD4D0078848B /* Release */ = { 436 | isa = XCBuildConfiguration; 437 | buildSettings = { 438 | CC = "$(inherited)"; 439 | "CC[arch=ACID32]" = "$(PROJECT_DIR)/clang32/clang-12"; 440 | CODE_SIGN_IDENTITY = ""; 441 | CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO; 442 | CODE_SIGN_STYLE = Manual; 443 | COMBINE_HIDPI_IMAGES = YES; 444 | CURRENT_PROJECT_VERSION = "$(MODULE_VERSION)"; 445 | CXX = "$(inherited)"; 446 | "CXX[arch=ACID32]" = "$(PROJECT_DIR)/clang32/clang-12"; 447 | DEAD_CODE_STRIPPING = YES; 448 | "DEAD_CODE_STRIPPING[arch=ACID32]" = NO; 449 | INFOPLIST_FILE = SerialMouse/Info.plist; 450 | LDPLUSPLUS = "$(inherited)"; 451 | "LDPLUSPLUS[arch=ACID32]" = "$(PROJECT_DIR)/clang32/clang-12"; 452 | LIBRARY_SEARCH_PATHS = ( 453 | "$(inherited)", 454 | "$(PROJECT_DIR)/MacKernelSDK/Library/universal", 455 | ); 456 | MACOSX_DEPLOYMENT_TARGET = 10.6; 457 | "MACOSX_DEPLOYMENT_TARGET[arch=ACID32]" = 10.4; 458 | MARKETING_VERSION = "$(MODULE_VERSION)"; 459 | MODULE_NAME = fish.goldfish64.SerialMouse; 460 | OTHER_CFLAGS = ( 461 | "-mmmx", 462 | "-msse", 463 | "-msse2", 464 | "-msse3", 465 | "-mfpmath=sse", 466 | "-mssse3", 467 | "-ftree-vectorize", 468 | "-fno-non-call-exceptions", 469 | "-fno-builtin", 470 | "-fno-asynchronous-unwind-tables", 471 | "-Wno-unknown-warning-option", 472 | "-Wno-ossharedptr-misuse", 473 | "-Wno-vla", 474 | "-Wno-stdlibcxx-not-found", 475 | ); 476 | "OTHER_CFLAGS[arch=ACID32]" = ( 477 | "-mmmx", 478 | "-msse", 479 | "-msse2", 480 | "-mfpmath=sse", 481 | "-ftree-vectorize", 482 | "-fno-non-call-exceptions", 483 | "-fno-builtin", 484 | "-fno-asynchronous-unwind-tables", 485 | "-Wno-unknown-warning-option", 486 | "-Wno-ossharedptr-misuse", 487 | "-Wno-vla", 488 | "-Wno-stdlibcxx-not-found", 489 | "-static", 490 | "-fallow-unsupported", 491 | "-fno-jump-tables", 492 | "-fno-stack-protector", 493 | "-target", 494 | "i386-apple-macos10.4", 495 | ); 496 | OTHER_LDFLAGS = "-static"; 497 | "OTHER_LDFLAGS[arch=ACID32]" = ( 498 | "-static", 499 | "-target", 500 | "i386-apple-macos10.4", 501 | ); 502 | PRODUCT_BUNDLE_IDENTIFIER = fish.goldfish64.SerialMouse; 503 | PRODUCT_NAME = "$(TARGET_NAME)"; 504 | STRIP_STYLE = "non-global"; 505 | WRAPPER_EXTENSION = kext; 506 | }; 507 | name = Release; 508 | }; 509 | /* End XCBuildConfiguration section */ 510 | 511 | /* Begin XCConfigurationList section */ 512 | 413B3F782A09EC6600A098A7 /* Build configuration list for PBXLegacyTarget "Package" */ = { 513 | isa = XCConfigurationList; 514 | buildConfigurations = ( 515 | 413B3F762A09EC6600A098A7 /* Debug */, 516 | 413B3F772A09EC6600A098A7 /* Release */, 517 | ); 518 | defaultConfigurationIsVisible = 0; 519 | defaultConfigurationName = Release; 520 | }; 521 | 419249F121C9AD4D0078848B /* Build configuration list for PBXProject "SerialMouse" */ = { 522 | isa = XCConfigurationList; 523 | buildConfigurations = ( 524 | 419249FF21C9AD4D0078848B /* Debug */, 525 | 41924A0021C9AD4D0078848B /* Release */, 526 | ); 527 | defaultConfigurationIsVisible = 0; 528 | defaultConfigurationName = Release; 529 | }; 530 | 41924A0121C9AD4D0078848B /* Build configuration list for PBXNativeTarget "SerialMouse" */ = { 531 | isa = XCConfigurationList; 532 | buildConfigurations = ( 533 | 41924A0221C9AD4D0078848B /* Debug */, 534 | 41924A0321C9AD4D0078848B /* Release */, 535 | ); 536 | defaultConfigurationIsVisible = 0; 537 | defaultConfigurationName = Release; 538 | }; 539 | /* End XCConfigurationList section */ 540 | }; 541 | rootObject = 419249EE21C9AD4D0078848B /* Project object */; 542 | } 543 | -------------------------------------------------------------------------------- /SerialMouse/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | KEXT 17 | CFBundleShortVersionString 18 | ${MODULE_VERSION} 19 | CFBundleVersion 20 | ${MODULE_VERSION} 21 | IOKitPersonalities 22 | 23 | SerialMouse 24 | 25 | CFBundleIdentifier 26 | $(PRODUCT_BUNDLE_IDENTIFIER) 27 | IOClass 28 | SerialMouse 29 | IOProbeScore 30 | 5000 31 | IOProviderClass 32 | IOSerialStreamSync 33 | 34 | SerialMouseResources 35 | 36 | CFBundleIdentifier 37 | $(PRODUCT_BUNDLE_IDENTIFIER) 38 | IOClass 39 | SerialMouseResources 40 | IOMatchCategory 41 | SerialMouseResources 42 | IOProviderClass 43 | IOResources 44 | IOResourceMatch 45 | IOKit 46 | 47 | 48 | NSHumanReadableCopyright 49 | Copyright © 2018-2023 Goldfish64. All rights reserved. 50 | OSBundleCompatibleVersion 51 | ${MODULE_VERSION} 52 | OSBundleLibraries 53 | 54 | com.apple.iokit.IOHIDFamily 55 | 1.0 56 | com.apple.iokit.IOSerialFamily 57 | 8.0.0 58 | com.apple.kpi.bsd 59 | 8.0.0 60 | com.apple.kpi.iokit 61 | 8.0.0 62 | com.apple.kpi.libkern 63 | 8.0.0 64 | com.apple.kpi.mach 65 | 8.0.0 66 | 67 | OSBundleRequired 68 | Console 69 | 70 | 71 | -------------------------------------------------------------------------------- /SerialMouse/SerialMouse.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // SerialMouse.cpp 3 | // Serial mouse driver for macOS. 4 | // 5 | // Copyright © 2018-2023 Goldfish64. All rights reserved. 6 | // 7 | 8 | #include "SerialMouse.hpp" 9 | 10 | OSDefineMetaClassAndStructors(SerialMouseResources, IOService) 11 | OSDefineMetaClassAndStructors(SerialMouse, IOHIPointing) 12 | 13 | IOService *SerialMouse::probe(IOService *provider, SInt32 *score) { 14 | DBGLOG("SerialMouse: probe()\n"); 15 | if (!super::probe(provider, score)) { 16 | return nullptr; 17 | } 18 | 19 | IOReturn status; 20 | bool probed = false; 21 | IOSerialStreamSync *serialStream = OSDynamicCast(IOSerialStreamSync, provider); 22 | UInt32 origDataRate = 0, origDataSize = 0, origStopBits = 0, origFlowControl = 0; 23 | 24 | if (serialStream == nullptr) { 25 | SYSLOG("SerialMouse: Provider is not a serial stream\n"); 26 | return nullptr; 27 | } 28 | 29 | // 30 | // Acquire serial port. 31 | // 32 | if (acquirePort(serialStream) != kIOReturnSuccess) { 33 | return nullptr; 34 | } 35 | 36 | // 37 | // Save original port settings, setup stream, and check mouse ID. 38 | // 39 | do { 40 | status = getPortSettings(&origDataRate, &origDataSize, &origStopBits, &origFlowControl); 41 | if (status != kIOReturnSuccess) { 42 | SYSLOG("SerialMouse: Failed to set serial port settings during probe()\n"); 43 | break; 44 | } 45 | 46 | status = setupPort(); 47 | if (status != kIOReturnSuccess) { 48 | SYSLOG("SerialMouse: Failed to setup serial port during probe()\n"); 49 | break; 50 | } 51 | 52 | status = checkMouseId(); 53 | if (status != kIOReturnSuccess) { 54 | SYSLOG("SerialMouse: Device on serial port is not a serial mouse\n"); 55 | break; 56 | } 57 | 58 | probed = true; 59 | } while (false); 60 | 61 | // 62 | // Restore port settings and release serial port. 63 | // 64 | setPortSettings(origDataRate, origDataSize, origStopBits, origFlowControl); 65 | releasePort(); 66 | return probed ? this : nullptr; 67 | } 68 | 69 | bool SerialMouse::start(IOService *provider) { 70 | DBGLOG("SerialMouse: Starting\n"); 71 | 72 | IOReturn status; 73 | bool started = false; 74 | IOSerialStreamSync *serialStream = OSDynamicCast(IOSerialStreamSync, provider); 75 | 76 | if (serialStream == nullptr) { 77 | SYSLOG("SerialMouse: Provider is not a serial stream\n"); 78 | return false; 79 | } 80 | 81 | if (!super::start(provider)) { 82 | return false; 83 | } 84 | 85 | // 86 | // Setup port and start polling. 87 | // 88 | do { 89 | status = acquirePort(serialStream); 90 | if (status != kIOReturnSuccess) { 91 | SYSLOG("SerialMouse: Failed to acquire serial port\n"); 92 | break; 93 | } 94 | 95 | status = setupPort(); 96 | if (status != kIOReturnSuccess) { 97 | SYSLOG("SerialMouse: Failed to setup serial port during probe()\n"); 98 | break; 99 | } 100 | 101 | status = kernel_thread_start(OSMemberFunctionCast(thread_continue_t, this, &SerialMouse::pollMouseThread), 102 | this, &_pollThread); 103 | if (status != kIOReturnSuccess) { 104 | SYSLOG("SerialMouse: Polling thread could not be created\n"); 105 | break; 106 | } 107 | 108 | started = true; 109 | SYSLOG("SerialMouse: Serial mouse started\n"); 110 | } while (false); 111 | 112 | if (!started) { 113 | stop(provider); 114 | } 115 | return started; 116 | } 117 | 118 | void SerialMouse::stop(IOService *provider) { 119 | // 120 | // Kill polling thread. 121 | // 122 | releasePort(); 123 | thread_terminate(_pollThread); 124 | thread_deallocate(_pollThread); 125 | 126 | super::stop(provider); 127 | } 128 | 129 | void SerialMouse::pollMouseThread(void) { 130 | DBGLOG("SerialMouse: Polling thread\n"); 131 | 132 | UInt8 packetByte; 133 | UInt32 count = 0; 134 | UInt32 packetSequence = 0; 135 | 136 | UInt8 packet[MOUSE_PACKET_LENGTH]; 137 | 138 | while (true) { 139 | // 140 | // If serial stream is null, exit. 141 | // 142 | if (_serialStream == nullptr) { 143 | break; 144 | } 145 | 146 | // 147 | // Read next byte. If the sync is off, need to get in sync first. 148 | // 149 | if (_serialStream->dequeueData(&packetByte, 1, &count, 1) == kIOReturnSuccess) { 150 | DBGLOG("SerialMouse::pollMouseThread(): got packet byte %X seq %u\n", packetByte, packetSequence); 151 | 152 | // 153 | // If we are expecting the first byte of the packet but did not receive it, discard byte. 154 | // 155 | if (packetByte & MOUSE_PACKET_HEADER_BIT) { 156 | packetSequence = 0; 157 | } 158 | 159 | packet[packetSequence] = packetByte; 160 | packetSequence++; 161 | 162 | if (packetSequence >= MOUSE_PACKET_LENGTH) { 163 | packetSequence = 0; 164 | 165 | // Get current time. 166 | uint64_t now_abs; 167 | clock_get_uptime(&now_abs); 168 | uint64_t now_ns; 169 | absolutetime_to_nanoseconds(now_abs, &now_ns); 170 | 171 | // 172 | // Dispatch pointer movement event. 173 | // 174 | dispatchRelativePointerEvent(MOUSE_PACKET_POSX(packet), MOUSE_PACKET_POSY(packet), 175 | MOUSE_PACKET_BUTTONS(packet), *(AbsoluteTime*)&now_ns); 176 | } 177 | } 178 | } 179 | } 180 | 181 | IOReturn SerialMouse::acquirePort(IOSerialStreamSync *serialStream) { 182 | DBGLOG("SerialMouse: Acquiring serial port\n"); 183 | 184 | IOReturn status = serialStream->acquirePort(false); 185 | if (status != kIOReturnSuccess) { 186 | SYSLOG("SerialMouse: Failed to acquire serial port with status 0x%X\n", status); 187 | return status; 188 | } 189 | 190 | _serialStream = serialStream; 191 | _serialStream->retain(); 192 | return status; 193 | } 194 | 195 | void SerialMouse::releasePort() { 196 | DBGLOG("SerialMouse: Releasing serial port\n"); 197 | 198 | // 199 | // Deactivate and release port. 200 | // 201 | if (_serialStream != nullptr) { 202 | if (_serialStream->executeEvent(PD_E_ACTIVE, false) == kIOReturnSuccess) { 203 | _serialStream->releasePort(); 204 | } 205 | } 206 | OSSafeReleaseNULL(_serialStream); 207 | } 208 | 209 | IOReturn SerialMouse::setupPort() { 210 | DBGLOG("SerialMouse: Setting up port\n"); 211 | IOReturn status; 212 | 213 | // 214 | // Set up and activate port. 215 | // 216 | status = setPortSettings(MOUSE_DATA_RATE, MOUSE_DATA_SIZE, MOUSE_STOP_BITS, MOUSE_FLOW_CONTROL); 217 | if (status != kIOReturnSuccess) { 218 | return status; 219 | } 220 | return _serialStream->executeEvent(PD_E_ACTIVE, true); 221 | } 222 | 223 | IOReturn SerialMouse::flushPort() { 224 | DBGLOG("SerialMouse: Flushing port\n"); 225 | return _serialStream->executeEvent(PD_E_RXQ_FLUSH, 0); 226 | } 227 | 228 | IOReturn SerialMouse::checkMouseId() { 229 | DBGLOG("SerialMouse: Checking mouse ID\n"); 230 | IOReturn status; 231 | UInt8 mouseId = 0; 232 | UInt32 count = 0; 233 | 234 | // 235 | // Flush receive buffer. 236 | // 237 | status = flushPort(); 238 | if (status != kIOReturnSuccess) { 239 | return status; 240 | } 241 | 242 | // 243 | // Toggle DTR bit. 244 | // 245 | status = _serialStream->executeEvent(PD_E_FLOW_CONTROL, MOUSE_FLOW_CONTROL); 246 | if (status != kIOReturnSuccess) { 247 | return status; 248 | } 249 | status = _serialStream->executeEvent(PD_E_FLOW_CONTROL, MOUSE_FLOW_CONTROL & ~PD_RS232_S_DTR); 250 | if (status != kIOReturnSuccess) { 251 | return status; 252 | } 253 | status = _serialStream->executeEvent(PD_E_FLOW_CONTROL, MOUSE_FLOW_CONTROL); 254 | if (status != kIOReturnSuccess) { 255 | return status; 256 | } 257 | 258 | // 259 | // Read ID byte. 260 | // 261 | IOSleep(MOUSE_ID_DELAY_MS); 262 | status = _serialStream->dequeueData(&mouseId, 1, &count, 0); 263 | DBGLOG("SerialMouse::checkMouseId(): device returned ID byte 0x%X\n", mouseId); 264 | if (status != kIOReturnSuccess) { 265 | return status; 266 | } 267 | 268 | // 269 | // Ensure mouse ID byte is valid. 270 | // 271 | if (mouseId != MOUSE_ID_BYTE) { 272 | return kIOReturnInvalid; 273 | } 274 | return kIOReturnSuccess; 275 | } 276 | 277 | IOReturn SerialMouse::getPortSettings(UInt32 *dataRate, UInt32 *dataSize, UInt32 *stopBits, UInt32 *flowControl) { 278 | DBGLOG("SerialMouse: Get port settings\n"); 279 | IOReturn status; 280 | 281 | // 282 | // Get port settings. 283 | // 284 | status = _serialStream->requestEvent(PD_E_DATA_RATE, dataRate); 285 | if (status != kIOReturnSuccess) { 286 | return status; 287 | } 288 | status = _serialStream->requestEvent(PD_E_DATA_SIZE, dataSize); 289 | if (status != kIOReturnSuccess) { 290 | return status; 291 | } 292 | status = _serialStream->requestEvent(PD_RS232_E_STOP_BITS, stopBits); 293 | if (status != kIOReturnSuccess) { 294 | return status; 295 | } 296 | return _serialStream->requestEvent(PD_E_FLOW_CONTROL, flowControl); 297 | } 298 | 299 | IOReturn SerialMouse::setPortSettings(UInt32 dataRate, UInt32 dataSize, UInt32 stopBits, UInt32 flowControl) { 300 | DBGLOG("SerialMouse: Set port settings(%u,%u,%u,%u)\n", dataRate, dataSize, stopBits, flowControl); 301 | IOReturn status; 302 | 303 | // 304 | // Set port settings. 305 | // 306 | status = _serialStream->executeEvent(PD_E_DATA_RATE, dataRate); 307 | if (status != kIOReturnSuccess) { 308 | return status; 309 | } 310 | status = _serialStream->executeEvent(PD_E_DATA_SIZE, dataSize); 311 | if (status != kIOReturnSuccess) { 312 | return status; 313 | } 314 | status = _serialStream->executeEvent(PD_RS232_E_STOP_BITS, stopBits); 315 | if (status != kIOReturnSuccess) { 316 | return status; 317 | } 318 | return _serialStream->executeEvent(PD_E_FLOW_CONTROL, flowControl); 319 | } 320 | -------------------------------------------------------------------------------- /SerialMouse/SerialMouse.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // SerialMouse.hpp 3 | // Serial mouse driver for macOS. 4 | // 5 | // Copyright © 2018-2023 Goldfish64. All rights reserved. 6 | // 7 | 8 | #ifndef SerialMouse_hpp 9 | #define SerialMouse_hpp 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | // Debug logging. 22 | #if DEBUG 23 | #define DBGLOG(args...) IOLog(args) 24 | #else 25 | #define DBGLOG(args...) ; 26 | #endif 27 | 28 | #define SYSLOG(args...) IOLog(args) 29 | 30 | #define bits <<1 31 | 32 | // 33 | // Serial settings for MS protocol. 34 | // 35 | #define MOUSE_DATA_RATE (1200 bits) 36 | #define MOUSE_DATA_SIZE (7 bits) 37 | #define MOUSE_STOP_BITS (1 bits) 38 | #define MOUSE_FLOW_CONTROL (PD_RS232_S_RTS | PD_RS232_S_DTR) 39 | #define MOUSE_ID_DELAY_MS 100 40 | #define MOUSE_ID_BYTE 0x4D // 'M' 41 | 42 | #define MOUSE_POLL_DELAY_MS 100 43 | 44 | // HID buttons. 45 | #define HID_MOUSE_LEFTB 0x1 46 | #define HID_MOUSE_RIGHTB 0x2 47 | 48 | // 49 | // Serial mouse packet format: 50 | // 51 | // 7 6 5 4 3 2 1 0 52 | // X 1 LB RB Y7 Y6 X7 X6 53 | // X 0 X5 X4 X3 X2 X1 X0 54 | // X 0 Y5 Y4 Y3 Y2 Y1 Y0 55 | // 56 | 57 | // 58 | // Serial mouse packet stuff. 59 | // 60 | #define MOUSE_PACKET_LENGTH 3 61 | #define MOUSE_PACKET_HEADER_BIT 0x40 62 | #define MOUSE_PACKET_LEFTB_BIT 0x20 63 | #define MOUSE_PACKET_RIGHTB_BIT 0x10 64 | 65 | #define MOUSE_PACKET_VALID(packet) ((Boolean)(packet[0] & MOUSE_PACKET_HEADER_BIT)) 66 | #define MOUSE_PACKET_LEFTB(packet) ((Boolean)(packet[0] & MOUSE_PACKET_LEFTB_BIT)) 67 | #define MOUSE_PACKET_RIGHTB(packet) ((Boolean)(packet[0] & MOUSE_PACKET_RIGHTB_BIT)) 68 | #define MOUSE_PACKET_BUTTONS(packet) ((UInt32)((MOUSE_PACKET_LEFTB(packet) ? HID_MOUSE_LEFTB : 0) | \ 69 | (MOUSE_PACKET_RIGHTB(packet) ? HID_MOUSE_RIGHTB : 0))) 70 | #define MOUSE_PACKET_POSX(packet) ((SInt8)((packet[1] & 0x3F) | ((packet[0] & 0x3) << 6))) 71 | #define MOUSE_PACKET_POSY(packet) ((SInt8)((packet[2] & 0x3F) | ((packet[0] & 0xC) << 4))) 72 | 73 | // 74 | // SerialMouseResources class. This is used to keep the kext in memory. 75 | // 76 | class SerialMouseResources : IOService { 77 | OSDeclareDefaultStructors(SerialMouseResources); 78 | }; 79 | 80 | class SerialMouse : IOHIPointing { 81 | typedef IOHIPointing super; 82 | OSDeclareDefaultStructors(SerialMouse); 83 | 84 | private: 85 | // 86 | // Serial stream. 87 | // 88 | IOSerialStreamSync *_serialStream = nullptr; 89 | 90 | // 91 | // Polling thread. 92 | // 93 | thread_t _pollThread = nullptr; 94 | void pollMouseThread(); 95 | 96 | IOReturn acquirePort(IOSerialStreamSync *serialStream); 97 | void releasePort(); 98 | IOReturn setupPort(); 99 | IOReturn flushPort(); 100 | IOReturn checkMouseId(); 101 | 102 | IOReturn getPortSettings(UInt32 *dataRate, UInt32 *dataSize, UInt32 *stopBits, UInt32 *flowControl); 103 | IOReturn setPortSettings(UInt32 dataRate, UInt32 dataSize, UInt32 stopBits, UInt32 flowControl); 104 | 105 | public: 106 | // 107 | // IOService overrides. 108 | // 109 | virtual IOService *probe(IOService *provider, SInt32 *score) APPLE_KEXT_OVERRIDE; 110 | virtual bool start(IOService *provider) APPLE_KEXT_OVERRIDE; 111 | virtual void stop(IOService *provider) APPLE_KEXT_OVERRIDE; 112 | }; 113 | 114 | #endif 115 | -------------------------------------------------------------------------------- /SerialMouse/package.tool: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "${TARGET_BUILD_DIR}" = "" ]; then 4 | echo "This must not be run outside of Xcode" 5 | exit 1 6 | fi 7 | 8 | cd "${TARGET_BUILD_DIR}" 9 | 10 | # clean / build 11 | if [ "$1" != "analyze" ]; then 12 | rm -rf package *.zip || exit 1 13 | fi 14 | 15 | if [ "$1" != "" ]; then 16 | echo "Got action $1, skipping!" 17 | exit 0 18 | fi 19 | 20 | # Xcode 10.2 build system seems to have a race condition between 21 | # dependency scheduling, and for some reason does not complete 22 | # the compilation of dependencies even though it should have been. 23 | # Adding a delay here "fixes" it. TODO: bugreport. 24 | if [ ! -f ../libaistat.dylib ]; then 25 | sleep 5 26 | fi 27 | 28 | # Workaround Xcode 10 bug 29 | if [ "$CONFIGURATION" = "" ]; then 30 | if [ "$(basename "$TARGET_BUILD_DIR")" = "Debug" ]; then 31 | CONFIGURATION="Debug" 32 | elif [ "$(basename "$TARGET_BUILD_DIR")" = "Sanitize" ]; then 33 | CONFIGURATION="Sanitize" 34 | else 35 | CONFIGURATION="Release" 36 | fi 37 | fi 38 | 39 | if [ "$CONFIGURATION" = "Release" ]; then 40 | mkdir -p dSYM || exit 1 41 | for dsym in ../*.dSYM; do 42 | if [ "$dsym" = "../*.dSYM" ]; then 43 | continue 44 | fi 45 | echo "$dsym" 46 | cp -a "$dsym" dSYM/ || exit 1 47 | done 48 | fi 49 | 50 | archive="SerialMouse-${MODULE_VERSION}-$(echo $CONFIGURATION | tr /a-z/ /A-Z/).zip" 51 | zip -qry -FS "${archive}" * || exit 1 52 | --------------------------------------------------------------------------------