Full Code of lujjjh/LinearMouse for AI

main 169f52014a0f cached
308 files
2.6 MB
698.7k tokens
179 symbols
1 requests
Download .txt
Showing preview only (2,873K chars total). Download the full file or copy to clipboard to get everything.
Repository: lujjjh/LinearMouse
Branch: main
Commit: 169f52014a0f
Files: 308
Total size: 2.6 MB

Directory structure:
gitextract_23xgv3jw/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── release.yml
│   └── workflows/
│       ├── add-coauthor.yml
│       ├── build.yml
│       └── stale.yml
├── .gitignore
├── .swift-version
├── .swiftformat
├── .swiftlint.yml
├── .vscode/
│   └── settings.json
├── ACCESSIBILITY.md
├── CLAUDE.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING-cn.md
├── CONTRIBUTING.md
├── Config.xcconfig
├── Documentation/
│   ├── Configuration.d.ts
│   ├── Configuration.json
│   ├── Configuration.md
│   └── translate-xcstrings.md
├── ExportOptions.plist
├── LICENSE
├── LinearMouse/
│   ├── AccessibilityPermission.swift
│   ├── AppDelegate.swift
│   ├── Assets.xcassets/
│   │   ├── AccentColor.colorset/
│   │   │   └── Contents.json
│   │   ├── AccessibilityIcon.imageset/
│   │   │   └── Contents.json
│   │   ├── AppIcon.appiconset/
│   │   │   └── Contents.json
│   │   ├── AppIconDev.appiconset/
│   │   │   └── Contents.json
│   │   ├── Contents.json
│   │   ├── MenuIcon.imageset/
│   │   │   └── Contents.json
│   │   ├── Minus.imageset/
│   │   │   └── Contents.json
│   │   ├── Plus.imageset/
│   │   │   └── Contents.json
│   │   └── Sidebar/
│   │       ├── Buttons.imageset/
│   │       │   └── Contents.json
│   │       ├── Contents.json
│   │       ├── General.imageset/
│   │       │   └── Contents.json
│   │       ├── Modifier Keys.imageset/
│   │       │   └── Contents.json
│   │       ├── Pointer.imageset/
│   │       │   └── Contents.json
│   │       └── Scrolling.imageset/
│   │           └── Contents.json
│   ├── AutoUpdateManager.swift
│   ├── DefaultsKeys.swift
│   ├── Device/
│   │   ├── Device.swift
│   │   ├── DeviceManager.swift
│   │   ├── InputReportHandler.swift
│   │   ├── InputReportHandlers/
│   │   │   ├── GenericSideButtonHandler.swift
│   │   │   └── KensingtonSlimbladeHandler.swift
│   │   ├── ReceiverLogicalDeviceIdentity.swift
│   │   ├── ReceiverMonitor.swift
│   │   └── VendorSpecific/
│   │       ├── BatteryDeviceMonitor.swift
│   │       ├── ConnectedBatteryDeviceInventory.swift
│   │       ├── ConnectedLogitechDeviceInventory.swift
│   │       ├── Logitech/
│   │       │   └── LogitechHIDPPDeviceMetadataProvider.swift
│   │       ├── PointerDevice+VendorSpecificDeviceContext.swift
│   │       └── VendorSpecificDeviceMetadata.swift
│   ├── EventTap/
│   │   ├── EventTap.swift
│   │   ├── EventThread.swift
│   │   ├── EventType.swift
│   │   ├── GlobalEventTap.swift
│   │   └── GlobalEventTapWatchdog.swift
│   ├── EventTransformer/
│   │   ├── AutoScrollTransformer.swift
│   │   ├── ButtonActionsTransformer.swift
│   │   ├── ClickDebouncingTransformer.swift
│   │   ├── EventTransformer.swift
│   │   ├── EventTransformerManager.swift
│   │   ├── GestureButtonTransformer.swift
│   │   ├── LinearScrollingHorizontalTransformer.swift
│   │   ├── LinearScrollingVerticalTransformer.swift
│   │   ├── LogitechEventContext.swift
│   │   ├── ModifierActionsTransformer.swift
│   │   ├── PointerRedirectsToScrollTransformer.swift
│   │   ├── ReverseScrollingTransformer.swift
│   │   ├── ScrollingAccelerationSpeedAdjustmentTransformer.swift
│   │   ├── SmoothedScrollingEngine.swift
│   │   ├── SmoothedScrollingTransformer.swift
│   │   ├── SwitchPrimaryAndSecondaryButtonsTransformer.swift
│   │   └── UniversalBackForwardTransformer.swift
│   ├── EventView/
│   │   ├── EventView.swift
│   │   ├── MouseEventView.swift
│   │   └── ScrollWheelEventView.swift
│   ├── Info.plist
│   ├── LinearMouse-Bridging-Header.h
│   ├── LinearMouse.entitlements
│   ├── LinearMouse.swift
│   ├── Localizable.xcstrings
│   ├── Model/
│   │   ├── AppTarget.swift
│   │   ├── Configuration/
│   │   │   ├── Configuration.swift
│   │   │   ├── DeviceMatcher.swift
│   │   │   └── Scheme/
│   │   │       ├── Buttons/
│   │   │       │   ├── AutoScroll/
│   │   │       │   │   └── AutoScroll.swift
│   │   │       │   ├── Buttons.swift
│   │   │       │   ├── ClickDebouncing/
│   │   │       │   │   └── ClickDebouncing.swift
│   │   │       │   ├── Gesture/
│   │   │       │   │   └── Gesture.swift
│   │   │       │   └── Mapping/
│   │   │       │       ├── Action+Codable.swift
│   │   │       │       ├── Action+CustomStringConvertible.swift
│   │   │       │       ├── Action+Kind.swift
│   │   │       │       ├── Action.swift
│   │   │       │       └── Mapping.swift
│   │   │       ├── If/
│   │   │       │   └── If.swift
│   │   │       ├── Pointer.swift
│   │   │       ├── Scheme.swift
│   │   │       └── Scrolling/
│   │   │           ├── Bidirectional.swift
│   │   │           ├── Distance+Mode.swift
│   │   │           ├── Distance.swift
│   │   │           ├── Modifiers+Kind.swift
│   │   │           ├── Modifiers.swift
│   │   │           ├── Scrolling.swift
│   │   │           └── Smoothed.swift
│   │   └── DeviceModel.swift
│   ├── ModifierKeys.swift
│   ├── ScreenManager/
│   │   └── ScreenManager.swift
│   ├── State/
│   │   ├── ConfigurationState.swift
│   │   ├── DeviceState.swift
│   │   ├── ModifierState.swift
│   │   ├── SchemeState.swift
│   │   └── SettingsState.swift
│   ├── UI/
│   │   ├── AccessibilityPermissionView.swift
│   │   ├── AccessibilityPermissionWindow.swift
│   │   ├── Base.lproj/
│   │   │   └── Main.storyboard
│   │   ├── ButtonStyle/
│   │   │   └── SecondaryButtonStyle.swift
│   │   ├── ButtonsSettings/
│   │   │   ├── AutoScrollSection/
│   │   │   │   └── AutoScrollSection.swift
│   │   │   ├── ButtonMappingsSection/
│   │   │   │   ├── ButtonMapping.swift
│   │   │   │   ├── ButtonMappingAction/
│   │   │   │   │   ├── ButtonMappingAction+Binding.swift
│   │   │   │   │   ├── ButtonMappingAction.swift
│   │   │   │   │   ├── ButtonMappingActionKeyPress.swift
│   │   │   │   │   ├── ButtonMappingActionPicker.swift
│   │   │   │   │   ├── ButtonMappingActionRun.swift
│   │   │   │   │   ├── ButtonMappingActionScroll.swift
│   │   │   │   │   └── ScrollingDistance+Binding.swift
│   │   │   │   ├── ButtonMappingButtonRecorder.swift
│   │   │   │   ├── ButtonMappingEditSheet.swift
│   │   │   │   └── ButtonMappingsSection.swift
│   │   │   ├── ButtonsSettings.swift
│   │   │   ├── ButtonsSettingsState.swift
│   │   │   ├── ClickDebouncingSection.swift
│   │   │   ├── GestureButtonSection/
│   │   │   │   ├── GestureActionPicker.swift
│   │   │   │   └── GestureButtonSection.swift
│   │   │   ├── SwitchPrimaryAndSecondaryButtonsSection.swift
│   │   │   └── UniversalBackForwardSection.swift
│   │   ├── CheckForUpdatesView.swift
│   │   ├── DetailView/
│   │   │   └── DetailView.swift
│   │   ├── Extensions/
│   │   │   ├── NSWindow.swift
│   │   │   └── View.swift
│   │   ├── GeneralSettings/
│   │   │   ├── ConfigurationSection.swift
│   │   │   ├── GeneralSettings.swift
│   │   │   └── LoggingSection.swift
│   │   ├── Header/
│   │   │   ├── AppIndicator/
│   │   │   │   ├── AppIndicator.swift
│   │   │   │   └── AppPickerSheet/
│   │   │   │       ├── AppPicker.swift
│   │   │   │       ├── AppPickerSheet.swift
│   │   │   │       └── AppPickerState.swift
│   │   │   ├── DeviceIndicator/
│   │   │   │   ├── DeviceIndicator.swift
│   │   │   │   ├── DeviceIndicatorState.swift
│   │   │   │   └── DevicePickerSheet/
│   │   │   │       ├── DeviceButtonStyle.swift
│   │   │   │       ├── DevicePicker.swift
│   │   │   │       ├── DevicePickerBatteryCoordinator.swift
│   │   │   │       ├── DevicePickerSection.swift
│   │   │   │       ├── DevicePickerSectionItem.swift
│   │   │   │       ├── DevicePickerSheet.swift
│   │   │   │       └── DevicePickerState.swift
│   │   │   ├── DisplayIndicator/
│   │   │   │   ├── DisplayIndicator.swift
│   │   │   │   └── DisplayPickerSheet/
│   │   │   │       ├── DisplayPicker.swift
│   │   │   │       ├── DisplayPickerSheet.swift
│   │   │   │       └── DisplayPickerState.swift
│   │   │   └── SchemeIndicator.swift
│   │   ├── HelpButton/
│   │   │   └── HelpButton.swift
│   │   ├── HyperLink.swift
│   │   ├── PointerSettings/
│   │   │   ├── PointerSettings.swift
│   │   │   └── PointerSettingsState.swift
│   │   ├── ScrollingSettings/
│   │   │   ├── Header.swift
│   │   │   ├── ModifierKeysSection/
│   │   │   │   ├── ModifierAction+Binding.swift
│   │   │   │   ├── ModifierKeyActionPicker.swift
│   │   │   │   └── ModifierKeysSection.swift
│   │   │   ├── ReverseScrollingSection.swift
│   │   │   ├── ScrollingModeSection.swift
│   │   │   ├── ScrollingSettings.swift
│   │   │   ├── ScrollingSettingsState.swift
│   │   │   └── SmoothedScrollingSection.swift
│   │   ├── Settings.swift
│   │   ├── SettingsWindowController.swift
│   │   ├── Sidebar/
│   │   │   ├── Sidebar.swift
│   │   │   └── SidebarItem.swift
│   │   ├── StatusItem.swift
│   │   ├── ViewModifiers/
│   │   │   └── FormViewModifier.swift
│   │   ├── VisualEffectView.swift
│   │   └── zh-Hant-HK.lproj/
│   │       └── Main.strings
│   └── Utilities/
│       ├── Application.swift
│       ├── ApplicationBundle.swift
│       ├── CGEvent+LinearMouseSynthetic.swift
│       ├── Codable/
│       │   ├── Clamp.swift
│       │   ├── HexRepresentation.swift
│       │   ├── ImplicitOptional.swift
│       │   ├── SingleValueOrArray.swift
│       │   └── Unsettable.swift
│       ├── CustomDecodingError.swift
│       ├── Extensions.swift
│       ├── KeyboardSettingsSnapshot.swift
│       ├── Notifier.swift
│       ├── Process.h
│       ├── Process.m
│       ├── ProcessEnvironment.swift
│       ├── WeakRef.swift
│       └── WindowInfo.swift
├── LinearMouse.xcodeproj/
│   ├── project.pbxproj
│   ├── project.xcworkspace/
│   │   ├── contents.xcworkspacedata
│   │   └── xcshareddata/
│   │       ├── IDEWorkspaceChecks.plist
│   │       └── swiftpm/
│   │           └── Package.resolved
│   └── xcshareddata/
│       └── xcschemes/
│           └── LinearMouse.xcscheme
├── LinearMouseUnitTests/
│   ├── Device/
│   │   ├── InputReportHandlerTests.swift
│   │   └── VendorSpecificDeviceMetadataTests.swift
│   ├── EventTransformer/
│   │   ├── AutoScrollTransformerTests.swift
│   │   ├── ButtonActionsTransformerTests.swift
│   │   ├── EventTransformerManagerTests.swift
│   │   ├── LinearScrollingTransformerTests.swift
│   │   ├── ModifierActionsTransformerTests.swift
│   │   ├── ReverseScrollingTransformerTests.swift
│   │   ├── SmoothedScrollingEngineTests.swift
│   │   ├── SmoothedScrollingTransformerTests.swift
│   │   └── UniversalBackForwardTransformerTests.swift
│   ├── Model/
│   │   ├── ConfigurationTests.swift
│   │   └── Scheme/
│   │       ├── Buttons/
│   │       │   ├── GestureTests.swift
│   │       │   └── MappingActionKindTests.swift
│   │       └── Scrolling/
│   │           ├── BidirectionalTests.swift
│   │           ├── DistanceModeTests.swift
│   │           └── ModifiersKindTests.swift
│   ├── UI/
│   │   ├── ButtonMappingActionBindingTests.swift
│   │   ├── ModifierKeyActionPickerTests.swift
│   │   ├── ScrollingDistanceBindingTests.swift
│   │   └── StatusItemBatteryIndicatorTests.swift
│   └── Utilities/
│       ├── Codable/
│       │   └── ImplicitOptionalTests.swift
│       └── KeyboardSettingsSnapshotTests.swift
├── Makefile
├── Modules/
│   ├── DockKit/
│   │   ├── .gitignore
│   │   ├── Package.swift
│   │   ├── README.md
│   │   ├── Sources/
│   │   │   ├── DockKit/
│   │   │   │   └── DockKit.swift
│   │   │   └── DockKitC/
│   │   │       ├── DockKitC.m
│   │   │       └── include/
│   │   │           └── ApplicationServicesSPI.h
│   │   └── Tests/
│   │       └── DockKitTests/
│   │           └── DockKitTests.swift
│   ├── GestureKit/
│   │   ├── .gitignore
│   │   ├── Package.swift
│   │   ├── README.md
│   │   ├── Sources/
│   │   │   └── GestureKit/
│   │   │       ├── CGEventField+Extensions.swift
│   │   │       ├── CGEventType+Extensions.swift
│   │   │       ├── GestureEvent+NavigationSwipe.swift
│   │   │       ├── GestureEvent+Zoom.swift
│   │   │       └── GestureEvent.swift
│   │   └── Tests/
│   │       └── GestureKitTests/
│   │           └── GestureKitTests.swift
│   ├── KeyKit/
│   │   ├── .gitignore
│   │   ├── Package.swift
│   │   ├── README.md
│   │   ├── Sources/
│   │   │   ├── KeyKit/
│   │   │   │   ├── Key.swift
│   │   │   │   ├── KeyCodeResolver.swift
│   │   │   │   ├── KeySimulator.swift
│   │   │   │   ├── SymbolicHotKey.swift
│   │   │   │   └── SystemDefinedKey.swift
│   │   │   └── KeyKitC/
│   │   │       ├── CGSInternal/
│   │   │       │   ├── CGSAccessibility.h
│   │   │       │   ├── CGSCIFilter.h
│   │   │       │   ├── CGSConnection.h
│   │   │       │   ├── CGSCursor.h
│   │   │       │   ├── CGSDebug.h
│   │   │       │   ├── CGSDevice.h
│   │   │       │   ├── CGSDisplays.h
│   │   │       │   ├── CGSEvent.h
│   │   │       │   ├── CGSHotKeys.h
│   │   │       │   ├── CGSInternal.h
│   │   │       │   ├── CGSMisc.h
│   │   │       │   ├── CGSRegion.h
│   │   │       │   ├── CGSSession.h
│   │   │       │   ├── CGSSpace.h
│   │   │       │   ├── CGSSurface.h
│   │   │       │   ├── CGSTile.h
│   │   │       │   ├── CGSTransitions.h
│   │   │       │   ├── CGSWindow.h
│   │   │       │   └── CGSWorkspace.h
│   │   │       ├── KeyKitC.m
│   │   │       └── include/
│   │   │           └── KeyKitC.h
│   │   └── Tests/
│   │       └── KeyKitTests/
│   │           └── KeyKitTests.swift
│   ├── ObservationToken/
│   │   ├── .gitignore
│   │   ├── Package.swift
│   │   ├── README.md
│   │   ├── Sources/
│   │   │   └── ObservationToken/
│   │   │       ├── LifetimeAssociation.swift
│   │   │       └── ObservationToken.swift
│   │   └── Tests/
│   │       └── ObservationTokenTests/
│   │           └── ObservationTokenTests.swift
│   ├── PointerKit/
│   │   ├── .gitignore
│   │   ├── Package.swift
│   │   ├── README.md
│   │   ├── Sources/
│   │   │   ├── PointerKit/
│   │   │   │   ├── Extensions/
│   │   │   │   │   ├── Comparable+Extensions.swift
│   │   │   │   │   ├── Dictionary+Extensions.swift
│   │   │   │   │   ├── IOHIDElement+Extensions.swift
│   │   │   │   │   ├── IOHIDServiceClient+Property.swift
│   │   │   │   │   ├── IOHIDServiceClient+Service.swift
│   │   │   │   │   └── IOHIDValue+Extensions.swift
│   │   │   │   ├── PointerDevice.swift
│   │   │   │   └── PointerDeviceManager.swift
│   │   │   └── PointerKitC/
│   │   │       ├── PointerKitC.m
│   │   │       └── include/
│   │   │           └── IOKitSPIMac.h
│   │   └── Tests/
│   │       └── PointerKitTests/
│   │           └── PointerDeviceManagerTest.swift
│   └── README.md
├── README-cn.md
├── README.md
├── Scripts/
│   ├── configure-code-signing
│   ├── configure-release
│   ├── configure-version
│   ├── pre-commit
│   ├── sign-and-notarize
│   └── translate-xcstrings.mjs
├── Signing.xcconfig.tpl
├── crowdin.yml
└── package.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Bug report
description: Create a report to help us improve.
labels: bug
body:
- type: input
  id: os
  attributes:
    label: OS
    placeholder: macOS 13.1 (Build 22C65)
  validations:
    required: true
- type: input
  id: linearmouse
  attributes:
    label: LinearMouse
    placeholder: v0.7.5
  validations:
    required: true
- type: textarea
  attributes:
    label: Describe the bug
    description: A clear and concise description of what the bug is.
  validations:
    required: true
- type: textarea
  attributes:
    label: To reproduce
    description: Steps to reproduce the behavior.
    placeholder: |
      1. In this environment...
      2. With this config...
      3. Run '...'
      4. See error...
  validations:
    required: false
- type: textarea
  attributes:
    label: Expected behavior
    description: A clear and concise description of what you expected to happen.
  validations:
    required: false
- type: textarea
  attributes:
    label: Anything else?
    description: |
      Links? References? Anything that will give us more context about the issue you are encountering!

      Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
  validations:
    required: false


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
contact_links:
  - name: Discussions
    url: https://github.com/linearmouse/linearmouse/discussions
    about: Please ask and answer questions here.


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Feature request
description: Suggest an idea for this project.
labels: enhancement
body:
- type: input
  id: os
  attributes:
    label: OS
    placeholder: macOS 13.1 (Build 22C65)
  validations:
    required: false
- type: input
  id: linearmouse
  attributes:
    label: LinearMouse
    placeholder: v0.7.5
  validations:
    required: false
- type: textarea
  attributes:
    label: Is your feature request related to a problem?
    description: A clear and concise description of what the problem is.
    placeholder: I'm always frustrated when [...]
  validations:
    required: false
- type: textarea
  attributes:
    label: Describe the solution you'd like
    description: A clear and concise description of what you want to happen.
    placeholder: I'd like to have per-app settings [...]
  validations:
    required: true
- type: textarea
  attributes:
    label: Describe alternatives you've considered
    description: A clear and concise description of any alternative solutions or features you've considered.
  validations:
    required: false
- type: textarea
  attributes:
    label: Additional context
    description: |
      Add any other context or screenshots about the feature request here.

      Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in.
  validations:
    required: false


================================================
FILE: .github/release.yml
================================================
changelog:
  exclude:
    labels:
      - ignore for release
  categories:
    - title: New features
      labels:
        - enhancement
    - title: Bug fixes
      labels:
        - bug
    - title: Other changes
      labels:
        - "*"


================================================
FILE: .github/workflows/add-coauthor.yml
================================================
name: Add Co-Author to PR

on:
  issue_comment:
    types: [created]

jobs:
  add-coauthor:
    runs-on: ubuntu-latest
    if: ${{ github.event.issue.pull_request && startsWith(github.event.comment.body, '/add-author') }}

    permissions:
      pull-requests: write
      contents: write

    steps:
      - name: Checkout repository
        uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - name: Add co-author to commit
        uses: actions/github-script@v6
        with:
          script: |
            try {
              // Parse the comment to extract the GitHub username
              const commentBody = context.payload.comment.body;
              const match = commentBody.match(/\/add-author\s+(\S+)/);

              if (!match || !match[1]) {
                return;
              }
              
              const username = match[1];
              
              // Get user information from GitHub API
              let userInfo;
              try {
                const { data } = await github.rest.users.getByUsername({
                  username: username
                });
                userInfo = data;
              } catch (error) {
                await github.rest.issues.createComment({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: context.payload.issue.number,
                  body: `Error: Could not find GitHub user "${username}"`
                });
                return;
              }
              
              // Construct the co-author line
              // GitHub noreply email format: ID+username@users.noreply.github.com
              const githubEmail = `${userInfo.id}+${username}@users.noreply.github.com`;
              const coAuthorLine = `Co-authored-by: ${userInfo.name || username} <${githubEmail}>`;
              
              // Get the PR details to find the head branch and latest commit
              const { data: pullRequest } = await github.rest.pulls.get({
                owner: context.repo.owner,
                repo: context.repo.repo,
                pull_number: context.payload.issue.number
              });
              
              const headRef = pullRequest.head.ref;
              const headSha = pullRequest.head.sha;
              
              // Get the latest commit message
              const { data: commit } = await github.rest.git.getCommit({
                owner: context.repo.owner,
                repo: context.repo.repo,
                commit_sha: headSha
              });
              
              let commitMessage = commit.message;
              
              // Check if the co-author is already in the commit message
              if (commitMessage.includes(coAuthorLine)) {
                // Add a "eyes" reaction to indicate the co-author is already there
                await github.rest.reactions.createForIssueComment({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  comment_id: context.payload.comment.id,
                  content: 'eyes'
                });
                return;
              }
              
              // Add the co-author line to the commit message
              if (!commitMessage.includes('\n\n')) {
                commitMessage = `${commitMessage}\n\n${coAuthorLine}`;
              } else if (!commitMessage.endsWith('\n')) {
                commitMessage = `${commitMessage}\n${coAuthorLine}`;
              } else {
                commitMessage = `${commitMessage}${coAuthorLine}`;
              }
              
              // Create a new commit with the updated message (amend)
              const { data: latestCommit } = await github.rest.git.getCommit({
                owner: context.repo.owner,
                repo: context.repo.repo,
                commit_sha: headSha
              });
              
              const { data: tree } = await github.rest.git.getTree({
                owner: context.repo.owner,
                repo: context.repo.repo,
                tree_sha: latestCommit.tree.sha
              });
              
              const newCommit = await github.rest.git.createCommit({
                owner: context.repo.owner,
                repo: context.repo.repo,
                message: commitMessage,
                tree: latestCommit.tree.sha,
                parents: [headSha]
              });
              
              // Update the reference
              await github.rest.git.updateRef({
                owner: context.repo.owner,
                repo: context.repo.repo,
                ref: `heads/${headRef}`,
                sha: newCommit.data.sha,
                force: true
              });
              
              // Add a "rocket" reaction to indicate success
              await github.rest.reactions.createForIssueComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                comment_id: context.payload.comment.id,
                content: 'rocket'
              });
            } catch (error) {
              console.error('Error:', error);
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.payload.issue.number,
                body: `Error adding co-author: ${error.message}`
              });
            }


================================================
FILE: .github/workflows/build.yml
================================================
name: Build
on:
  push:
    branches:
      - main
    tags:
      - v*
  pull_request:
    branches:
      - main

jobs:
  build:
    runs-on: macos-26
    env:
      APPLE_ID: ${{ secrets.APPLE_ID }}
      CODE_SIGN_IDENTITY: ${{ secrets.CODE_SIGN_IDENTITY }}
      DEVELOPMENT_TEAM: ${{ secrets.DEVELOPMENT_TEAM }}
    steps:
      - name: Install dependencies
        run: |
          brew update
          brew upgrade swiftformat
          brew install swiftlint
      - name: Checkout repository
        uses: actions/checkout@v4
      - name: Install the Apple certificate
        env:
          BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
          KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
          P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
        if: github.event_name != 'pull_request' && env.BUILD_CERTIFICATE_BASE64 != null
        run: |
          # create variables
          CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
          KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db

          # import certificate from secrets
          echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode --output $CERTIFICATE_PATH

          # create temporary keychain
          security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
          security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
          security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH

          # import certificate to keychain
          security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
          security list-keychain -d user -s $KEYCHAIN_PATH

          # Configure code signing
          make configure-release
      - name: Build pull request
        if: github.event_name == 'pull_request'
        run: make configure clean lint test XCODEBUILD_ARGS="CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY= DEVELOPMENT_TEAM="
      - name: Build
        if: github.event_name != 'pull_request'
        run: make
      - name: Prepublish
        if: startsWith(github.ref, 'refs/tags/')
        env:
          NOTARIZATION_PASSWORD: ${{ secrets.NOTARIZATION_PASSWORD }}
        run: make prepublish
      - name: Prepare dSYM archive
        if: startsWith(github.ref, 'refs/tags/')
        run: cd build/LinearMouse.xcarchive/dSYMs && zip -r "$GITHUB_WORKSPACE/build/LinearMouse.dSYM.zip" LinearMouse.app.dSYM
      - name: Upload dmg
        if: github.event_name != 'pull_request'
        uses: actions/upload-artifact@v4
        with:
          name: LinearMouse.dmg
          path: build/LinearMouse.dmg
          retention-days: 7
      - name: Release
        uses: softprops/action-gh-release@v2
        if: startsWith(github.ref, 'refs/tags/')
        with:
          draft: true
          prerelease: ${{ contains(github.ref, '-') }}
          files: |
            build/LinearMouse.dmg
            build/LinearMouse.dSYM.zip
          fail_on_unmatched_files: true
          generate_release_notes: true


================================================
FILE: .github/workflows/stale.yml
================================================
name: "Close stale issues and PRs"
on:
  schedule:
    - cron: "0 0 * * *"
  issue_comment:
    types: [created]

jobs:
  stale:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/stale@v7
        with:
          stale-issue-message: "This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days."
          stale-pr-message: "This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 10 days."
          close-issue-message: "This issue was closed because it has been stalled for 5 days with no activity."
          close-pr-message: "This PR was closed because it has been stalled for 10 days with no activity."
          days-before-issue-stale: 60
          days-before-pr-stale: 60
          days-before-issue-close: 5
          days-before-pr-close: 10
          stale-issue-label: stale
          stale-pr-label: stale
          exempt-issue-labels: help wanted
          exempt-draft-pr: true
          exempt-assignees: lujjjh


================================================
FILE: .gitignore
================================================
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

## User settings
xcuserdata/

## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout

## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3

## Gcc Patch
/*.gcno

## Configuration
/Signing.xcconfig
/Version.xcconfig
/Release.xcconfig

# macOS
.DS_Store

# Node.js
node_modules


================================================
FILE: .swift-version
================================================
5.6


================================================
FILE: .swiftformat
================================================
--commas inline
--disable wrapMultilineStatementBraces
--enable wrapConditionalBodies
--enable wrapMultilineFunctionChains
--header "MIT License\nCopyright (c) 2021-{year} LinearMouse"
--decimalgrouping 3,5
--maxwidth 120
--wraparguments before-first
--wrapparameters before-first


================================================
FILE: .swiftlint.yml
================================================
only_rules:
  - accessibility_trait_for_button
  - array_init
  - blanket_disable_command
  - block_based_kvo
  - class_delegate_protocol
  - closing_brace
  - closure_end_indentation
  - closure_parameter_position
  - closure_spacing
  - collection_alignment
  - colon
  - comma
  - comma_inheritance
  - compiler_protocol_init
  - computed_accessors_order
  - conditional_returns_on_newline
  - contains_over_filter_count
  - contains_over_filter_is_empty
  - contains_over_first_not_nil
  - contains_over_range_nil_comparison
  - control_statement
  - custom_rules
  - deployment_target
  - direct_return
  - discarded_notification_center_observer
  - discouraged_assert
  - discouraged_direct_init
  - discouraged_none_name
  - discouraged_object_literal
  - discouraged_optional_collection
  - duplicate_conditions
  - duplicate_enum_cases
  - duplicate_imports
  - duplicated_key_in_dictionary_literal
  - dynamic_inline
  - empty_collection_literal
  - empty_count
  - empty_enum_arguments
  - empty_parameters
  - empty_parentheses_with_trailing_closure
  - empty_string
  - empty_xctest_method
  - enum_case_associated_values_count
  - explicit_init
  - fallthrough
  - fatal_error_message
  - final_test_case
  - first_where
  - flatmap_over_map_reduce
  - for_where
  - generic_type_name
  - ibinspectable_in_extension
  - identical_operands
  - implicit_getter
  - implicit_return
  - inclusive_language
  - invalid_swiftlint_command
  - is_disjoint
  - joined_default_parameter
  - last_where
  - leading_whitespace
  - legacy_cggeometry_functions
  - legacy_constant
  - legacy_constructor
  - legacy_hashing
  - legacy_multiple
  - legacy_nsgeometry_functions
  - legacy_random
  - literal_expression_end_indentation
  - lower_acl_than_parent
  - mark
  - modifier_order
  - multiline_arguments
  - multiline_arguments_brackets
  - multiline_function_chains
  - multiline_literal_brackets
  - multiline_parameters
  - multiline_parameters_brackets
  - nimble_operator
  - number_separator
  - no_fallthrough_only
  - no_space_in_method_call
  - non_optional_string_data_conversion
  - non_overridable_class_declaration
  - notification_center_detachment
  - ns_number_init_as_function_reference
  - nsobject_prefer_isequal
  - operator_usage_whitespace
  - operator_whitespace
  - optional_data_string_conversion
  - overridden_super_call
  - prefer_key_path
  - prefer_self_in_static_references
  - prefer_self_type_over_type_of_self
  - prefer_zero_over_explicit_init
  - private_action
  - private_outlet
  - private_subject
  - private_swiftui_state
  - private_unit_test
  - prohibited_super_call
  - protocol_property_accessors_order
  - reduce_boolean
  - reduce_into
  - redundant_discardable_let
  - redundant_nil_coalescing
  - redundant_objc_attribute
  - redundant_optional_initialization
  - redundant_sendable
  - redundant_set_access_control
  - redundant_string_enum_value
  - redundant_type_annotation
  - redundant_void_return
  - required_enum_case
  - return_arrow_whitespace
  - return_value_from_void_function
  - self_binding
  - self_in_property_initialization
  - shorthand_operator
  - shorthand_optional_binding
  - sorted_first_last
  - statement_position
  - static_operator
  - static_over_final_class
  - strong_iboutlet
  - superfluous_disable_command
  - superfluous_else
  - switch_case_alignment
  - switch_case_on_newline
  - syntactic_sugar
  - test_case_accessibility
  - toggle_bool
  - trailing_closure
  - trailing_comma
  - trailing_newline
  - trailing_semicolon
  - trailing_whitespace
  - unavailable_condition
  - unavailable_function
  - unneeded_break_in_switch
  - unneeded_override
  - unneeded_parentheses_in_closure_argument
  - unowned_variable_capture
  - untyped_error_in_catch
  - unused_closure_parameter
  - unused_control_flow_label
  - unused_enumerated
  - unused_optional_binding
  - unused_setter_value
  - valid_ibinspectable
  - vertical_parameter_alignment
  - vertical_parameter_alignment_on_call
  - vertical_whitespace_closing_braces
  - vertical_whitespace_opening_braces
  - void_function_in_ternary
  - void_return
  - xct_specific_matcher
  - xctfail_message
  - yoda_condition
analyzer_rules:
  - capture_variable
  - typesafe_array_init
  - unneeded_synthesized_initializer
  - unused_declaration
  - unused_import
redundant_discardable_let:
  ignore_swiftui_view_bodies: true
for_where:
  allow_for_as_filter: true
number_separator:
  minimum_length: 5
redundant_type_annotation:
  consider_default_literal_types_redundant: true
unneeded_override:
  affect_initializers: true


================================================
FILE: .vscode/settings.json
================================================
{
  "editor.formatOnSave": true
}


================================================
FILE: ACCESSIBILITY.md
================================================
# Accessibility permission

LinearMouse requires accessibility features to work properly.
You need to grant Accessibility permission at first launch.

## Grant Accessibility permission

1. Click “Open Accessibility”.
2. Click the lock to make changes.
3. Toggle “LinearMouse” on.

https://user-images.githubusercontent.com/3000535/173173454-b4b8e7ae-5184-4b7a-ba72-f6ce8041f721.mp4

## Not working?

If LinearMouse continues to display accessibility permission request window even after it has been
granted, it's likely due to a common macOS bug.

To resolve this issue, you can try the following steps:

1. Remove LinearMouse from accessibility permissions using the "-" button.
2. Re-add it.

If the previous steps did not resolve the issue, you can try the following:

1. Quit LinearMouse.
2. Open Terminal.app.
3. <p>Copy and paste the following command:</p>
   <pre><code>tccutil reset Accessibility com.lujjjh.LinearMouse</code></pre>
   Then press the return key.
4. Launch LinearMouse and try again.


================================================
FILE: CLAUDE.md
================================================
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

LinearMouse is a macOS utility app that enhances mouse and trackpad functionality. It's built with Swift and uses SwiftUI for the user interface. The app provides customizable mouse button mappings, scrolling behavior, and pointer settings that can be configured per-device, per-application, or per-display.

## Development Commands

### Build and Test
```bash
# Build the project
xcodebuild -project LinearMouse.xcodeproj -scheme LinearMouse

# Run tests
make test
# or
xcodebuild test -project LinearMouse.xcodeproj -scheme LinearMouse

# Full build pipeline (configure, clean, lint, test, package)
make all
```

### Code Quality
```bash
# Lint the codebase
make lint
# or run individually:
swiftformat --lint .
swiftlint .

# Clean build artifacts
make clean
```

### Packaging
```bash
# Create DMG package
make package

# For release (requires signing certificates)
make configure-release
make prepublish
```

### Configuration Schema Generation
```bash
# Generate JSON schema from TypeScript definitions
npm run generate:json-schema
```

## Architecture Overview

### Core Components

1. **EventTransformer System** (`LinearMouse/EventTransformer/`):
   - `EventTransformerManager`: Central coordinator that manages event processing
   - Individual transformers handle specific functionality (scrolling, button mapping, etc.)
   - Uses LRU cache for performance optimization

2. **Configuration System** (`LinearMouse/Model/Configuration/`):
   - `Configuration.swift`: Main configuration model with JSON schema validation
   - `Scheme.swift`: Defines device-specific settings
   - `DeviceMatcher.swift`: Logic for matching devices to configurations

3. **Device Management** (`LinearMouse/Device/`):
   - `DeviceManager.swift`: Manages connected input devices
   - `Device.swift`: Represents individual input devices

4. **Event Processing** (`LinearMouse/EventTap/`):
   - `GlobalEventTap.swift`: Captures system-wide input events
   - `EventTap.swift`: Base event handling functionality

5. **User Interface** (`LinearMouse/UI/`):
   - SwiftUI-based settings interface
   - Modular components for different settings categories
   - State management using `@Published` properties

### Custom Modules

The project includes several custom Swift packages in the `Modules/` directory:

- **KeyKit**: Keyboard input handling and simulation
- **PointerKit**: Mouse/trackpad device interaction
- **GestureKit**: Gesture recognition (zoom, navigation swipes)
- **DockKit**: Dock integration utilities
- **ObservationToken**: Observation pattern utilities

### Key Patterns

1. **Event Transformation Pipeline**: Events flow through multiple transformers in sequence
2. **Configuration-Driven Behavior**: All functionality is controlled by JSON configuration
3. **Device Matching**: Settings are applied based on device type, application, or display
4. **State Management**: Uses Combine framework for reactive state updates

## Important Development Notes

- The app requires accessibility permissions to function
- Event processing happens at the system level using CGEvent
- Configuration is stored as JSON and validated against a schema
- The project uses Swift Package Manager for dependencies
- Localization is handled through Crowdin integration
- Code signing is required for distribution (see `Scripts/` directory)

## Testing

- Unit tests are in `LinearMouseUnitTests/`
- Focus on testing event transformers and configuration parsing
- Run tests before submitting changes: `make test`

## Configuration Structure

The app uses a JSON configuration format with:
- `schemes`: Array of device-specific configurations
- Each scheme can target specific devices, applications, or displays
- Settings include button mappings, scrolling behavior, and pointer adjustments
- Configuration schema is defined in `Documentation/Configuration.d.ts`

================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our
community include:

* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
  and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
  community

Examples of unacceptable behavior include:

* The use of sexualized language or imagery, and sexual attention or advances of
  any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
  without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.

Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
feedback@linearmouse.org.
All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the
reporter of any incident.

## Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:

### 1. Correction

**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series of
actions.

**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.

### 3. Temporary Ban

**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.

### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within the
community.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].

Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].

For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].

[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations


================================================
FILE: CONTRIBUTING-cn.md
================================================
# 贡献指南

感谢你投入时间为 LinearMouse 做出贡献。

阅读我们的[行为准则](CODE_OF_CONDUCT.md),以保持我们的社区平易近人,受到尊重。

## 构建指南

在 macOS 上构建 LinearMouse 的指南。

### 设置仓库

```sh
$ git clone https://github.com/linearmouse/linearmouse.git
$ cd linearmouse
```

### 配置代码签名

Apple 要求代码签名。你可以运行以下命令来生成代码签名配置。

```
$ make configure
```

> 注:如果你希望为 LinearMouse 贡献代码,请不要在 Xcode 中直接修改“Signing & Capabilities”。使用 `make configure` 或者修改 `Signing.xcconfig`。

如果在你的钥匙串中没有代码签名证书,会生成一份使用 ad-hoc 证书签名应用的配置。

使用 ad-hoc 证书,你需要为每次构建[授予辅助功能权限](https://github.com/linearmouse/linearmouse#accessibility-permission)。因此,推荐使用 Apple Development 证书。你可以[在 Xcode 中](https://help.apple.com/xcode/mac/current/#/dev154b28f09) 创建 Apple Development 证书,这是完全免费的。

### 构建

现在,你可以运行以下命令来构建和打包 LinearMouse 了。

```sh
$ make
```


================================================
FILE: CONTRIBUTING.md
================================================
# Contributing guide

Thank you for investing your time in contributing to LinearMouse!

Read our [Code of Conduct](CODE_OF_CONDUCT.md) to keep our community approachable and respectable.

## Build instructions

Instructions for building LinearMouse on macOS.

### Dependencies

- [Xcode](https://apps.apple.com/app/xcode/id497799835), obviously
- [SwiftLint](https://github.com/realm/SwiftLint), used to lint Swift files
- [SwiftFormat](https://github.com/nicklockwood/SwiftFormat), used to format Swift files
- `npm` & [ts-json-schema-generator](https://www.npmjs.com/package/ts-json-schema-generator), used to generate and document the custom configuration JSON scheme

Install tools using brew:

```bash
$ brew install npm swiftlint swiftformat
```

Install npm dependencies from the [package.json](./package.json)

```bash
$ npm install
```

### Setup the repository

```sh
$ git clone https://github.com/linearmouse/linearmouse.git
$ cd linearmouse
```

### Configure code signing

Code signing is required by Apple. You can generate a code signing configuration by running

```
$ make configure
```

> Note: If you want to contribute to LinearMouse, please don't modify the ‘Signing & Capabilities’ configurations directly in Xcode. Instead, use `make configure` or modify the `Signing.xcconfig`.

If there are no available code signing certificates in your Keychain, it will generate a configuration that uses ad-hoc certificates to sign the app.

By using ad-hoc certificates, you'll have to [grant accessibility permissions](https://github.com/linearmouse/linearmouse#accessibility-permission) for each builds.
In that case, using Apple Development certificates is recommended.
You can create an Apple Development certificate [in Xcode](https://help.apple.com/xcode/mac/current/#/dev154b28f09), which is totally free.

### Build

Now, you can build and package LinearMouse by running

```sh
$ make
```


================================================
FILE: Config.xcconfig
================================================
CURRENT_PROJECT_VERSION = dev
MARKETING_VERSION = $(CURRENT_PROJECT_VERSION)

PRODUCT_BUNDLE_IDENTIFIER = com.lujjjh.dev.LinearMouse
ASSETCATALOG_COMPILER_APPICON_NAME = AppIconDev

#include? "Signing.xcconfig"
#include? "Version.xcconfig"
#include? "Release.xcconfig"


================================================
FILE: Documentation/Configuration.d.ts
================================================
type SingleValueOrArray<T> = T | T[];

/** @asType number */
type Int = number;

/** @pattern ^\d+$ */
type IntString = string;

/** @pattern ^0x[0-9a-fA-F]+$ */
type HexString = string;

type PhysicalButton = Primary | Secondary | Auxiliary | Back | Forward | number;

/**
 * @title Unset
 * @description A special value that explicitly restores a setting to the system or device default. Currently supported in pointer settings; may be supported more broadly in the future.
 */
export type Unset = "unset";

/**
 * @description Primary button, usually the left button.
 */
type Primary = 0;

/**
 * @description Secondary button, usually the right button.
 */
type Secondary = 1;

/**
 * @description Auxiliary button, usually the wheel button or the middle button.
 */
type Auxiliary = 2;

/**
 * @description Forth button, typically the back button.
 */
type Back = 3;

/**
 * @description Fifth button, typically the forward button.
 */
type Forward = 4;

export type Configuration = {
  $schema?: string;

  /**
   * @title Schemes
   * @description A scheme is a collection of settings that are activated in specified circumstances.
   * @examples [{"if":{"device":{"category":"mouse"}},"scrolling":{"reverse":"vertical"}}]
   */
  schemes?: Scheme[];
};

type Scheme = {
  /**
   * @title Scheme activation conditions
   * @description This value can be a single condition or an array. A scheme is activated if at least one of the conditions is met.
   */
  if?: SingleValueOrArray<Scheme.If>;

  /**
   * @title Scrolling settings
   * @description Customize the scrolling behavior.
   */
  scrolling?: Scheme.Scrolling;

  /**
   * @title Pointer settings
   * @description Customize the pointer acceleration and speed.
   */
  pointer?: Scheme.Pointer;

  /**
   * @title Buttons settings
   * @description Customize the buttons behavior.
   */
  buttons?: Scheme.Buttons;
};

declare namespace Scheme {
  type If = {
    /**
     * @title Device
     * @description Match one or more devices. If not provided, the scheme is activated on all devices.
     */
    device?: If.Device;

    /**
     * @title App
     * @description Match apps by providing the bundle ID. For example, `com.apple.Safari`.
     */
    app?: string;

    /**
     * @title Parent app
     * @description Match apps by providing the bundle ID of the parent process. For example, `org.polymc.PolyMC`.
     */
    parentApp?: string;

    /**
     * @title Group app
     * @description Match apps by providing the bundle ID of the process group. For example, `org.polymc.PolyMC`.
     */
    groupApp?: string;

    /**
     * @title Process name
     * @description Match by the executable file name of the frontmost process (from NSRunningApplication.executableURL.lastPathComponent). Case-sensitive.
     */
    processName?: string;

    /**
     * @title Process path
     * @description Match by the absolute executable path of the frontmost process (from NSRunningApplication.executableURL.path). Case-sensitive.
     */
    processPath?: string;

    /**
     * @title Display name
     * @description Match displays by providing the display name. For example, `DELL P2415Q`.
     */
    display?: string;
  };

  namespace If {
    type Device = {
      /**
       * @title Vendor ID
       * @description The vendor ID of the devices.
       * @examples ["0xA123"]
       */
      vendorID?: HexString | Int;

      /**
       * @title Product ID
       * @description The product ID of the devices.
       * @examples ["0xA123"]
       */
      productID?: HexString | Int;

      /**
       * @title Product name
       * @description The product name of the devices.
       */
      productName?: string;

      /**
       * @title Serial number
       * @description The serial number of the devices.
       */
      serialNumber?: string;

      /**
       * @title Category
       * @description The category of the devices.
       */
      category?: SingleValueOrArray<Category>;
    };

    /**
     * @title Mouse
     * @description Match mouse devices.
     */
    type Mouse = "mouse";

    /**
     * @title Trackpad
     * @description Match trackpad devices.
     */
    type Trackpad = "trackpad";

    type Category = Mouse | Trackpad;
  }

  type Scrolling = {
    /**
     * @title Reverse scrolling
     */
    reverse?: Scrolling.Bidirectional<boolean>;

    /**
     * @title Scroll distance
     * @description The distance after rolling the wheel.
     */
    distance?: Scrolling.Bidirectional<Scrolling.Distance>;

    /**
     * @description The scrolling acceleration.
     * @default 1
     */
    acceleration?: Scrolling.Bidirectional<number>;

    /**
     * @description The scrolling speed.
     * @default 0
     */
    speed?: Scrolling.Bidirectional<number>;

    /**
     * @title Smoothed scrolling
     * @description Use a preset curve or fine-tune response, speed, acceleration, and inertia.
     */
    smoothed?: Scrolling.Bidirectional<Scrolling.Smoothed>;

    /**
     * @title Modifier keys settings
     */
    modifiers?: Scrolling.Bidirectional<Scrolling.Modifiers>;
  };

  namespace Scrolling {
    type Bidirectional<T> =
      | T
      | undefined
      | {
          vertical?: T;
          horizontal?: T;
        };

    /**
     * @description The scrolling distance will not be modified.
     */
    type Auto = "auto";

    type Distance = Auto | Distance.Line | Distance.Pixel;

    namespace Distance {
      /**
       * @description The scrolling distance in lines.
       */
      type Line = Int | IntString;

      /**
       * @description The scrolling distance in pixels.
       * @pattern ^\d[1-9]*(\.\d+)?px
       */
      type Pixel = string;
    }

    type Smoothed = {
      /**
       * @description Set to `false` to explicitly disable inherited smoothed scrolling for this direction.
       * @default true
       */
      enabled?: boolean;

      /**
       * @description The preset curve to use.
       * @default "natural"
       */
      preset?: Smoothed.Preset;

      /**
       * @description How quickly the scrolling responds to input.
       */
      response?: number;

      /**
       * @description The scrolling speed.
       */
      speed?: number;

      /**
       * @description The scrolling acceleration.
       */
      acceleration?: number;

      /**
       * @description The scrolling inertia.
       */
      inertia?: number;
    };

    namespace Smoothed {
      type Preset =
        | "custom"
        | "linear"
        | "easeIn"
        | "easeOut"
        | "easeInOut"
        | "easeOutIn"
        | "quadratic"
        | "cubic"
        | "quartic"
        | "easeOutCubic"
        | "easeInOutCubic"
        | "easeOutQuartic"
        | "easeInOutQuartic"
        | "quintic"
        | "sine"
        | "exponential"
        | "circular"
        | "back"
        | "bounce"
        | "elastic"
        | "spring"
        | "natural"
        | "smooth"
        | "snappy"
        | "gentle";
    }

    type Modifiers = {
      /**
       * @description The action when command key is pressed.
       */
      command?: Modifiers.Action;

      /**
       * @description The action when shift key is pressed.
       */
      shift?: Modifiers.Action;

      /**
       * @description The action when option key is pressed.
       */
      option?: Modifiers.Action;

      /**
       * @description The action when control key is pressed.
       */
      control?: Modifiers.Action;
    };

    namespace Modifiers {
      /**
       * @deprecated
       * @description Default action.
       */
      type None = { type: "none" };

      /**
       * @description Default action.
       */
      type Auto = { type: "auto" };

      /**
       * @description Ignore modifier.
       */
      type Ignore = { type: "ignore" };

      /**
       * @description No action.
       */
      type PreventDefault = { type: "preventDefault" };

      /**
       * @description Alter the scrolling orientation from vertical to horizontal or vice versa.
       */
      type AlterOrientation = {
        type: "alterOrientation";
      };

      /**
       * @description Scale the scrolling speed.
       */
      type ChangeSpeed = {
        type: "changeSpeed";

        /**
         * @description The factor to scale the scrolling speed.
         */
        scale: number;
      };

      /**
       * @description Zoom in and out using ⌘+ and ⌘-.
       */
      type Zoom = {
        type: "zoom";
      };

      /**
       * @description Zoom in and out using pinch gestures.
       */
      type PinchZoom = {
        type: "pinchZoom";
      };

      type Action =
        | None
        | Auto
        | Ignore
        | PreventDefault
        | AlterOrientation
        | ChangeSpeed
        | Zoom
        | PinchZoom;
    }
  }

  type Pointer = {
    /**
     * @title Pointer acceleration
     * @description A number to set acceleration, or "unset" to restore system default. If omitted, the previous/merged value applies.
     * @minimum 0
     * @maximum 20
     */
    acceleration?: number | Unset;

    /**
     * @title Pointer speed
     * @description A number to set speed, or "unset" to restore device default. If omitted, the previous/merged value applies.
     * @minimal 0
     * @maximum 1
     */
    speed?: number | Unset;

    /**
     * @title Disable pointer acceleration
     * @description If the value is true, the pointer acceleration will be disabled and acceleration and speed will not take effect.
     * @default false
     */
    disableAcceleration?: boolean;

    /**
     * @title Redirects to scroll
     * @description If the value is true, pointer movements will be redirected to scroll events.
     * @default false
     */
    redirectsToScroll?: boolean;
  };

  type Buttons = {
    /**
     * @title Button mappings
     * @description Assign actions to buttons.
     */
    mappings?: Buttons.Mapping[];

    /**
     * @title Universal back and forward
     * @description If the value is true, the back and forward side buttons will be enabled in Safari and some other apps that do not handle these side buttons correctly. If the value is "backOnly" or "forwardOnly", only universal back or universal forward will be enabled.
     * @default false
     */
    universalBackForward?: Buttons.UniversalBackForward;

    /**
     * @title Switch primary and secondary buttons
     * @description If the value is true, the primary button will be the right button and the secondary button will be the left button.
     * @default false
     */
    switchPrimaryButtonAndSecondaryButtons?: boolean;

    /**
     * @title Debounce button clicks
     * @description Ignore rapid clicks with a certain time period.
     */
    clickDebouncing?: Buttons.ClickDebouncing;

    /**
     * @title Gesture button
     * @description Press and hold a button while dragging to trigger gestures like switching desktop spaces or opening Mission Control.
     */
    gesture?: Buttons.Gesture;

    /**
     * @title Auto scroll
     * @description Click or hold a mouse button, then move the pointer to continuously scroll like the Windows middle-button autoscroll behavior.
     */
    autoScroll?: Buttons.AutoScroll;
  };

  namespace Buttons {
    type AutoScroll = {
      /**
       * @description Indicates if auto scroll is enabled.
       * @default false
       */
      enabled?: boolean;

      /**
       * @title Activation mode
       * @description Use \"toggle\" to click once and move until clicking again, \"hold\" to scroll only while the trigger stays pressed, or provide both to support both behaviors.
       */
      mode?: SingleValueOrArray<AutoScroll.Mode>;

      /**
       * @description Adjust the auto scroll speed multiplier.
       * @default 1
       */
      speed?: number;

      /**
       * @description If true, a plain middle click on a pressable element such as a link will keep its native behavior when possible.
       * @default false
       */
      preserveNativeMiddleClick?: boolean;

      /**
       * @title Trigger
       * @description Choose the mouse button and modifier keys used to activate auto scroll.
       */
      trigger?: AutoScroll.Trigger;
    };

    namespace AutoScroll {
      /**
       * @description Click once to enter auto scroll and click again to exit.
       */
      type Toggle = "toggle";

      /**
       * @description Auto scroll is active only while the trigger button remains pressed.
       */
      type Hold = "hold";

      type Mode = Toggle | Hold;

      type Trigger = {
        /**
         * @title Button number
         * @description The button number. See https://developer.apple.com/documentation/coregraphics/cgmousebutton
         */
        button: Mapping.Button;

        /**
         * @description Indicates if the command modifier key should be pressed.
         */
        command?: boolean;

        /**
         * @description Indicates if the shift modifier key should be pressed.
         */
        shift?: boolean;

        /**
         * @description Indicates if the option modifier key should be pressed.
         */
        option?: boolean;

        /**
         * @description Indicates if the control modifier key should be pressed.
         */
        control?: boolean;
      };
    }

    type Mapping = (
      | {
          /**
           * @title Button number
           * @description The button number. See https://developer.apple.com/documentation/coregraphics/cgmousebutton
           */
          button: Mapping.Button;

          /**
           * @description Indicates if key repeat is enabled. If the value is true, the action will be repeatedly executed when the button is hold according to the key repeat settings in System Settings.
           */
          repeat?: boolean;

          /**
           * @description Indicates if keyboard shortcut actions should stay pressed while the button is held. When enabled, LinearMouse sends key down on button press and key up on button release instead of repeating the shortcut.
           */
          hold?: boolean;
        }
      | {
          /**
           * @title Scroll direction
           * @description Map scroll events to specific actions.
           */
          scroll: Mapping.ScrollDirection;
        }
    ) & {
      /**
       * @description Indicates if the command modifier key should be pressed.
       */
      command?: boolean;

      /**
       * @description Indicates if the shift modifier key should be pressed.
       */
      shift?: boolean;

      /**
       * @description Indicates if the option modifier key should be pressed.
       */
      option?: boolean;

      /**
       * @description Indicates if the control modifier key should be pressed.
       */
      control?: boolean;

      /**
       * @title Action
       */
      action?: Mapping.Action;
    };

    namespace Mapping {
      type Button = PhysicalButton | LogitechControlButton;

      type LogitechControlButton = {
        /**
         * @description Logitech control button identifier.
         */
        kind: "logitechControl";

        /**
         * @description Logitech control ID (CID).
         */
        controlID: Int;

        /**
         * @description Match a specific Logitech device product ID when needed.
         */
        productID?: HexString | Int;

        /**
         * @description Match a specific Logitech device serial number when needed.
         */
        serialNumber?: string;
      };

      type Action =
        | SimpleAction
        | Run
        | MouseWheelScrollUpWithDistance
        | MouseWheelScrollDownWithDistance
        | MouseWheelScrollLeftWithDistance
        | MouseWheelScrollRightWithDistance
        | KeyPress;

      type SimpleAction =
        | Auto
        | None
        | MissionControlSpaceLeft
        | MissionControlSpaceRight
        | MissionControl
        | AppExpose
        | Launchpad
        | ShowDesktop
        | LookUpAndDataDetectors
        | SmartZoom
        | DisplayBrightnessUp
        | DisplayBrightnessDown
        | MediaVolumeUp
        | MediaVolumeDown
        | MediaMute
        | MediaPlayPause
        | MediaNext
        | MediaPrevious
        | MediaFastForward
        | MediaRewind
        | KeyboardBrightnessUp
        | KeyboardBrightnessDown
        | MouseWheelScrollUp
        | MouseWheelScrollDown
        | MouseWheelScrollLeft
        | MouseWheelScrollRight
        | MouseButtonLeft
        | MouseButtonMiddle
        | MouseButtonRight
        | MouseButtonBack
        | MouseButtonForward;

      /**
       * @description Do not modify the button behavior.
       */
      type Auto = "auto";

      /**
       * @description Prevent the button events.
       */
      type None = "none";

      /**
       * @description Mission Control.
       */
      type MissionControl = "missionControl";

      /**
       * @description Mission Control: Move left a space.
       */
      type MissionControlSpaceLeft = "missionControl.spaceLeft";

      /**
       * @description Mission Control: Move right a space.
       */
      type MissionControlSpaceRight = "missionControl.spaceRight";

      /**
       * @description Application windows.
       */
      type AppExpose = "appExpose";

      /**
       * @description Launchpad.
       */
      type Launchpad = "launchpad";

      /**
       * @description Show desktop.
       */
      type ShowDesktop = "showDesktop";

      /**
       * @description Look up & data detectors.
       */
      type LookUpAndDataDetectors = "lookUpAndDataDetectors";

      /**
       * @description Smart zoom.
       */
      type SmartZoom = "smartZoom";

      /**
       * @description Display: Brightness up.
       */
      type DisplayBrightnessUp = "display.brightnessUp";

      /**
       * @description Display: Brightness down.
       */
      type DisplayBrightnessDown = "display.brightnessDown";

      /**
       * @description Media: Volume up.
       */
      type MediaVolumeUp = "media.volumeUp";

      /**
       * @description Media: Volume down.
       */
      type MediaVolumeDown = "media.volumeDown";

      /**
       * @description Media: Toggle mute.
       */
      type MediaMute = "media.mute";

      /**
       * @description Media: Play / pause.
       */
      type MediaPlayPause = "media.playPause";

      /**
       * @description Media: Next.
       */
      type MediaNext = "media.next";

      /**
       * @description Media: Previous.
       */
      type MediaPrevious = "media.previous";

      /**
       * @description Media: Fast forward.
       */
      type MediaFastForward = "media.fastForward";

      /**
       * @description Media: Rewind.
       */
      type MediaRewind = "media.rewind";

      /**
       * @description Keyboard: Brightness up.
       */
      type KeyboardBrightnessUp = "keyboard.brightnessUp";

      /**
       * @description Keyboard: Brightness down.
       */
      type KeyboardBrightnessDown = "keyboard.brightnessDown";

      /**
       * @description Mouse: Wheel: Scroll up.
       */
      type MouseWheelScrollUp = "mouse.wheel.scrollUp";

      /**
       * @description Mouse: Wheel: Scroll down.
       */
      type MouseWheelScrollDown = "mouse.wheel.scrollDown";

      /**
       * @description Mouse: Wheel: Scroll left.
       */
      type MouseWheelScrollLeft = "mouse.wheel.scrollLeft";

      /**
       * @description Mouse: Wheel: Scroll right.
       */
      type MouseWheelScrollRight = "mouse.wheel.scrollRight";

      /**
       * @description Mouse: Button: Act as left button.
       */
      type MouseButtonLeft = "mouse.button.left";

      /**
       * @description Mouse: Button: Act as middle button.
       */
      type MouseButtonMiddle = "mouse.button.middle";

      /**
       * @description Mouse: Button: Act as right button.
       */
      type MouseButtonRight = "mouse.button.right";

      /**
       * @description Mouse: Button: Act as back button.
       */
      type MouseButtonBack = "mouse.button.back";

      /**
       * @description Mouse: Button: Act as forward button.
       */
      type MouseButtonForward = "mouse.button.forward";

      type Run = {
        /**
         * @description Run a specific command. For example, `"open -a 'Mission Control'"`.
         */
        run: string;
      };

      type MouseWheelScrollUpWithDistance = {
        /**
         * @description Mouse: Wheel: Scroll up a certain distance.
         */
        "mouse.wheel.scrollUp": Scheme.Scrolling.Distance;
      };

      type MouseWheelScrollDownWithDistance = {
        /**
         * @description Mouse: Wheel: Scroll down a certain distance.
         */
        "mouse.wheel.scrollDown": Scheme.Scrolling.Distance;
      };

      type MouseWheelScrollLeftWithDistance = {
        /**
         * @description Mouse: Wheel: Scroll left a certain distance.
         */
        "mouse.wheel.scrollLeft": Scheme.Scrolling.Distance;
      };

      type MouseWheelScrollRightWithDistance = {
        /**
         * @description Mouse: Wheel: Scroll right a certain distance.
         */
        "mouse.wheel.scrollRight": Scheme.Scrolling.Distance;
      };

      type KeyPress = {
        /**
         * @description Keyboard: Keyboard shortcut.
         */
        keyPress: Array<Key>;
      };

      /**
       * @description Scroll direction.
       */
      type ScrollDirection = "up" | "down" | "left" | "right";

      type Key =
        | "enter"
        | "tab"
        | "space"
        | "delete"
        | "escape"
        | "command"
        | "shift"
        | "capsLock"
        | "option"
        | "control"
        | "commandRight"
        | "shiftRight"
        | "optionRight"
        | "controlRight"
        | "arrowLeft"
        | "arrowRight"
        | "arrowDown"
        | "arrowUp"
        | "home"
        | "pageUp"
        | "backspace"
        | "end"
        | "pageDown"
        | "f1"
        | "f2"
        | "f3"
        | "f4"
        | "f5"
        | "f6"
        | "f7"
        | "f8"
        | "f9"
        | "f10"
        | "f11"
        | "f12"
        | "a"
        | "b"
        | "c"
        | "d"
        | "e"
        | "f"
        | "g"
        | "h"
        | "i"
        | "j"
        | "k"
        | "l"
        | "m"
        | "n"
        | "o"
        | "p"
        | "q"
        | "r"
        | "s"
        | "t"
        | "u"
        | "v"
        | "w"
        | "x"
        | "y"
        | "z"
        | "0"
        | "1"
        | "2"
        | "3"
        | "4"
        | "5"
        | "6"
        | "7"
        | "8"
        | "9"
        | "="
        | "-"
        | ";"
        | "'"
        | ","
        | "."
        | "/"
        | "\\"
        | "`"
        | "["
        | "]"
        | "numpadPlus"
        | "numpadMinus"
        | "numpadMultiply"
        | "numpadDivide"
        | "numpadEnter"
        | "numpadEquals"
        | "numpadDecimal"
        | "numpadClear"
        | "numpad0"
        | "numpad1"
        | "numpad2"
        | "numpad3"
        | "numpad4"
        | "numpad5"
        | "numpad6"
        | "numpad7"
        | "numpad8"
        | "numpad9";
    }

    type UniversalBackForward =
      | boolean
      | UniversalBackForward.BackOnly
      | UniversalBackForward.ForwardOnly;

    namespace UniversalBackForward {
      /**
       * @description Enable universal back only.
       */
      type BackOnly = "backOnly";

      /**
       * @description Enable universal forward only.
       */
      type ForwardOnly = "forwardOnly";
    }

    type ClickDebouncing = {
      /**
       * @description The time period in which rapid clicks are ignored.
       */
      timeout?: Int;

      /**
       * @description If the value is true, the timer will be reset on mouse up.
       */
      resetTimerOnMouseUp?: boolean;

      /**
       * @description Buttons to debounce.
       */
      buttons?: PhysicalButton[];
    };

    type Gesture = {
      /**
       * @title Enable gesture button
       * @description If the value is true, the gesture button feature is enabled.
       * @default false
       */
      enabled?: boolean;

      /**
       * @title Trigger
       * @description Choose the mouse button and modifier keys used to activate gestures.
       */
      trigger?: Gesture.Trigger;

      /**
       * @title Button
       * @description Deprecated. Use trigger instead.
       * @deprecated
       */
      button?: PhysicalButton;

      /**
       * @title Threshold
       * @description The distance in pixels that must be dragged before triggering a gesture.
       * @default 50
       * @minimum 20
       * @maximum 200
       */
      threshold?: Int;

      /**
       * @title Dead zone
       * @description The tolerance in pixels for the non-dominant axis to prevent accidental gestures.
       * @default 40
       * @minimum 0
       * @maximum 100
       */
      deadZone?: Int;

      /**
       * @title Cooldown
       * @description The cooldown period in milliseconds between gestures to prevent double-triggering.
       * @default 500
       * @minimum 0
       * @maximum 2000
       */
      cooldownMs?: Int;

      /**
       * @title Gesture actions
       * @description Actions to trigger for each gesture direction.
       */
      actions?: Gesture.Actions;
    };

    namespace Gesture {
      type Trigger = {
        /**
         * @title Button number
         * @description The button number. See https://developer.apple.com/documentation/coregraphics/cgmousebutton
         */
        button: Mapping.Button;

        /**
         * @description Indicates if the command modifier key should be pressed.
         */
        command?: boolean;

        /**
         * @description Indicates if the shift modifier key should be pressed.
         */
        shift?: boolean;

        /**
         * @description Indicates if the option modifier key should be pressed.
         */
        option?: boolean;

        /**
         * @description Indicates if the control modifier key should be pressed.
         */
        control?: boolean;
      };

      type Actions = {
        /**
         * @title Swipe left action
         * @description Action to trigger when dragging left.
         * @default "missionControl.spaceLeft"
         */
        left?: GestureAction;

        /**
         * @title Swipe right action
         * @description Action to trigger when dragging right.
         * @default "missionControl.spaceRight"
         */
        right?: GestureAction;

        /**
         * @title Swipe up action
         * @description Action to trigger when dragging up.
         * @default "missionControl"
         */
        up?: GestureAction;

        /**
         * @title Swipe down action
         * @description Action to trigger when dragging down.
         * @default "appExpose"
         */
        down?: GestureAction;
      };

      type GestureAction =
        | "none"
        | "missionControl.spaceLeft"
        | "missionControl.spaceRight"
        | "missionControl"
        | "appExpose"
        | "showDesktop"
        | "launchpad";
    }
  }
}


================================================
FILE: Documentation/Configuration.json
================================================
{
  "$ref": "#/definitions/Configuration",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "definitions": {
    "Auxiliary": {
      "const": 2,
      "description": "Auxiliary button, usually the wheel button or the middle button.",
      "type": "number"
    },
    "Back": {
      "const": 3,
      "description": "Forth button, typically the back button.",
      "type": "number"
    },
    "Configuration": {
      "additionalProperties": false,
      "properties": {
        "$schema": {
          "type": "string"
        },
        "schemes": {
          "description": "A scheme is a collection of settings that are activated in specified circumstances.",
          "examples": [
            {
              "if": {
                "device": {
                  "category": "mouse"
                }
              },
              "scrolling": {
                "reverse": "vertical"
              }
            }
          ],
          "items": {
            "$ref": "#/definitions/Scheme"
          },
          "title": "Schemes",
          "type": "array"
        }
      },
      "type": "object"
    },
    "Forward": {
      "const": 4,
      "description": "Fifth button, typically the forward button.",
      "type": "number"
    },
    "HexString": {
      "pattern": "^0x[0-9a-fA-F]+$",
      "type": "string"
    },
    "Int": {
      "type": "number"
    },
    "IntString": {
      "pattern": "^\\d+$",
      "type": "string"
    },
    "PhysicalButton": {
      "anyOf": [
        {
          "$ref": "#/definitions/Primary"
        },
        {
          "$ref": "#/definitions/Secondary"
        },
        {
          "$ref": "#/definitions/Auxiliary"
        },
        {
          "$ref": "#/definitions/Back"
        },
        {
          "$ref": "#/definitions/Forward"
        },
        {
          "type": "number"
        }
      ]
    },
    "Primary": {
      "const": 0,
      "description": "Primary button, usually the left button.",
      "type": "number"
    },
    "Scheme": {
      "additionalProperties": false,
      "properties": {
        "buttons": {
          "$ref": "#/definitions/Scheme.Buttons",
          "description": "Customize the buttons behavior.",
          "title": "Buttons settings"
        },
        "if": {
          "$ref": "#/definitions/SingleValueOrArray%3CScheme.If%3E",
          "description": "This value can be a single condition or an array. A scheme is activated if at least one of the conditions is met.",
          "title": "Scheme activation conditions"
        },
        "pointer": {
          "$ref": "#/definitions/Scheme.Pointer",
          "description": "Customize the pointer acceleration and speed.",
          "title": "Pointer settings"
        },
        "scrolling": {
          "$ref": "#/definitions/Scheme.Scrolling",
          "description": "Customize the scrolling behavior.",
          "title": "Scrolling settings"
        }
      },
      "type": "object"
    },
    "Scheme.Buttons": {
      "additionalProperties": false,
      "properties": {
        "autoScroll": {
          "$ref": "#/definitions/Scheme.Buttons.AutoScroll",
          "description": "Click or hold a mouse button, then move the pointer to continuously scroll like the Windows middle-button autoscroll behavior.",
          "title": "Auto scroll"
        },
        "clickDebouncing": {
          "$ref": "#/definitions/Scheme.Buttons.ClickDebouncing",
          "description": "Ignore rapid clicks with a certain time period.",
          "title": "Debounce button clicks"
        },
        "gesture": {
          "$ref": "#/definitions/Scheme.Buttons.Gesture",
          "description": "Press and hold a button while dragging to trigger gestures like switching desktop spaces or opening Mission Control.",
          "title": "Gesture button"
        },
        "mappings": {
          "description": "Assign actions to buttons.",
          "items": {
            "$ref": "#/definitions/Scheme.Buttons.Mapping"
          },
          "title": "Button mappings",
          "type": "array"
        },
        "switchPrimaryButtonAndSecondaryButtons": {
          "default": false,
          "description": "If the value is true, the primary button will be the right button and the secondary button will be the left button.",
          "title": "Switch primary and secondary buttons",
          "type": "boolean"
        },
        "universalBackForward": {
          "$ref": "#/definitions/Scheme.Buttons.UniversalBackForward",
          "default": false,
          "description": "If the value is true, the back and forward side buttons will be enabled in Safari and some other apps that do not handle these side buttons correctly. If the value is \"backOnly\" or \"forwardOnly\", only universal back or universal forward will be enabled.",
          "title": "Universal back and forward"
        }
      },
      "type": "object"
    },
    "Scheme.Buttons.AutoScroll": {
      "additionalProperties": false,
      "properties": {
        "enabled": {
          "default": false,
          "description": "Indicates if auto scroll is enabled.",
          "type": "boolean"
        },
        "mode": {
          "$ref": "#/definitions/SingleValueOrArray%3CScheme.Buttons.AutoScroll.Mode%3E",
          "description": "Use \\\"toggle\\\" to click once and move until clicking again, \\\"hold\\\" to scroll only while the trigger stays pressed, or provide both to support both behaviors.",
          "title": "Activation mode"
        },
        "preserveNativeMiddleClick": {
          "default": false,
          "description": "If true, a plain middle click on a pressable element such as a link will keep its native behavior when possible.",
          "type": "boolean"
        },
        "speed": {
          "default": 1,
          "description": "Adjust the auto scroll speed multiplier.",
          "type": "number"
        },
        "trigger": {
          "$ref": "#/definitions/Scheme.Buttons.AutoScroll.Trigger",
          "description": "Choose the mouse button and modifier keys used to activate auto scroll.",
          "title": "Trigger"
        }
      },
      "type": "object"
    },
    "Scheme.Buttons.AutoScroll.Hold": {
      "const": "hold",
      "description": "Auto scroll is active only while the trigger button remains pressed.",
      "type": "string"
    },
    "Scheme.Buttons.AutoScroll.Mode": {
      "anyOf": [
        {
          "$ref": "#/definitions/Scheme.Buttons.AutoScroll.Toggle"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.AutoScroll.Hold"
        }
      ]
    },
    "Scheme.Buttons.AutoScroll.Toggle": {
      "const": "toggle",
      "description": "Click once to enter auto scroll and click again to exit.",
      "type": "string"
    },
    "Scheme.Buttons.AutoScroll.Trigger": {
      "additionalProperties": false,
      "properties": {
        "button": {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.Button",
          "description": "The button number. See https://developer.apple.com/documentation/coregraphics/cgmousebutton",
          "title": "Button number"
        },
        "command": {
          "description": "Indicates if the command modifier key should be pressed.",
          "type": "boolean"
        },
        "control": {
          "description": "Indicates if the control modifier key should be pressed.",
          "type": "boolean"
        },
        "option": {
          "description": "Indicates if the option modifier key should be pressed.",
          "type": "boolean"
        },
        "shift": {
          "description": "Indicates if the shift modifier key should be pressed.",
          "type": "boolean"
        }
      },
      "required": [
        "button"
      ],
      "type": "object"
    },
    "Scheme.Buttons.ClickDebouncing": {
      "additionalProperties": false,
      "properties": {
        "buttons": {
          "description": "Buttons to debounce.",
          "items": {
            "$ref": "#/definitions/PhysicalButton"
          },
          "type": "array"
        },
        "resetTimerOnMouseUp": {
          "description": "If the value is true, the timer will be reset on mouse up.",
          "type": "boolean"
        },
        "timeout": {
          "$ref": "#/definitions/Int",
          "description": "The time period in which rapid clicks are ignored."
        }
      },
      "type": "object"
    },
    "Scheme.Buttons.Gesture": {
      "additionalProperties": false,
      "properties": {
        "actions": {
          "$ref": "#/definitions/Scheme.Buttons.Gesture.Actions",
          "description": "Actions to trigger for each gesture direction.",
          "title": "Gesture actions"
        },
        "button": {
          "$ref": "#/definitions/PhysicalButton",
          "deprecated": true,
          "description": "Deprecated. Use trigger instead.",
          "title": "Button"
        },
        "cooldownMs": {
          "$ref": "#/definitions/Int",
          "default": 500,
          "description": "The cooldown period in milliseconds between gestures to prevent double-triggering.",
          "maximum": 2000,
          "minimum": 0,
          "title": "Cooldown"
        },
        "deadZone": {
          "$ref": "#/definitions/Int",
          "default": 40,
          "description": "The tolerance in pixels for the non-dominant axis to prevent accidental gestures.",
          "maximum": 100,
          "minimum": 0,
          "title": "Dead zone"
        },
        "enabled": {
          "default": false,
          "description": "If the value is true, the gesture button feature is enabled.",
          "title": "Enable gesture button",
          "type": "boolean"
        },
        "threshold": {
          "$ref": "#/definitions/Int",
          "default": 50,
          "description": "The distance in pixels that must be dragged before triggering a gesture.",
          "maximum": 200,
          "minimum": 20,
          "title": "Threshold"
        },
        "trigger": {
          "$ref": "#/definitions/Scheme.Buttons.Gesture.Trigger",
          "description": "Choose the mouse button and modifier keys used to activate gestures.",
          "title": "Trigger"
        }
      },
      "type": "object"
    },
    "Scheme.Buttons.Gesture.Actions": {
      "additionalProperties": false,
      "properties": {
        "down": {
          "$ref": "#/definitions/Scheme.Buttons.Gesture.GestureAction",
          "default": "appExpose",
          "description": "Action to trigger when dragging down.",
          "title": "Swipe down action"
        },
        "left": {
          "$ref": "#/definitions/Scheme.Buttons.Gesture.GestureAction",
          "default": "missionControl.spaceLeft",
          "description": "Action to trigger when dragging left.",
          "title": "Swipe left action"
        },
        "right": {
          "$ref": "#/definitions/Scheme.Buttons.Gesture.GestureAction",
          "default": "missionControl.spaceRight",
          "description": "Action to trigger when dragging right.",
          "title": "Swipe right action"
        },
        "up": {
          "$ref": "#/definitions/Scheme.Buttons.Gesture.GestureAction",
          "default": "missionControl",
          "description": "Action to trigger when dragging up.",
          "title": "Swipe up action"
        }
      },
      "type": "object"
    },
    "Scheme.Buttons.Gesture.GestureAction": {
      "enum": [
        "none",
        "missionControl.spaceLeft",
        "missionControl.spaceRight",
        "missionControl",
        "appExpose",
        "showDesktop",
        "launchpad"
      ],
      "type": "string"
    },
    "Scheme.Buttons.Gesture.Trigger": {
      "additionalProperties": false,
      "properties": {
        "button": {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.Button",
          "description": "The button number. See https://developer.apple.com/documentation/coregraphics/cgmousebutton",
          "title": "Button number"
        },
        "command": {
          "description": "Indicates if the command modifier key should be pressed.",
          "type": "boolean"
        },
        "control": {
          "description": "Indicates if the control modifier key should be pressed.",
          "type": "boolean"
        },
        "option": {
          "description": "Indicates if the option modifier key should be pressed.",
          "type": "boolean"
        },
        "shift": {
          "description": "Indicates if the shift modifier key should be pressed.",
          "type": "boolean"
        }
      },
      "required": [
        "button"
      ],
      "type": "object"
    },
    "Scheme.Buttons.Mapping": {
      "anyOf": [
        {
          "additionalProperties": false,
          "properties": {
            "action": {
              "$ref": "#/definitions/Scheme.Buttons.Mapping.Action",
              "title": "Action"
            },
            "button": {
              "$ref": "#/definitions/Scheme.Buttons.Mapping.Button",
              "description": "The button number. See https://developer.apple.com/documentation/coregraphics/cgmousebutton",
              "title": "Button number"
            },
            "command": {
              "description": "Indicates if the command modifier key should be pressed.",
              "type": "boolean"
            },
            "control": {
              "description": "Indicates if the control modifier key should be pressed.",
              "type": "boolean"
            },
            "hold": {
              "description": "Indicates if keyboard shortcut actions should stay pressed while the button is held. When enabled, LinearMouse sends key down on button press and key up on button release instead of repeating the shortcut.",
              "type": "boolean"
            },
            "option": {
              "description": "Indicates if the option modifier key should be pressed.",
              "type": "boolean"
            },
            "repeat": {
              "description": "Indicates if key repeat is enabled. If the value is true, the action will be repeatedly executed when the button is hold according to the key repeat settings in System Settings.",
              "type": "boolean"
            },
            "shift": {
              "description": "Indicates if the shift modifier key should be pressed.",
              "type": "boolean"
            }
          },
          "required": [
            "button"
          ],
          "type": "object"
        },
        {
          "additionalProperties": false,
          "properties": {
            "action": {
              "$ref": "#/definitions/Scheme.Buttons.Mapping.Action",
              "title": "Action"
            },
            "command": {
              "description": "Indicates if the command modifier key should be pressed.",
              "type": "boolean"
            },
            "control": {
              "description": "Indicates if the control modifier key should be pressed.",
              "type": "boolean"
            },
            "option": {
              "description": "Indicates if the option modifier key should be pressed.",
              "type": "boolean"
            },
            "scroll": {
              "$ref": "#/definitions/Scheme.Buttons.Mapping.ScrollDirection",
              "description": "Map scroll events to specific actions.",
              "title": "Scroll direction"
            },
            "shift": {
              "description": "Indicates if the shift modifier key should be pressed.",
              "type": "boolean"
            }
          },
          "required": [
            "scroll"
          ],
          "type": "object"
        }
      ]
    },
    "Scheme.Buttons.Mapping.Action": {
      "anyOf": [
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.SimpleAction"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.Run"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MouseWheelScrollUpWithDistance"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MouseWheelScrollDownWithDistance"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MouseWheelScrollLeftWithDistance"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MouseWheelScrollRightWithDistance"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.KeyPress"
        }
      ]
    },
    "Scheme.Buttons.Mapping.AppExpose": {
      "const": "appExpose",
      "description": "Application windows.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.Auto": {
      "const": "auto",
      "description": "Do not modify the button behavior.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.Button": {
      "anyOf": [
        {
          "$ref": "#/definitions/PhysicalButton"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.LogitechControlButton"
        }
      ]
    },
    "Scheme.Buttons.Mapping.DisplayBrightnessDown": {
      "const": "display.brightnessDown",
      "description": "Display: Brightness down.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.DisplayBrightnessUp": {
      "const": "display.brightnessUp",
      "description": "Display: Brightness up.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.Key": {
      "enum": [
        "enter",
        "tab",
        "space",
        "delete",
        "escape",
        "command",
        "shift",
        "capsLock",
        "option",
        "control",
        "commandRight",
        "shiftRight",
        "optionRight",
        "controlRight",
        "arrowLeft",
        "arrowRight",
        "arrowDown",
        "arrowUp",
        "home",
        "pageUp",
        "backspace",
        "end",
        "pageDown",
        "f1",
        "f2",
        "f3",
        "f4",
        "f5",
        "f6",
        "f7",
        "f8",
        "f9",
        "f10",
        "f11",
        "f12",
        "a",
        "b",
        "c",
        "d",
        "e",
        "f",
        "g",
        "h",
        "i",
        "j",
        "k",
        "l",
        "m",
        "n",
        "o",
        "p",
        "q",
        "r",
        "s",
        "t",
        "u",
        "v",
        "w",
        "x",
        "y",
        "z",
        "0",
        "1",
        "2",
        "3",
        "4",
        "5",
        "6",
        "7",
        "8",
        "9",
        "=",
        "-",
        ";",
        "'",
        ",",
        ".",
        "/",
        "\\",
        "`",
        "[",
        "]",
        "numpadPlus",
        "numpadMinus",
        "numpadMultiply",
        "numpadDivide",
        "numpadEnter",
        "numpadEquals",
        "numpadDecimal",
        "numpadClear",
        "numpad0",
        "numpad1",
        "numpad2",
        "numpad3",
        "numpad4",
        "numpad5",
        "numpad6",
        "numpad7",
        "numpad8",
        "numpad9"
      ],
      "type": "string"
    },
    "Scheme.Buttons.Mapping.KeyPress": {
      "additionalProperties": false,
      "properties": {
        "keyPress": {
          "description": "Keyboard: Keyboard shortcut.",
          "items": {
            "$ref": "#/definitions/Scheme.Buttons.Mapping.Key"
          },
          "type": "array"
        }
      },
      "required": [
        "keyPress"
      ],
      "type": "object"
    },
    "Scheme.Buttons.Mapping.KeyboardBrightnessDown": {
      "const": "keyboard.brightnessDown",
      "description": "Keyboard: Brightness down.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.KeyboardBrightnessUp": {
      "const": "keyboard.brightnessUp",
      "description": "Keyboard: Brightness up.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.Launchpad": {
      "const": "launchpad",
      "description": "Launchpad.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.LogitechControlButton": {
      "additionalProperties": false,
      "properties": {
        "controlID": {
          "$ref": "#/definitions/Int",
          "description": "Logitech control ID (CID)."
        },
        "kind": {
          "const": "logitechControl",
          "description": "Logitech control button identifier.",
          "type": "string"
        },
        "productID": {
          "anyOf": [
            {
              "$ref": "#/definitions/HexString"
            },
            {
              "$ref": "#/definitions/Int"
            }
          ],
          "description": "Match a specific Logitech device product ID when needed."
        },
        "serialNumber": {
          "description": "Match a specific Logitech device serial number when needed.",
          "type": "string"
        }
      },
      "required": [
        "kind",
        "controlID"
      ],
      "type": "object"
    },
    "Scheme.Buttons.Mapping.LookUpAndDataDetectors": {
      "const": "lookUpAndDataDetectors",
      "description": "Look up & data detectors.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.MediaFastForward": {
      "const": "media.fastForward",
      "description": "Media: Fast forward.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.MediaMute": {
      "const": "media.mute",
      "description": "Media: Toggle mute.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.MediaNext": {
      "const": "media.next",
      "description": "Media: Next.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.MediaPlayPause": {
      "const": "media.playPause",
      "description": "Media: Play / pause.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.MediaPrevious": {
      "const": "media.previous",
      "description": "Media: Previous.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.MediaRewind": {
      "const": "media.rewind",
      "description": "Media: Rewind.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.MediaVolumeDown": {
      "const": "media.volumeDown",
      "description": "Media: Volume down.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.MediaVolumeUp": {
      "const": "media.volumeUp",
      "description": "Media: Volume up.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.MissionControl": {
      "const": "missionControl",
      "description": "Mission Control.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.MissionControlSpaceLeft": {
      "const": "missionControl.spaceLeft",
      "description": "Mission Control: Move left a space.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.MissionControlSpaceRight": {
      "const": "missionControl.spaceRight",
      "description": "Mission Control: Move right a space.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.MouseButtonBack": {
      "const": "mouse.button.back",
      "description": "Mouse: Button: Act as back button.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.MouseButtonForward": {
      "const": "mouse.button.forward",
      "description": "Mouse: Button: Act as forward button.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.MouseButtonLeft": {
      "const": "mouse.button.left",
      "description": "Mouse: Button: Act as left button.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.MouseButtonMiddle": {
      "const": "mouse.button.middle",
      "description": "Mouse: Button: Act as middle button.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.MouseButtonRight": {
      "const": "mouse.button.right",
      "description": "Mouse: Button: Act as right button.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.MouseWheelScrollDown": {
      "const": "mouse.wheel.scrollDown",
      "description": "Mouse: Wheel: Scroll down.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.MouseWheelScrollDownWithDistance": {
      "additionalProperties": false,
      "properties": {
        "mouse.wheel.scrollDown": {
          "$ref": "#/definitions/Scheme.Scrolling.Distance",
          "description": "Mouse: Wheel: Scroll down a certain distance."
        }
      },
      "required": [
        "mouse.wheel.scrollDown"
      ],
      "type": "object"
    },
    "Scheme.Buttons.Mapping.MouseWheelScrollLeft": {
      "const": "mouse.wheel.scrollLeft",
      "description": "Mouse: Wheel: Scroll left.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.MouseWheelScrollLeftWithDistance": {
      "additionalProperties": false,
      "properties": {
        "mouse.wheel.scrollLeft": {
          "$ref": "#/definitions/Scheme.Scrolling.Distance",
          "description": "Mouse: Wheel: Scroll left a certain distance."
        }
      },
      "required": [
        "mouse.wheel.scrollLeft"
      ],
      "type": "object"
    },
    "Scheme.Buttons.Mapping.MouseWheelScrollRight": {
      "const": "mouse.wheel.scrollRight",
      "description": "Mouse: Wheel: Scroll right.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.MouseWheelScrollRightWithDistance": {
      "additionalProperties": false,
      "properties": {
        "mouse.wheel.scrollRight": {
          "$ref": "#/definitions/Scheme.Scrolling.Distance",
          "description": "Mouse: Wheel: Scroll right a certain distance."
        }
      },
      "required": [
        "mouse.wheel.scrollRight"
      ],
      "type": "object"
    },
    "Scheme.Buttons.Mapping.MouseWheelScrollUp": {
      "const": "mouse.wheel.scrollUp",
      "description": "Mouse: Wheel: Scroll up.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.MouseWheelScrollUpWithDistance": {
      "additionalProperties": false,
      "properties": {
        "mouse.wheel.scrollUp": {
          "$ref": "#/definitions/Scheme.Scrolling.Distance",
          "description": "Mouse: Wheel: Scroll up a certain distance."
        }
      },
      "required": [
        "mouse.wheel.scrollUp"
      ],
      "type": "object"
    },
    "Scheme.Buttons.Mapping.None": {
      "const": "none",
      "description": "Prevent the button events.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.Run": {
      "additionalProperties": false,
      "properties": {
        "run": {
          "description": "Run a specific command. For example, `\"open -a 'Mission Control'\"`.",
          "type": "string"
        }
      },
      "required": [
        "run"
      ],
      "type": "object"
    },
    "Scheme.Buttons.Mapping.ScrollDirection": {
      "description": "Scroll direction.",
      "enum": [
        "up",
        "down",
        "left",
        "right"
      ],
      "type": "string"
    },
    "Scheme.Buttons.Mapping.ShowDesktop": {
      "const": "showDesktop",
      "description": "Show desktop.",
      "type": "string"
    },
    "Scheme.Buttons.Mapping.SimpleAction": {
      "anyOf": [
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.Auto"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.None"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MissionControlSpaceLeft"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MissionControlSpaceRight"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MissionControl"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.AppExpose"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.Launchpad"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.ShowDesktop"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.LookUpAndDataDetectors"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.SmartZoom"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.DisplayBrightnessUp"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.DisplayBrightnessDown"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MediaVolumeUp"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MediaVolumeDown"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MediaMute"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MediaPlayPause"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MediaNext"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MediaPrevious"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MediaFastForward"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MediaRewind"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.KeyboardBrightnessUp"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.KeyboardBrightnessDown"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MouseWheelScrollUp"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MouseWheelScrollDown"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MouseWheelScrollLeft"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MouseWheelScrollRight"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MouseButtonLeft"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MouseButtonMiddle"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MouseButtonRight"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MouseButtonBack"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.Mapping.MouseButtonForward"
        }
      ]
    },
    "Scheme.Buttons.Mapping.SmartZoom": {
      "const": "smartZoom",
      "description": "Smart zoom.",
      "type": "string"
    },
    "Scheme.Buttons.UniversalBackForward": {
      "anyOf": [
        {
          "type": "boolean"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.UniversalBackForward.BackOnly"
        },
        {
          "$ref": "#/definitions/Scheme.Buttons.UniversalBackForward.ForwardOnly"
        }
      ]
    },
    "Scheme.Buttons.UniversalBackForward.BackOnly": {
      "const": "backOnly",
      "description": "Enable universal back only.",
      "type": "string"
    },
    "Scheme.Buttons.UniversalBackForward.ForwardOnly": {
      "const": "forwardOnly",
      "description": "Enable universal forward only.",
      "type": "string"
    },
    "Scheme.If": {
      "additionalProperties": false,
      "properties": {
        "app": {
          "description": "Match apps by providing the bundle ID. For example, `com.apple.Safari`.",
          "title": "App",
          "type": "string"
        },
        "device": {
          "$ref": "#/definitions/Scheme.If.Device",
          "description": "Match one or more devices. If not provided, the scheme is activated on all devices.",
          "title": "Device"
        },
        "display": {
          "description": "Match displays by providing the display name. For example, `DELL P2415Q`.",
          "title": "Display name",
          "type": "string"
        },
        "groupApp": {
          "description": "Match apps by providing the bundle ID of the process group. For example, `org.polymc.PolyMC`.",
          "title": "Group app",
          "type": "string"
        },
        "parentApp": {
          "description": "Match apps by providing the bundle ID of the parent process. For example, `org.polymc.PolyMC`.",
          "title": "Parent app",
          "type": "string"
        },
        "processName": {
          "description": "Match by the executable file name of the frontmost process (from NSRunningApplication.executableURL.lastPathComponent). Case-sensitive.",
          "title": "Process name",
          "type": "string"
        },
        "processPath": {
          "description": "Match by the absolute executable path of the frontmost process (from NSRunningApplication.executableURL.path). Case-sensitive.",
          "title": "Process path",
          "type": "string"
        }
      },
      "type": "object"
    },
    "Scheme.If.Category": {
      "anyOf": [
        {
          "$ref": "#/definitions/Scheme.If.Mouse"
        },
        {
          "$ref": "#/definitions/Scheme.If.Trackpad"
        }
      ]
    },
    "Scheme.If.Device": {
      "additionalProperties": false,
      "properties": {
        "category": {
          "$ref": "#/definitions/SingleValueOrArray%3CScheme.If.Category%3E",
          "description": "The category of the devices.",
          "title": "Category"
        },
        "productID": {
          "anyOf": [
            {
              "$ref": "#/definitions/HexString"
            },
            {
              "$ref": "#/definitions/Int"
            }
          ],
          "description": "The product ID of the devices.",
          "examples": [
            "0xA123"
          ],
          "title": "Product ID"
        },
        "productName": {
          "description": "The product name of the devices.",
          "title": "Product name",
          "type": "string"
        },
        "serialNumber": {
          "description": "The serial number of the devices.",
          "title": "Serial number",
          "type": "string"
        },
        "vendorID": {
          "anyOf": [
            {
              "$ref": "#/definitions/HexString"
            },
            {
              "$ref": "#/definitions/Int"
            }
          ],
          "description": "The vendor ID of the devices.",
          "examples": [
            "0xA123"
          ],
          "title": "Vendor ID"
        }
      },
      "type": "object"
    },
    "Scheme.If.Mouse": {
      "const": "mouse",
      "description": "Match mouse devices.",
      "title": "Mouse",
      "type": "string"
    },
    "Scheme.If.Trackpad": {
      "const": "trackpad",
      "description": "Match trackpad devices.",
      "title": "Trackpad",
      "type": "string"
    },
    "Scheme.Pointer": {
      "additionalProperties": false,
      "properties": {
        "acceleration": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "$ref": "#/definitions/Unset"
            }
          ],
          "description": "A number to set acceleration, or \"unset\" to restore system default. If omitted, the previous/merged value applies.",
          "maximum": 20,
          "minimum": 0,
          "title": "Pointer acceleration"
        },
        "disableAcceleration": {
          "default": false,
          "description": "If the value is true, the pointer acceleration will be disabled and acceleration and speed will not take effect.",
          "title": "Disable pointer acceleration",
          "type": "boolean"
        },
        "redirectsToScroll": {
          "default": false,
          "description": "If the value is true, pointer movements will be redirected to scroll events.",
          "title": "Redirects to scroll",
          "type": "boolean"
        },
        "speed": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "$ref": "#/definitions/Unset"
            }
          ],
          "description": "A number to set speed, or \"unset\" to restore device default. If omitted, the previous/merged value applies.",
          "maximum": 1,
          "title": "Pointer speed"
        }
      },
      "type": "object"
    },
    "Scheme.Scrolling": {
      "additionalProperties": false,
      "properties": {
        "acceleration": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "additionalProperties": false,
              "properties": {
                "horizontal": {
                  "type": "number"
                },
                "vertical": {
                  "type": "number"
                }
              },
              "type": "object"
            }
          ],
          "default": 1,
          "description": "The scrolling acceleration."
        },
        "distance": {
          "anyOf": [
            {
              "$ref": "#/definitions/Scheme.Scrolling.Distance"
            },
            {
              "additionalProperties": false,
              "properties": {
                "horizontal": {
                  "$ref": "#/definitions/Scheme.Scrolling.Distance"
                },
                "vertical": {
                  "$ref": "#/definitions/Scheme.Scrolling.Distance"
                }
              },
              "type": "object"
            }
          ],
          "description": "The distance after rolling the wheel.",
          "title": "Scroll distance"
        },
        "modifiers": {
          "anyOf": [
            {
              "$ref": "#/definitions/Scheme.Scrolling.Modifiers"
            },
            {
              "additionalProperties": false,
              "properties": {
                "horizontal": {
                  "$ref": "#/definitions/Scheme.Scrolling.Modifiers"
                },
                "vertical": {
                  "$ref": "#/definitions/Scheme.Scrolling.Modifiers"
                }
              },
              "type": "object"
            }
          ],
          "title": "Modifier keys settings"
        },
        "reverse": {
          "anyOf": [
            {
              "type": "boolean"
            },
            {
              "additionalProperties": false,
              "properties": {
                "horizontal": {
                  "type": "boolean"
                },
                "vertical": {
                  "type": "boolean"
                }
              },
              "type": "object"
            }
          ],
          "title": "Reverse scrolling"
        },
        "smoothed": {
          "anyOf": [
            {
              "$ref": "#/definitions/Scheme.Scrolling.Smoothed"
            },
            {
              "additionalProperties": false,
              "properties": {
                "horizontal": {
                  "$ref": "#/definitions/Scheme.Scrolling.Smoothed"
                },
                "vertical": {
                  "$ref": "#/definitions/Scheme.Scrolling.Smoothed"
                }
              },
              "type": "object"
            }
          ],
          "description": "Use a preset curve or fine-tune response, speed, acceleration, and inertia.",
          "title": "Smoothed scrolling"
        },
        "speed": {
          "anyOf": [
            {
              "type": "number"
            },
            {
              "additionalProperties": false,
              "properties": {
                "horizontal": {
                  "type": "number"
                },
                "vertical": {
                  "type": "number"
                }
              },
              "type": "object"
            }
          ],
          "default": 0,
          "description": "The scrolling speed."
        }
      },
      "type": "object"
    },
    "Scheme.Scrolling.Auto": {
      "const": "auto",
      "description": "The scrolling distance will not be modified.",
      "type": "string"
    },
    "Scheme.Scrolling.Distance": {
      "anyOf": [
        {
          "$ref": "#/definitions/Scheme.Scrolling.Auto"
        },
        {
          "$ref": "#/definitions/Scheme.Scrolling.Distance.Line"
        },
        {
          "$ref": "#/definitions/Scheme.Scrolling.Distance.Pixel"
        }
      ]
    },
    "Scheme.Scrolling.Distance.Line": {
      "anyOf": [
        {
          "$ref": "#/definitions/Int"
        },
        {
          "$ref": "#/definitions/IntString"
        }
      ],
      "description": "The scrolling distance in lines."
    },
    "Scheme.Scrolling.Distance.Pixel": {
      "description": "The scrolling distance in pixels.",
      "pattern": "^\\d[1-9]*(\\.\\d+)?px",
      "type": "string"
    },
    "Scheme.Scrolling.Modifiers": {
      "additionalProperties": false,
      "properties": {
        "command": {
          "$ref": "#/definitions/Scheme.Scrolling.Modifiers.Action",
          "description": "The action when command key is pressed."
        },
        "control": {
          "$ref": "#/definitions/Scheme.Scrolling.Modifiers.Action",
          "description": "The action when control key is pressed."
        },
        "option": {
          "$ref": "#/definitions/Scheme.Scrolling.Modifiers.Action",
          "description": "The action when option key is pressed."
        },
        "shift": {
          "$ref": "#/definitions/Scheme.Scrolling.Modifiers.Action",
          "description": "The action when shift key is pressed."
        }
      },
      "type": "object"
    },
    "Scheme.Scrolling.Modifiers.Action": {
      "anyOf": [
        {
          "$ref": "#/definitions/Scheme.Scrolling.Modifiers.None"
        },
        {
          "$ref": "#/definitions/Scheme.Scrolling.Modifiers.Auto"
        },
        {
          "$ref": "#/definitions/Scheme.Scrolling.Modifiers.Ignore"
        },
        {
          "$ref": "#/definitions/Scheme.Scrolling.Modifiers.PreventDefault"
        },
        {
          "$ref": "#/definitions/Scheme.Scrolling.Modifiers.AlterOrientation"
        },
        {
          "$ref": "#/definitions/Scheme.Scrolling.Modifiers.ChangeSpeed"
        },
        {
          "$ref": "#/definitions/Scheme.Scrolling.Modifiers.Zoom"
        },
        {
          "$ref": "#/definitions/Scheme.Scrolling.Modifiers.PinchZoom"
        }
      ]
    },
    "Scheme.Scrolling.Modifiers.AlterOrientation": {
      "additionalProperties": false,
      "description": "Alter the scrolling orientation from vertical to horizontal or vice versa.",
      "properties": {
        "type": {
          "const": "alterOrientation",
          "type": "string"
        }
      },
      "required": [
        "type"
      ],
      "type": "object"
    },
    "Scheme.Scrolling.Modifiers.Auto": {
      "additionalProperties": false,
      "description": "Default action.",
      "properties": {
        "type": {
          "const": "auto",
          "type": "string"
        }
      },
      "required": [
        "type"
      ],
      "type": "object"
    },
    "Scheme.Scrolling.Modifiers.ChangeSpeed": {
      "additionalProperties": false,
      "description": "Scale the scrolling speed.",
      "properties": {
        "scale": {
          "description": "The factor to scale the scrolling speed.",
          "type": "number"
        },
        "type": {
          "const": "changeSpeed",
          "type": "string"
        }
      },
      "required": [
        "type",
        "scale"
      ],
      "type": "object"
    },
    "Scheme.Scrolling.Modifiers.Ignore": {
      "additionalProperties": false,
      "description": "Ignore modifier.",
      "properties": {
        "type": {
          "const": "ignore",
          "type": "string"
        }
      },
      "required": [
        "type"
      ],
      "type": "object"
    },
    "Scheme.Scrolling.Modifiers.None": {
      "additionalProperties": false,
      "deprecated": true,
      "description": "Default action.",
      "properties": {
        "type": {
          "const": "none",
          "type": "string"
        }
      },
      "required": [
        "type"
      ],
      "type": "object"
    },
    "Scheme.Scrolling.Modifiers.PinchZoom": {
      "additionalProperties": false,
      "description": "Zoom in and out using pinch gestures.",
      "properties": {
        "type": {
          "const": "pinchZoom",
          "type": "string"
        }
      },
      "required": [
        "type"
      ],
      "type": "object"
    },
    "Scheme.Scrolling.Modifiers.PreventDefault": {
      "additionalProperties": false,
      "description": "No action.",
      "properties": {
        "type": {
          "const": "preventDefault",
          "type": "string"
        }
      },
      "required": [
        "type"
      ],
      "type": "object"
    },
    "Scheme.Scrolling.Modifiers.Zoom": {
      "additionalProperties": false,
      "description": "Zoom in and out using ⌘+ and ⌘-.",
      "properties": {
        "type": {
          "const": "zoom",
          "type": "string"
        }
      },
      "required": [
        "type"
      ],
      "type": "object"
    },
    "Scheme.Scrolling.Smoothed": {
      "additionalProperties": false,
      "properties": {
        "acceleration": {
          "description": "The scrolling acceleration.",
          "type": "number"
        },
        "enabled": {
          "default": true,
          "description": "Set to `false` to explicitly disable inherited smoothed scrolling for this direction.",
          "type": "boolean"
        },
        "inertia": {
          "description": "The scrolling inertia.",
          "type": "number"
        },
        "preset": {
          "$ref": "#/definitions/Scheme.Scrolling.Smoothed.Preset",
          "default": "natural",
          "description": "The preset curve to use."
        },
        "response": {
          "description": "How quickly the scrolling responds to input.",
          "type": "number"
        },
        "speed": {
          "description": "The scrolling speed.",
          "type": "number"
        }
      },
      "type": "object"
    },
    "Scheme.Scrolling.Smoothed.Preset": {
      "enum": [
        "custom",
        "linear",
        "easeIn",
        "easeOut",
        "easeInOut",
        "easeOutIn",
        "quadratic",
        "cubic",
        "quartic",
        "easeOutCubic",
        "easeInOutCubic",
        "easeOutQuartic",
        "easeInOutQuartic",
        "quintic",
        "sine",
        "exponential",
        "circular",
        "back",
        "bounce",
        "elastic",
        "spring",
        "natural",
        "smooth",
        "snappy",
        "gentle"
      ],
      "type": "string"
    },
    "Secondary": {
      "const": 1,
      "description": "Secondary button, usually the right button.",
      "type": "number"
    },
    "SingleValueOrArray<Scheme.Buttons.AutoScroll.Mode>": {
      "anyOf": [
        {
          "$ref": "#/definitions/Scheme.Buttons.AutoScroll.Mode"
        },
        {
          "items": {
            "$ref": "#/definitions/Scheme.Buttons.AutoScroll.Mode"
          },
          "type": "array"
        }
      ]
    },
    "SingleValueOrArray<Scheme.If.Category>": {
      "anyOf": [
        {
          "$ref": "#/definitions/Scheme.If.Category"
        },
        {
          "items": {
            "$ref": "#/definitions/Scheme.If.Category"
          },
          "type": "array"
        }
      ]
    },
    "SingleValueOrArray<Scheme.If>": {
      "anyOf": [
        {
          "$ref": "#/definitions/Scheme.If"
        },
        {
          "items": {
            "$ref": "#/definitions/Scheme.If"
          },
          "type": "array"
        }
      ]
    },
    "Unset": {
      "const": "unset",
      "description": "A special value that explicitly restores a setting to the system or device default. Currently supported in pointer settings; may be supported more broadly in the future.",
      "title": "Unset",
      "type": "string"
    }
  }
}


================================================
FILE: Documentation/Configuration.md
================================================
# Configuration

The LinearMouse configuration is stored in `~/.config/linearmouse/linearmouse.json`.

If the configuration file does not exist, LinearMouse will create an empty configuration automatically.

> **Note**  
> It's preferable to use the GUI to alter settings rather than manually updating configuration
> unless you want to use advanced features.

> **Note**  
> JSON5 is not supported yet. Writing comments in configuration will raise a parsing error.

## Get started

Here is a simple example of LinearMouse configuration.

```json
{
  "$schema": "https://app.linearmouse.org/schema/0.7.2",
  "schemes": [
    {
      "if": {
        "device": {
          "category": "mouse"
        }
      },
      "scrolling": {
        "reverse": {
          "vertical": true
        }
      }
    }
  ]
}
```

This configuration reverses the vertical scrolling direction for any mouse connected to your device.

## JSON Schema

As you can see, `$schema` defines the JSON schema of the LinearMouse configuration, which enables
autocompletion in editors like VS Code.

SON schemas are published for each LinearMouse version. Backward compatibility is guaranteed for
the same major versions.

## Schemes

A scheme is a collection of settings that are activated in specified circumstances.

For example, in [get started](#get-started), we defined a scheme. The `if` field instructs
LinearMouse to activate this scheme only when the active device is a mouse:

```json
{
  "if": {
    "device": {
      "category": "mouse"
    }
  }
}
```

And the `scrolling` field in this scheme defines the scrolling behaviors, with
`"reverse": { "vertical": true }` reversing the vertical scrolling direction:

```json
{
  "scrolling": {
    "reverse": {
      "vertical": true
    }
  }
}
```

## Smoothed scrolling

`scrolling.smoothed` enables a phase-aware scrolling curve that can be tuned separately for
vertical and horizontal scrolling. You can choose a preset such as `easeIn`, `easeOut`,
`easeInOut`, `quadratic`, `cubic`, `easeOutCubic`, `easeInOutCubic`, `quartic`,
`easeOutQuartic`, `easeInOutQuartic`, `smooth`, or `custom`, then fine-tune `response`,
`speed`, `acceleration`, and `inertia` as needed.

Set `enabled` to `false` to explicitly disable an inherited smoothed scrolling configuration for a
direction.

For example, to use a smoother scrolling profile for a mouse:

```json
{
  "schemes": [
    {
      "if": {
        "device": {
          "category": "mouse"
        }
      },
      "scrolling": {
        "smoothed": {
          "enabled": true,
          "preset": "easeInOut",
          "response": 0.45,
          "speed": 1,
          "acceleration": 1.2,
          "inertia": 0.65
        }
      }
    }
  ]
}
```

If you want different tuning for each direction, provide `vertical` and `horizontal` values under
`smoothed`.

## Device matching

Vendor ID and product ID can be provided to match a specific device.

You may find these values in About This Mac → System Report... → Bluetooth / USB.

For example, to configure pointer speed of my Logitech mouse and Microsoft mouse respectively,
I would create two schemes and specify the vendor ID and product ID:

```json
{
  "schemes": [
    {
      "if": {
        "device": {
          "vendorID": "0x046d",
          "productID": "0xc52b"
        }
      },
      "pointer": {
        "acceleration": 0,
        "speed": 0.36
      }
    },
    {
      "if": {
        "device": {
          "vendorID": "0x045e",
          "productID": "0x0827"
        }
      },
      "pointer": {
        "acceleration": 0,
        "speed": 0.4
      }
    }
  ]
}
```

Then, the pointer speed of my Logitech mouse and Microsoft mouse will be set to 0.36 and 0.4
respectively.

### Unsetting values

LinearMouse supports a special "unset" value to explicitly restore settings back to their system or
device defaults. This differs from omitting a field, which keeps the previously merged value.

Currently, "unset" is supported for pointer acceleration and speed.

```json
{
  "schemes": [
    {
      "if": {
        "device": { "category": "mouse" }
      },
      "pointer": { "acceleration": "unset", "speed": "unset" }
    }
  ]
}
```

## App matching

App bundle ID can be provided to match a specific app.

For example, to modify the pointer acceleration in Safari for my Logitech mouse:

```json
{
  "schemes": [
    {
      "if": {
        "device": {
          "vendorID": "0x046d",
          "productID": "0xc52b"
        },
        "app": "com.apple.Safari"
      },
      "pointer": {
        "acceleration": 0.5
      }
    }
  ]
}
```

Or, to disable reverse scrolling in Safari for all devices:

```json
{
  "schemes": [
    {
      "if": {
        "app": "com.apple.Safari"
      },
      "scrolling": {
        "reverse": {
          "vertical": false,
          "horizontal": false
        }
      }
    }
  ]
}
```

By default, LinearMouse checks the app bundle ID of the frontmost process. However, in some
circumstances, a program might not be placed in a specific application bundle. In that case, you
may specify the app bundle ID of the parent process or the process group of the frontmost process
by specify `parentApp` and `groupApp`.

For example, to match the Minecraft (a Java process) launched by PolyMC:

```json
{
  "schemes": [
    {
      "if": {
        "parentApp": "org.polymc.PolyMC"
      }
    }
  ]
}
```

Or, to match the whole process group:

```json
{
  "schemes": [
    {
      "if": {
        "groupApp": "org.polymc.PolyMC"
      }
    }
  ]
}
```

### Process (binary) matching

Some programs do not have a stable or any bundle identifier. You can match by the frontmost process's executable instead.

- processName: Match by executable name (case-sensitive). Example:

```json
{
  "schemes": [
    {
      "if": {
        "processName": "wezterm"
      },
      "scrolling": { "reverse": false }
    }
  ]
}
```

- processPath: Match by absolute executable path (case-sensitive). Example:

```json
{
  "schemes": [
    {
      "if": {
        "processPath": "/Applications/WezTerm.app/Contents/MacOS/WezTerm"
      },
      "pointer": { "acceleration": 0.4 }
    }
  ]
}
```

Notes
- processName/processPath compare exactly; no wildcard or regex.
- Matching is against the frontmost application process (NSRunningApplication); child processes inside a terminal are not detected as the frontmost process.
- You can still combine with device and display conditions.

## Display Matching

Display name can be provided to match a specific display.

For example, to modify the pointer acceleration on DELL P2415Q:

```json
{
  "schemes": [
    {
      "if": {
        "device": {
          "vendorID": "0x046d",
          "productID": "0xc52b"
        },
        "display": "DELL P2415Q"
      },
      "pointer": {
        "acceleration": 0.5
      }
    }
  ]
}
```

## Schemes merging and multiple `if`s

If multiple schemes are activated at the same time, they will be merged in the order of their
definitions.

Additionally, if multiple `if`s are specified, the scheme will be activated as long as any of them
is satisfied.

For example, the configuration above can alternatively be written as:

```json
{
  "schemes": [
    {
      "if": [
        {
          "device": {
            "vendorID": "0x046d",
            "productID": "0xc52b"
          }
        },
        {
          "device": {
            "vendorID": "0x045e",
            "productID": "0x0827"
          }
        }
      ],
      "pointer": {
        "acceleration": 0
      }
    },
    {
      "if": {
        "device": {
          "vendorID": "0x046d",
          "productID": "0xc52b"
        }
      },
      "pointer": {
        "speed": 0.36
      }
    },
    {
      "if": {
        "device": {
          "vendorID": "0x045e",
          "productID": "0x0827"
        }
      },
      "pointer": {
        "speed": 0.4
      }
    }
  ]
}
```

Or, with fewer lines but more difficult to maintain:

```json
{
  "schemes": [
    {
      "if": [
        {
          "device": {
            "vendorID": "0x046d",
            "productID": "0xc52b"
          }
        },
        {
          "device": {
            "vendorID": "0x045e",
            "productID": "0x0827"
          }
        }
      ],
      "pointer": {
        "acceleration": 0,
        "speed": 0.36
      }
    },
    {
      "if": {
        "device": {
          "vendorID": "0x045e",
          "productID": "0x0827"
        }
      },
      "pointer": {
        "speed": 0.4
      }
    }
  ]
}
```

## Button mappings

Button mappings is a list that allows you to assign actions to buttons or scroll wheels.
For example, to open Launchpad when the wheel button is clicked, or to switch spaces when
<kbd>command + back</kbd> or <kbd>command + forward</kbd> is clicked.

### Basic example

```json
{
  "schemes": [
    {
      "if": [
        {
          "device": {
            "category": "mouse"
          }
        }
      ],
      "buttons": {
        "mappings": [
          {
            "button": 2,
            "action": "launchpad"
          }
        ]
      }
    }
  ]
}
```

In this example, the wheel button is bound to open Launchpad.

`"button": 2` denotes the auxiliary button, which is usually the wheel button.

The following table lists all the buttons:

| Button | Description                                                      |
| ------ | ---------------------------------------------------------------- |
| 0      | Primary button, usually the left button.                         |
| 1      | Secondary button, usually the right button.                      |
| 2      | Auxiliary button, usually the wheel button or the middle button. |
| 3      | The fourth button, typically the back button.                    |
| 4      | The fifth button, typically the forward button.                  |
| 5-31   | Other buttons.                                                   |

`{ "action": { "run": "open -a Launchpad" } }` assigns a shell command `open -a LaunchPad` to
the button. When the button is clicked, the shell command will be executed.

### Modifier keys

In this example, <kbd>command + forward</kbd> is bound to open Mission Control.

```json
{
  "schemes": [
    {
      "if": [
        {
          "device": {
            "category": "mouse"
          }
        }
      ],
      "buttons": {
        "mappings": [
          {
            "button": 4,
            "command": true,
            "action": "missionControl"
          }
        ]
      }
    }
  ]
}
```

`"command": true` denotes that <kbd>command</kbd> should be pressed.

You can specify `shift`, `option` and `control` as well.

### Switch spaces (desktops) with the <kbd>command + back</kbd> and <kbd>command + forward</kbd>

`missionControl.spaceLeft` and `missionControl.spaceRight` can be used to move left and right a space.

```json
{
  "schemes": [
    {
      "if": [
        {
          "device": {
            "category": "mouse"
          }
        }
      ],
      "buttons": {
        "mappings": [
          {
            "button": 3,
            "command": true,
            "action": "missionControl.spaceLeft"
          },
          {
            "button": 4,
            "command": true,
            "action": "missionControl.spaceRight"
          }
        ]
      }
    }
  ]
}
```

> **Note**  
> You will have to grant an additional permission to allow LinearMouse to simulate keys.

### Key repeat

With `repeat: true`, actions will be repeated until the button is up.

In this example, <kbd>option + back</kbd> and <kbd>option + forward</kbd> is bound to volume down
and volume up.

If you hold <kbd>option + back</kbd>, the volume will continue to decrease.

> **Note**  
> If you disabled key repeat in System Settings, `repeat: true` will not work.
> If you change key repeat rate or delay until repeat in System Settings, you have to restart
> LinearMouse to take effect.

```json
{
  "schemes": [
    {
      "if": [
        {
          "device": {
            "category": "mouse"
          }
        }
      ],
      "buttons": {
        "mappings": [
          {
            "button": 4,
            "repeat": true,
            "option": true,
            "action": "media.volumeUp"
          },
          {
            "button": 3,
            "repeat": true,
            "option": true,
            "action": "media.volumeDown"
          }
        ]
      }
    }
  ]
}
```

### Hold keyboard shortcuts while pressed

With `hold: true`, keyboard shortcut actions stay pressed for as long as the mouse button is held.

This is different from `repeat: true`:

- `repeat: true` keeps sending the shortcut over and over.
- `hold: true` sends key down when the mouse button is pressed, then key up when it is released.

This is useful for apps that expect a real held key, such as timeline scrubbing or temporary tools.

```json
{
  "schemes": [
    {
      "if": {
        "device": {
          "category": "mouse"
        }
      },
      "buttons": {
        "mappings": [
          {
            "button": 3,
            "hold": true,
            "action": {
              "keyPress": ["c"]
            }
          }
        ]
      }
    }
  ]
}
```

### Volume up and down with <kbd>option + scrollUp</kbd> and <kbd>option + scrollDown</kbd>

`scroll` can be specified instead of `button` to map scroll events to specific actions.

```json
{
  "schemes": [
    {
      "if": [
        {
          "device": {
            "category": "mouse"
          }
        }
      ],
      "buttons": {
        "mappings": [
          {
            "scroll": "up",
            "option": true,
            "action": "media.volumeUp"
          },
          {
            "scroll": "down",
            "option": true,
            "action": "media.volumeDown"
          }
        ]
      }
    }
  ]
}
```

### Swap back and forward buttons

```json
{
  "schemes": [
    {
      "if": [
        {
          "device": {
            "category": "mouse"
          }
        }
      ],
      "buttons": {
        "mappings": [
          {
            "button": 3,
            "action": "mouse.button.forward"
          },
          {
            "button": 4,
            "action": "mouse.button.back"
          }
        ]
      }
    }
  ]
}
```

### Action sheet

#### Simple actions

A simple action is an action without any parameters.

```json
{
  "action": "<action>"
}
```

`<action>` could be one of:

| Action                      | Description                           |
| --------------------------- | ------------------------------------- |
| `auto`                      | Do not modify the button behavior.    |
| `none`                      | Prevent the button events.            |
| `missionControl`            | Mission Control.                      |
| `missionControl.spaceLeft`  | Mission Control: Move left a space.   |
| `missionControl.spaceRight` | Mission Control: Move right a space.  |
| `appExpose`                 | App Exposé.                           |
| `launchpad`                 | Launchpad.                            |
| `showDesktop`               | Show desktop.                         |
| `showDesktop`               | Show desktop.                         |
| `lookUpAndDataDetectors`    | Look up & data detectors.             |
| `smartZoom`                 | Smart zoom.                           |
| `display.brightnessUp`      | Display: Brightness up.               |
| `display.brightnessDown`    | Display: Brightness down.             |
| `media.volumeUp`            | Media: Volume up.                     |
| `media.volumeDown`          | Media: Volume down.                   |
| `media.mute`                | Media: Toggle mute.                   |
| `media.playPause`           | Media: Play / pause.                  |
| `media.next`                | Media: Next.                          |
| `media.previous`            | Media: Previous.                      |
| `media.fastForward`         | Media: Fast forward.                  |
| `media.rewind`              | Media: Rewind.                        |
| `keyboard.brightnessUp`     | Keyboard: Brightness up.              |
| `keyboard.brightnessDown`   | Keyboard: Brightness down.            |
| `mouse.wheel.scrollUp`      | Mouse: Wheel: Scroll up.              |
| `mouse.wheel.scrollDown`    | Mouse: Wheel: Scroll down.            |
| `mouse.wheel.scrollLeft`    | Mouse: Wheel: Scroll left.            |
| `mouse.wheel.scrollRight`   | Mouse: Wheel: Scroll right.           |
| `mouse.button.left`         | Mouse: Button: Act as left button.    |
| `mouse.button.middle`       | Mouse: Button: Act as middle button.  |
| `mouse.button.right`        | Mouse: Button: Act as right button.   |
| `mouse.button.back`         | Mouse: Button: Act as back button.    |
| `mouse.button.forward`      | Mouse: Button: Act as forward button. |

#### Run shell commands

```json
{
  "action": {
    "run": "<command>"
  }
}
```

The `<command>` will be executed with bash.

#### Scroll a certain distance

##### Scroll up 2 lines

```json
{
  "action": {
    "mouse.wheel.scrollUp": 2
  }
}
```

##### Scroll left 32 pixels

```json
{
  "action": {
    "mouse.wheel.scrollLeft": "32px"
  }
}
```

#### Press keyboard shortcuts

```json
{
  "action": {
    "keyPress": ["shift", "command", "4"]
  }
}
```

To see the full list of keys, please refer to [Configuration.d.ts#L652](Configuration.d.ts#L652).

#### Numpad keys support

LinearMouse supports all numpad keys for keyboard shortcuts:

- Number keys: `numpad0`, `numpad1`, `numpad2`, `numpad3`, `numpad4`, `numpad5`, `numpad6`, `numpad7`, `numpad8`, `numpad9`
- Operator keys: `numpadPlus`, `numpadMinus`, `numpadMultiply`, `numpadDivide`, `numpadEquals`
- Function keys: `numpadEnter`, `numpadDecimal`, `numpadClear`

Example usage:
```json
{
  "action": {
    "keyPress": ["numpad5"]
  }
}
```

## Pointer settings

### Redirects to scroll

The `redirectsToScroll` property allows you to redirect pointer movements to scroll events. This is useful for scenarios where you want mouse movements to control scrolling instead of cursor positioning.

```json
{
  "schemes": [
    {
      "if": {
        "device": {
          "category": "mouse"
        }
      },
      "pointer": {
        "redirectsToScroll": true
      }
    }
  ]
}
```

When `redirectsToScroll` is set to `true`, horizontal mouse movements will generate horizontal scroll events, and vertical mouse movements will generate vertical scroll events.


================================================
FILE: Documentation/translate-xcstrings.md
================================================
# XCStrings LLM Translation Script

`Scripts/translate-xcstrings.mjs` uses Xcode's native localization export/import flow, then fills unfinished xcstrings entries with an LLM through OpenRouter.

What it does:

- exports `.xcloc` / `.xliff` bundles with `xcodebuild -exportLocalizations`
- inspects only `.xcstrings` translation units inside the exported XLIFF
- skips units whose target is already `translated`
- sends only unfinished units to the model
- imports the translated XLIFF back with `xcodebuild -importLocalizations -mergeImport`

## Install

```bash
npm install
```

## Required environment variables

```bash
export OPENROUTER_API_KEY="your-api-key"
```

Optional:

```bash
export OPENROUTER_MODEL="openai/gpt-4.1-mini"
export OPENROUTER_SITE_URL="https://github.com/linearmouse/linearmouse"
export OPENROUTER_APP_NAME="LinearMouse xcstrings translator"
```

## Usage

Dry run first:

```bash
npm run translate:xcstrings -- --dry-run
```

Translate everything unfinished:

```bash
npm run translate:xcstrings
```

Translate only selected languages:

```bash
npm run translate:xcstrings -- --languages ja,zh-Hans,zh-Hant
```

Use a different model or smaller batches:

```bash
npm run translate:xcstrings -- --model openai/gpt-4.1 --batch-size 5
```

Limit a test run to a few translation units:

```bash
npm run translate:xcstrings -- --max-units 20
```

Keep the exported localization bundle for inspection:

```bash
npm run translate:xcstrings -- --languages ja --keep-export
```

## Notes

- The script uses the OpenAI SDK against OpenRouter's OpenAI-compatible endpoint.
- Extraction and import are handled by Xcode, not by a custom xcstrings parser.
- The script only edits XLIFF units whose `original` file ends with `.xcstrings`; other exported resources are left untouched.
- Xcode's exported XLIFF already expands plural and variation entries into individual `trans-unit` items, so the model works on those units directly.
- Review the diff after each run because model translations can still need terminology cleanup.


================================================
FILE: ExportOptions.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>destination</key>
	<string>export</string>
	<key>method</key>
	<string>developer-id</string>
	<key>signingCertificate</key>
	<string>Developer ID Application</string>
	<key>signingStyle</key>
	<string>manual</string>
	<key>teamID</key>
	<string>C5686NKYJ7</string>
</dict>
</plist>


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2021-2024 LinearMouse

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: LinearMouse/AccessibilityPermission.swift
================================================
// MIT License
// Copyright (c) 2021-2026 LinearMouse

import Foundation
import os.log
import SwiftUI

enum AccessibilityPermission {
    private static let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "AccessibilityPermission")

    static var enabled: Bool {
        AXIsProcessTrustedWithOptions([
            kAXTrustedCheckOptionPrompt.takeUnretainedValue(): false
        ] as CFDictionary)
    }

    static func prompt() {
        AXIsProcessTrustedWithOptions([
            kAXTrustedCheckOptionPrompt.takeUnretainedValue(): true
        ] as CFDictionary)
    }

    static func pollingUntilEnabled(completion: @escaping () -> Void) {
        guard enabled else {
            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                os_log("Polling accessibility permission", log: log, type: .info)
                pollingUntilEnabled(completion: completion)
            }
            return
        }
        completion()
    }
}

enum AccessibilityPermissionError: Error {
    case resetError
}


================================================
FILE: LinearMouse/AppDelegate.swift
================================================
// MIT License
// Copyright (c) 2021-2026 LinearMouse

import AppMover
import Combine
import LaunchAtLogin
import os.log
import SwiftUI

@main
class AppDelegate: NSObject, NSApplicationDelegate {
    private static let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "AppDelegate")

    private let autoUpdateManager = AutoUpdateManager.shared
    private let statusItem = StatusItem.shared
    private var subscriptions = Set<AnyCancellable>()

    func applicationDidFinishLaunching(_: Notification) {
        guard ProcessEnvironment.isRunningApp else {
            return
        }

        #if !DEBUG
            if AppMover.moveIfNecessary() {
                return
            }
        #endif

        guard AccessibilityPermission.enabled else {
            AccessibilityPermissionWindow.shared.bringToFront()
            return
        }

        setup()

        if CommandLine.arguments.contains("--show") {
            SettingsWindowController.shared.bringToFront()
        }
    }

    func applicationShouldHandleReopen(_: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
        guard ProcessEnvironment.isRunningApp else {
            return true
        }

        if flag {
            return true
        }

        SettingsWindowController.shared.bringToFront()

        return false
    }

    func applicationWillTerminate(_: Notification) {
        guard ProcessEnvironment.isRunningApp else {
            return
        }

        stop()
    }
}

extension AppDelegate {
    func setup() {
        setupConfiguration()
        setupNotifications()
        KeyboardSettingsSnapshot.shared.refresh()
        start()
    }

    func setupConfiguration() {
        ConfigurationState.shared.load()
        // Start watching the configuration file for hot reload
        ConfigurationState.shared.startHotReload()
    }

    func setupNotifications() {
        // Prepare user notifications for error popups
        Notifier.shared.setup()
        NSWorkspace.shared.notificationCenter.addObserver(
            forName: NSWorkspace.sessionDidResignActiveNotification,
            object: nil,
            queue: .main
        ) { [weak self] _ in
            os_log("Session inactive", log: Self.log, type: .info)
            self?.stop()
        }

        NSWorkspace.shared.notificationCenter.addObserver(
            forName: NSWorkspace.sessionDidBecomeActiveNotification,
            object: nil,
            queue: .main
        ) { [weak self] _ in
            os_log("Session active", log: Self.log, type: .info)
            KeyboardSettingsSnapshot.shared.refresh()
            self?.start()
        }
    }

    func start() {
        DeviceManager.shared.start()
        BatteryDeviceMonitor.shared.start()
        GlobalEventTap.shared.start()
    }

    func stop() {
        BatteryDeviceMonitor.shared.stop()
        DeviceManager.shared.stop()
        GlobalEventTap.shared.stop()
    }
}


================================================
FILE: LinearMouse/Assets.xcassets/AccentColor.colorset/Contents.json
================================================
{
  "colors" : [
    {
      "color" : {
        "color-space" : "srgb",
        "components" : {
          "alpha" : "1.000",
          "blue" : "0xF6",
          "green" : "0x82",
          "red" : "0x3A"
        }
      },
      "idiom" : "universal"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: LinearMouse/Assets.xcassets/AccessibilityIcon.imageset/Contents.json
================================================
{
  "images" : [
    {
      "idiom" : "universal",
      "scale" : "1x"
    },
    {
      "filename" : "UniversalAccessPref@2x.png",
      "idiom" : "universal",
      "scale" : "2x"
    },
    {
      "idiom" : "universal",
      "scale" : "3x"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: LinearMouse/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
  "images" : [
    {
      "idiom" : "mac",
      "scale" : "1x",
      "size" : "16x16"
    },
    {
      "filename" : "Icon-32.png",
      "idiom" : "mac",
      "scale" : "2x",
      "size" : "16x16"
    },
    {
      "filename" : "Icon-33.png",
      "idiom" : "mac",
      "scale" : "1x",
      "size" : "32x32"
    },
    {
      "filename" : "Icon-64.png",
      "idiom" : "mac",
      "scale" : "2x",
      "size" : "32x32"
    },
    {
      "filename" : "Icon-128.png",
      "idiom" : "mac",
      "scale" : "1x",
      "size" : "128x128"
    },
    {
      "filename" : "Icon-256.png",
      "idiom" : "mac",
      "scale" : "2x",
      "size" : "128x128"
    },
    {
      "filename" : "Icon-257.png",
      "idiom" : "mac",
      "scale" : "1x",
      "size" : "256x256"
    },
    {
      "filename" : "Icon-513.png",
      "idiom" : "mac",
      "scale" : "2x",
      "size" : "256x256"
    },
    {
      "filename" : "Icon-512.png",
      "idiom" : "mac",
      "scale" : "1x",
      "size" : "512x512"
    },
    {
      "filename" : "Icon-1024.png",
      "idiom" : "mac",
      "scale" : "2x",
      "size" : "512x512"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: LinearMouse/Assets.xcassets/AppIconDev.appiconset/Contents.json
================================================
{
  "images" : [
    {
      "idiom" : "mac",
      "scale" : "1x",
      "size" : "16x16"
    },
    {
      "idiom" : "mac",
      "scale" : "2x",
      "size" : "16x16"
    },
    {
      "idiom" : "mac",
      "scale" : "1x",
      "size" : "32x32"
    },
    {
      "idiom" : "mac",
      "scale" : "2x",
      "size" : "32x32"
    },
    {
      "idiom" : "mac",
      "scale" : "1x",
      "size" : "128x128"
    },
    {
      "idiom" : "mac",
      "scale" : "2x",
      "size" : "128x128"
    },
    {
      "idiom" : "mac",
      "scale" : "1x",
      "size" : "256x256"
    },
    {
      "filename" : "Icon-512.png",
      "idiom" : "mac",
      "scale" : "2x",
      "size" : "256x256"
    },
    {
      "idiom" : "mac",
      "scale" : "1x",
      "size" : "512x512"
    },
    {
      "idiom" : "mac",
      "scale" : "2x",
      "size" : "512x512"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: LinearMouse/Assets.xcassets/Contents.json
================================================
{
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: LinearMouse/Assets.xcassets/MenuIcon.imageset/Contents.json
================================================
{
  "images" : [
    {
      "filename" : "MenuIcon.png",
      "idiom" : "universal",
      "scale" : "1x"
    },
    {
      "filename" : "MenuIcon@2x.png",
      "idiom" : "universal",
      "scale" : "2x"
    },
    {
      "filename" : "MenuIcon@3x.png",
      "idiom" : "universal",
      "scale" : "3x"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  },
  "properties" : {
    "template-rendering-intent" : "template"
  }
}


================================================
FILE: LinearMouse/Assets.xcassets/Minus.imageset/Contents.json
================================================
{
  "images" : [
    {
      "filename" : "minus.svg",
      "idiom" : "universal",
      "scale" : "1x"
    },
    {
      "idiom" : "universal",
      "scale" : "2x"
    },
    {
      "idiom" : "universal",
      "scale" : "3x"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  },
  "properties" : {
    "template-rendering-intent" : "template"
  }
}


================================================
FILE: LinearMouse/Assets.xcassets/Plus.imageset/Contents.json
================================================
{
  "images" : [
    {
      "filename" : "plus.svg",
      "idiom" : "universal",
      "scale" : "1x"
    },
    {
      "idiom" : "universal",
      "scale" : "2x"
    },
    {
      "idiom" : "universal",
      "scale" : "3x"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  },
  "properties" : {
    "template-rendering-intent" : "template"
  }
}


================================================
FILE: LinearMouse/Assets.xcassets/Sidebar/Buttons.imageset/Contents.json
================================================
{
  "images" : [
    {
      "idiom" : "universal",
      "scale" : "1x"
    },
    {
      "filename" : "button.programmable@2x.png",
      "idiom" : "universal",
      "scale" : "2x"
    },
    {
      "idiom" : "universal",
      "scale" : "3x"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  },
  "properties" : {
    "template-rendering-intent" : "template"
  }
}


================================================
FILE: LinearMouse/Assets.xcassets/Sidebar/Contents.json
================================================
{
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}


================================================
FILE: LinearMouse/Assets.xcassets/Sidebar/General.imageset/Contents.json
================================================
{
  "images" : [
    {
      "idiom" : "universal",
      "scale" : "1x"
    },
    {
      "filename" : "gearshape.fill@2x.png",
      "idiom" : "universal",
      "scale" : "2x"
    },
    {
      "idiom" : "universal",
      "scale" : "3x"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  },
  "properties" : {
    "template-rendering-intent" : "template"
  }
}


================================================
FILE: LinearMouse/Assets.xcassets/Sidebar/Modifier Keys.imageset/Contents.json
================================================
{
  "images" : [
    {
      "idiom" : "universal",
      "scale" : "1x"
    },
    {
      "filename" : "command@2x.png",
      "idiom" : "universal",
      "scale" : "2x"
    },
    {
      "idiom" : "universal",
      "scale" : "3x"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  },
  "properties" : {
    "template-rendering-intent" : "template"
  }
}


================================================
FILE: LinearMouse/Assets.xcassets/Sidebar/Pointer.imageset/Contents.json
================================================
{
  "images" : [
    {
      "idiom" : "universal",
      "scale" : "1x"
    },
    {
      "filename" : "cursorarrow@2x.png",
      "idiom" : "universal",
      "scale" : "2x"
    },
    {
      "idiom" : "universal",
      "scale" : "3x"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  },
  "properties" : {
    "template-rendering-intent" : "template"
  }
}


================================================
FILE: LinearMouse/Assets.xcassets/Sidebar/Scrolling.imageset/Contents.json
================================================
{
  "images" : [
    {
      "idiom" : "universal",
      "scale" : "1x"
    },
    {
      "filename" : "arrow.up.and.down@2x.png",
      "idiom" : "universal",
      "scale" : "2x"
    },
    {
      "idiom" : "universal",
      "scale" : "3x"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  },
  "properties" : {
    "template-rendering-intent" : "template"
  }
}


================================================
FILE: LinearMouse/AutoUpdateManager.swift
================================================
// MIT License
// Copyright (c) 2021-2026 LinearMouse

import Defaults
import Foundation
import Sparkle
import Version

class AutoUpdateManager: NSObject {
    static let shared = AutoUpdateManager()

    private var _controller: SPUStandardUpdaterController!
    var controller: SPUStandardUpdaterController {
        _controller
    }

    override init() {
        super.init()
        _controller = SPUStandardUpdaterController(
            startingUpdater: true,
            updaterDelegate: self,
            userDriverDelegate: nil
        )
    }
}

extension AutoUpdateManager: SPUUpdaterDelegate {
    func allowedChannels(for _: SPUUpdater) -> Set<String> {
        Defaults[.betaChannelOn] ? ["beta"] : []
    }

    func versionComparator(for _: SPUUpdater) -> SUVersionComparison? {
        SemanticVersioningComparator()
    }
}

class SemanticVersioningComparator: SUVersionComparison {
    func compareVersion(_ versionA: String, toVersion versionB: String) -> ComparisonResult {
        guard let a = try? Version(versionA) else {
            return .orderedAscending
        }
        guard let b = try? Version(versionB) else {
            return .orderedDescending
        }
        if a < b {
            return .orderedAscending
        }
        if a > b {
            return .orderedDescending
        }
        return .orderedSame
    }
}


================================================
FILE: LinearMouse/DefaultsKeys.swift
================================================
// MIT License
// Copyright (c) 2021-2026 LinearMouse

import Defaults

enum MenuBarBatteryDisplayMode: String, Codable, Defaults.Serializable {
    case off
    case below5
    case below10
    case below15
    case below20
    case always

    var threshold: Int? {
        switch self {
        case .off:
            return nil
        case .below5:
            return 5
        case .below10:
            return 10
        case .below15:
            return 15
        case .below20:
            return 20
        case .always:
            return 100
        }
    }
}

extension Defaults.Keys {
    static let showInMenuBar = Key<Bool>("showInMenuBar", default: true)
    static let menuBarBatteryDisplayMode = Key<MenuBarBatteryDisplayMode>("menuBarBatteryDisplayMode", default: .off)

    static let showInDock = Key<Bool>("showInDock", default: true)

    static let betaChannelOn = Key("betaChannelOn", default: false)

    static let bypassEventsFromOtherApplications = Key("bypassEventsFromOtherApplications", default: false)

    static let verbosedLoggingOn = Key("verbosedLoggingOn", default: false)
}


================================================
FILE: LinearMouse/Device/Device.swift
================================================
// MIT License
// Copyright (c) 2021-2026 LinearMouse

import Defaults
import Foundation
import ObservationToken
import os.log
import PointerKit

class Device {
    private static let log = OSLog(
        subsystem: Bundle.main.bundleIdentifier!, category: "Device"
    )

    static let fallbackPointerAcceleration = 0.6875
    static let fallbackPointerResolution = 400.0
    static let fallbackPointerSpeed = pointerSpeed(
        fromPointerResolution: fallbackPointerResolution
    )

    private static var nextID: Int32 = 0

    private(set) lazy var id: Int32 = OSAtomicIncrement32(&Self.nextID)

    var name: String
    var productName: String?
    var vendorID: Int?
    var productID: Int?
    var serialNumber: String?
    var buttonCount: Int?
    var batteryLevel: Int?
    private let categoryValue: Category

    private weak var manager: DeviceManager?
    private var inputReportHandlers: [InputReportHandler] = []
    private var logitechReprogrammableControlsMonitor: LogitechReprogrammableControlsMonitor?
    private let device: PointerDevice

    var pointerDevice: PointerDevice {
        device
    }

    private var removed = false

    private var verbosedLoggingOn = Defaults[.verbosedLoggingOn]

    private let initialPointerResolution: Double

    private var inputObservationToken: ObservationToken?
    private var reportObservationToken: ObservationToken?

    private var lastButtonStates: UInt8 = 0

    var category: Category {
        categoryValue
    }

    init(_ manager: DeviceManager, _ device: PointerDevice) {
        self.manager = manager
        self.device = device

        vendorID = device.vendorID
        productID = device.productID
        serialNumber = device.serialNumber
        buttonCount = device.buttonCount

        let rawProductName = device.product
        let rawName = rawProductName ?? device.name
        name = rawName
        productName = rawProductName
        batteryLevel = nil
        categoryValue = Self.detectCategory(for: device)

        initialPointerResolution =
            device.pointerResolution ?? Self.fallbackPointerResolution

        // TODO: More elegant way?
        inputObservationToken = device.observeInput { [weak self] in
            self?.inputValueCallback($0, $1)
        }

        // Some bluetooth devices, such as Mi Dual Mode Wireless Mouse Silent Edition, report only
        // 3 buttons in the HID report descriptor. As a result, macOS does not recognize side button
        // clicks from these devices.
        //
        // To work around this issue, we subscribe to the input reports and monitor the side button
        // states. When the side buttons are clicked, we simulate those events.
        if let vendorID, let productID {
            let handlers = InputReportHandlerRegistry.handlers(for: vendorID, productID: productID)
            let needsObservation = handlers.contains { $0.alwaysNeedsReportObservation() } || buttonCount == 3
            if needsObservation, !handlers.isEmpty {
                inputReportHandlers = handlers
                reportObservationToken = device.observeReport { [weak self] in
                    self?.inputReportCallback($0, $1)
                }
            }
        }

        if LogitechReprogrammableControlsMonitor.supports(device: self) {
            let monitor = LogitechReprogrammableControlsMonitor(device: self)
            logitechReprogrammableControlsMonitor = monitor
            monitor.start()
        }

        os_log(
            "Device initialized: %{public}@: HIDPointerResolution=%{public}f, HIDPointerAccelerationType=%{public}@, battery=%{public}@",
            log: Self.log,
            type: .info,
            String(describing: device),
            initialPointerResolution,
            device.pointerAccelerationType ?? "(unknown)",
            batteryLevel.map(formattedPercent) ?? "(unknown)"
        )

        Defaults.observe(.verbosedLoggingOn) { [weak self] change in
            guard let self else {
                return
            }

            verbosedLoggingOn = change.newValue
        }
        .tieToLifetime(of: self)
    }

    func markRemoved() {
        removed = true

        inputObservationToken = nil
        reportObservationToken = nil
        logitechReprogrammableControlsMonitor?.stop()
        logitechReprogrammableControlsMonitor = nil
    }

    func markActive(reason: String) {
        manager?.markDeviceActive(self, reason: reason)
    }

    var hasLogitechControlsMonitor: Bool {
        logitechReprogrammableControlsMonitor != nil
    }

    func requestLogitechControlsForcedReconfiguration() {
        logitechReprogrammableControlsMonitor?.requestForcedReconfiguration()
    }
}

extension Device {
    enum Category {
        case mouse, trackpad
    }

    private static let appleVendorIDs = Set([0x004C, 0x05AC])
    private static let appleMagicMouseProductIDs = Set([0x0269, 0x030D])
    private static let appleMagicTrackpadProductIDs = Set([0x0265, 0x030E])
    private static let appleBuiltInTrackpadProductIDs = Set([0x0273, 0x0276, 0x0278, 0x0340])

    private static func detectCategory(for device: PointerDevice) -> Category {
        if let vendorID = device.vendorID,
           let productID = device.productID,
           isAppleMagicMouse(vendorID: vendorID, productID: productID) {
            return .mouse
        }

        if device.confirmsTo(kHIDPage_Digitizer, kHIDUsage_Dig_TouchPad) {
            return .trackpad
        }

        return .mouse
    }

    private static func isAppleMagicMouse(vendorID: Int, productID: Int) -> Bool {
        appleVendorIDs.contains(vendorID)
            && appleMagicMouseProductIDs.contains(productID)
    }

    private static func isAppleMagicTrackpad(vendorID: Int, productID: Int) -> Bool {
        appleVendorIDs.contains(vendorID)
            && appleMagicTrackpadProductIDs.contains(productID)
    }

    private static func isAppleBuiltInTrackpad(vendorID: Int, productID: Int) -> Bool {
        vendorID == 0x05AC
            && appleBuiltInTrackpadProductIDs.contains(productID)
    }

    var showsPointerSpeedLimitationNotice: Bool {
        guard let vendorID, let productID else {
            return false
        }

        return Self.isAppleMagicMouse(vendorID: vendorID, productID: productID)
            || Self.isAppleMagicTrackpad(vendorID: vendorID, productID: productID)
            || Self.isAppleBuiltInTrackpad(vendorID: vendorID, productID: productID)
    }

    /**
     This feature was introduced in macOS Sonoma. In the earlier versions of
     macOS, this value would be nil.
     */
    var disablePointerAcceleration: Bool? {
        get {
            device.useLinearScalingMouseAcceleration.map { $0 != 0 }
        }
        set {
            guard device.useLinearScalingMouseAcceleration != nil, let newValue else {
                return
            }
            device.useLinearScalingMouseAcceleration = newValue ? 1 : 0
        }
    }

    var pointerAcceleration: Double {
        get {
            device.pointerAcceleration ?? Self.fallbackPointerAcceleration
        }
        set {
            os_log(
                "Update pointer acceleration for device: %{public}@: %{public}f",
                log: Self.log,
                type: .info,
                String(describing: self),
                newValue
            )
            device.pointerAcceleration = newValue
        }
    }

    private static let pointerSpeedRange = 1.0 / 1200 ... 1.0 / 40

    static func pointerSpeed(fromPointerResolution pointerResolution: Double)
        -> Double {
        (1 / pointerResolution).normalized(from: pointerSpeedRange)
    }

    static func pointerResolution(fromPointerSpeed pointerSpeed: Double)
        -> Double {
        1 / (pointerSpeed.normalized(to: pointerSpeedRange))
    }

    var pointerSpeed: Double {
        get {
            device.pointerResolution.map {
                Self.pointerSpeed(fromPointerResolution: $0)
            }
                ?? Self
                .fallbackPointerSpeed
        }
        set {
            os_log(
                "Update pointer speed for device: %{public}@: %{public}f",
                log: Self.log,
                type: .info,
                String(describing: self),
                newValue
            )
            device.pointerResolution = Self.pointerResolution(fromPointerSpeed: newValue)
        }
    }

    func restorePointerAcceleration() {
        let systemPointerAcceleration = (DeviceManager.shared
            .getSystemProperty(forKey: device.pointerAccelerationType ?? kIOHIDMouseAccelerationTypeKey) as IOFixed?
        )
        .map { Double($0) / 65_536 } ?? Self.fallbackPointerAcceleration

        os_log(
            "Restore pointer acceleration for device: %{public}@: %{public}f",
            log: Self.log,
            type: .info,
            String(describing: device),
            systemPointerAcceleration
        )

        pointerAcceleration = systemPointerAcceleration
    }

    func restorePointerSpeed() {
        os_log(
            "Restore pointer speed for device: %{public}@: %{public}f",
            log: Self.log,
            type: .info,
            String(describing: device),
            Self.pointerSpeed(fromPointerResolution: initialPointerResolution)
        )

        device.pointerResolution = initialPointerResolution
    }

    func restorePointerAccelerationAndPointerSpeed() {
        restorePointerSpeed()
        restorePointerAcceleration()
    }

    private func inputValueCallback(
        _ device: PointerDevice, _ value: IOHIDValue
    ) {
        if verbosedLoggingOn {
            os_log(
                "Received input value from: %{public}@: %{public}@",
                log: Self.log,
                type: .info,
                String(describing: device),
                String(describing: value)
            )
        }

        guard let manager else {
            os_log("manager is nil", log: Self.log, type: .error)
            return
        }

        guard manager.lastActiveDeviceId != id else {
            return
        }

        let element = value.element

        let usagePage = element.usagePage
        let usage = element.usage

        switch Int(usagePage) {
        case kHIDPage_GenericDesktop:
            switch Int(usage) {
            case kHIDUsage_GD_X, kHIDUsage_GD_Y, kHIDUsage_GD_Z:
                guard IOHIDValueGetIntegerValue(value) != 0 else {
                    return
                }
            default:
                return
            }
        case kHIDPage_Button:
            break
        default:
            return
        }

        manager.markDeviceActive(
            self,
            reason: "Received input value: usagePage=0x\(String(format: "%02X", usagePage)), usage=0x\(String(format: "%02X", usage))"
        )
    }

    private func inputReportCallback(_ device: PointerDevice, _ report: Data) {
        if verbosedLoggingOn {
            let reportHex = report.map { String(format: "%02X", $0) }.joined(separator: " ")
            os_log(
                "Received input report from: %{public}@: %{public}@",
                log: Self.log,
                type: .info,
                String(describing: device),
                String(describing: reportHex)
            )
        }

        let context = InputReportContext(report: report, lastButtonStates: lastButtonStates)
        let chain = inputReportHandlers.reversed().reduce({ (_: InputReportContext) in }) { next, handler in
            { context in handler.handleReport(context, next: next) }
        }
        chain(context)
        lastButtonStates = context.lastButtonStates
    }
}

extension Device: Hashable {
    static func == (lhs: Device, rhs: Device) -> Bool {
        lhs === rhs
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(ObjectIdentifier(self))
    }
}

extension Device: CustomStringConvertible {
    var description: String {
        let vendorIDString = vendorID.map { String(format: "0x%04X", $0) } ?? "(nil)"
        let productIDString = productID.map { String(format: "0x%04X", $0) } ?? "(nil)"
        return String(format: "%@ (VID=%@, PID=%@)", name, vendorIDString, productIDString)
    }
}


================================================
FILE: LinearMouse/Device/DeviceManager.swift
================================================
// MIT License
// Copyright (c) 2021-2026 LinearMouse

import AppKit
import Combine
import Foundation
import os.log
import PointerKit

class DeviceManager: ObservableObject {
    static let shared = DeviceManager()

    private static let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "DeviceManager")

    private let manager = PointerDeviceManager()
    private let receiverMonitor = ReceiverMonitor()

    private var pointerDeviceToDevice = [PointerDevice: Device]()
    @Published private(set) var receiverPairedDeviceIdentities = [Int: [ReceiverLogicalDeviceIdentity]]()
    @Published var devices: [Device] = []

    var lastActiveDeviceId: Int32?
    @Published var lastActiveDeviceRef: WeakRef<Device>?

    init() {
        manager.observeDeviceAdded { [weak self] in
            self?.deviceAdded($0, $1)
        }
        .tieToLifetime(of: self)

        manager.observeDeviceRemoved { [weak self] in
            self?.deviceRemoved($0, $1)
        }
        .tieToLifetime(of: self)

        manager.observeEventReceived { [weak self] in
            self?.eventReceived($0, $1, $2)
        }
        .tieToLifetime(of: self)

        receiverMonitor.onPointingDevicesChanged = { [weak self] locationID, identities in
            self?.receiverPointingDevicesChanged(locationID: locationID, identities: identities)
        }

        for property in [
            kIOHIDMouseAccelerationType,
            kIOHIDTrackpadAccelerationType,
            kIOHIDPointerResolutionKey,
            "HIDUseLinearScalingMouseAcceleration"
        ] {
            manager.observePropertyChanged(property: property) { [self] _ in
                os_log("Property %{public}@ changed", log: Self.log, type: .info, property)
                updatePointerSpeed()
            }.tieToLifetime(of: self)
        }
    }

    deinit {
        stop()
    }

    private enum State {
        case stopped, running
    }

    private var state: State = .stopped

    private var subscriptions = Set<AnyCancellable>()

    private var activateApplicationObserver: Any?

    func stop() {
        guard state == .running else {
            return
        }
        state = .stopped

        restorePointerSpeedToInitialValue()
        manager.stopObservation()
        subscriptions.removeAll()

        if let activateApplicationObserver {
            NSWorkspace.shared.notificationCenter.removeObserver(activateApplicationObserver)
        }
    }

    func start() {
        guard state == .stopped else {
            return
        }
        state = .running

        manager.startObservation()

        ConfigurationState.shared
            .$configuration
            .debounce(for: 0.1, scheduler: RunLoop.main)
            .sink { [weak self] _ in
                guard let self else {
                    return
                }
                DispatchQueue.main.async {
                    self.updatePointerSpeed()
                }
            }
            .store(in: &subscriptions)

        ScreenManager.shared
            .$currentScreenName
            .sink { [weak self] _ in
                guard let self else {
                    return
                }
                DispatchQueue.main.async {
                    self.updatePointerSpeed()
                }
            }
            .store(in: &subscriptions)

        activateApplicationObserver = NSWorkspace.shared.notificationCenter.addObserver(
            forName: NSWorkspace.didActivateApplicationNotification,
            object: nil,
            queue: .main
        ) { [weak self] notification in
            let application = notification.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication
            os_log(
                "Frontmost app changed: %{public}@",
                log: Self.log,
                type: .info,
                application?.bundleIdentifier ?? "(nil)"
            )
            self?.updatePointerSpeed()
        }
    }

    private func deviceAdded(_: PointerDeviceManager, _ pointerDevice: PointerDevice) {
        let device = Device(self, pointerDevice)

        objectWillChange.send()

        pointerDeviceToDevice[pointerDevice] = device
        refreshVisibleDevices()

        os_log(
            "Device added: %{public}@",
            log: Self.log,
            type: .info,
            String(describing: device)
        )

        updatePointerSpeed(for: device)

        if shouldMonitorReceiver(device) {
            receiverMonitor.startMonitoring(device: device)
        }
    }

    private func deviceRemoved(_: PointerDeviceManager, _ pointerDevice: PointerDevice) {
        guard let device = pointerDeviceToDevice[pointerDevice] else {
            return
        }
        device.markRemoved()

        objectWillChange.send()

        if lastActiveDeviceId == device.id {
            lastActiveDeviceId = nil
            lastActiveDeviceRef = nil
        }

        if let locationID = pointerDevice.locationID {
            let hasRemainingReceiverAtLocation = pointerDeviceToDevice
                .filter { $0.key != pointerDevice }
                .contains { _, existingDevice in
                    existingDevice.pointerDevice.locationID == locationID && shouldMonitorReceiver(existingDevice)
                }

            if hasRemainingReceiverAtLocation {
                os_log(
                    "Keep receiver monitor running because another receiver device shares locationID=%{public}d",
                    log: Self.log,
                    type: .info,
                    locationID
                )
            } else {
                receiverMonitor.stopMonitoring(device: device)
                receiverPairedDeviceIdentities.removeValue(forKey: locationID)
            }
        }

        pointerDeviceToDevice.removeValue(forKey: pointerDevice)
        refreshVisibleDevices()

        os_log(
            "Device removed: %{public}@",
            log: Self.log,
            type: .info,
            String(describing: device)
        )
    }

    /// Observes events from `DeviceManager`.
    ///
    /// It seems that extenal Trackpads do not trigger to `IOHIDDevice`'s inputValueCallback.
    /// That's why we need to observe events from `DeviceManager` too.
    private func eventReceived(_: PointerDeviceManager, _ pointerDevice: PointerDevice, _ event: IOHIDEvent) {
        guard let physicalDevice = pointerDeviceToDevice[pointerDevice] else {
            return
        }

        guard IOHIDEventGetType(event) == kIOHIDEventTypeScroll else {
            return
        }

        let scrollX = IOHIDEventGetFloatValue(event, kIOHIDEventFieldScrollX)
        let scrollY = IOHIDEventGetFloatValue(event, kIOHIDEventFieldScrollY)
        guard scrollX != 0 || scrollY != 0 else {
            return
        }

        markDeviceActive(physicalDevice, reason: "Received event from DeviceManager")
    }

    func deviceFromCGEvent(_ cgEvent: CGEvent) -> Device? {
        // Issue: https://github.com/linearmouse/linearmouse/issues/677#issuecomment-1938208542
        guard ![.flagsChanged, .keyDown, .keyUp].contains(cgEvent.type) else {
            return lastActiveDeviceRef?.value
        }

        guard let ioHIDEvent = CGEventCopyIOHIDEvent(cgEvent) else {
            return lastActiveDeviceRef?.value
        }

        guard let pointerDevice = manager.pointerDeviceFromIOHIDEvent(ioHIDEvent) else {
            return lastActiveDeviceRef?.value
        }

        guard let physicalDevice = pointerDeviceToDevice[pointerDevice] else {
            return lastActiveDeviceRef?.value
        }

        return physicalDevice
    }

    func updatePointerSpeed() {
        for device in devices {
            updatePointerSpeed(for: device)
        }
    }

    func updatePointerSpeed(for device: Device) {
        let scheme = ConfigurationState.shared.configuration.matchScheme(
            withDevice: device,
            withPid: NSWorkspace.shared.frontmostApplication?.processIdentifier,
            withDisplay: ScreenManager.shared
                .currentScreenName
        )

        if let pointerDisableAcceleration = scheme.pointer.disableAcceleration, pointerDisableAcceleration {
            // If the pointer acceleration is turned off, it is preferable to utilize
            // the new API introduced by macOS Sonoma.
            // Otherwise, set pointer acceleration to -1.
            if device.disablePointerAcceleration != nil {
                device.disablePointerAcceleration = true

                // This might be a bit confusing because of the historical naming
                // convention, but here, the pointerAcceleration actually refers to
                // the tracking speed.
                if let pointerAcceleration = scheme.pointer.acceleration {
                    switch pointerAcceleration {
                    case let .value(v):
                        device.pointerAcceleration = v.asTruncatedDouble
                    case .unset:
                        device.restorePointerAcceleration()
                    }
                } else {
                    device.restorePointerAcceleration()
                }
            } else {
                device.pointerAcceleration = -1
            }

            return
        }

        if device.disablePointerAcceleration != nil {
            device.disablePointerAcceleration = false
        }

        if let pointerSpeed = scheme.pointer.speed {
            switch pointerSpeed {
            case let .value(v):
                device.pointerSpeed = v.asTruncatedDouble
            case .unset:
                device.restorePointerSpeed()
            }
        } else {
            device.restorePointerSpeed()
        }

        if let pointerAcceleration = scheme.pointer.acceleration {
            switch pointerAcceleration {
            case let .value(v):
                device.pointerAcceleration = v.asTruncatedDouble
            case .unset:
                device.restorePointerAcceleration()
            }
        } else {
            device.restorePointerAcceleration()
        }
    }

    func restorePointerSpeedToInitialValue() {
        for device in devices {
            device.restorePointerAccelerationAndPointerSpeed()
        }
    }

    func getSystemProperty<T>(forKey key: String) -> T? {
        let service = IORegistryEntryFromPath(kIOMasterPortDefault, "\(kIOServicePlane):/IOResources/IOHIDSystem")
        guard service != .zero else {
            return nil
        }
        defer { IOObjectRelease(service) }

        var handle: io_connect_t = .zero
        guard IOServiceOpen(service, mach_task_self_, UInt32(kIOHIDParamConnectType), &handle) == KERN_SUCCESS else {
            return nil
        }
        defer { IOServiceClose(handle) }

        var valueRef: Unmanaged<CFTypeRef>?
        guard IOHIDCopyCFTypeParameter(handle, key as CFString, &valueRef) == KERN_SUCCESS else {
            return nil
        }
        guard let valueRefUnwrapped = valueRef else {
            return nil
        }
        guard let value = valueRefUnwrapped.takeRetainedValue() as? T else {
            return nil
        }
        return value
    }

    func markDeviceActive(_ device: Device, reason: String) {
        guard lastActiveDeviceId != device.id else {
            return
        }

        lastActiveDeviceId = device.id
        lastActiveDeviceRef = .init(device)

        os_log(
            "Last active device changed: %{public}@, category=%{public}@ (Reason: %{public}@)",
            log: Self.log,
            type: .info,
            String(describing: device),
            String(describing: device.category),
            reason
        )

        updatePointerSpeed()
    }

    func pairedReceiverDevices(for device: Device) -> [ReceiverLogicalDeviceIdentity] {
        guard shouldMonitorReceiver(device),
              let locationID = device.pointerDevice.locationID
        else {
            return []
        }

        let identities = receiverPairedDeviceIdentities[locationID] ?? []
        os_log(
            "Receiver paired device lookup: locationID=%{public}d device=%{public}@ count=%{public}u",
            log: Self.log,
            type: .info,
            locationID,
            String(describing: device),
            UInt32(identities.count)
        )
        return identities
    }

    func preferredName(for device: Device, fallback: String? = nil) -> String {
        fallback ?? device.name
    }

    func displayName(for device: Device, fallbackBaseName: String? = nil) -> String {
        Self.displayName(
            baseName: preferredName(for: device, fallback: fallbackBaseName),
            pairedDevices: pairedReceiverDevices(for: device)
        )
    }

    private func shouldMonitorReceiver(_ device: Device) -> Bool {
        guard let vendorID = device.vendorID,
              vendorID == LogitechHIDPPDeviceMetadataProvider.Constants.vendorID,
              device.pointerDevice.transport == PointerDeviceTransportName.usb
        else {
            return false
        }

        let productName = device.productName ?? device.name
        return productName.localizedCaseInsensitiveContains("receiver")
    }

    private func receiverPointingDevicesChanged(locationID: Int, identities: [ReceiverLogicalDeviceIdentity]) {
        guard pointerDeviceToDevice.values.contains(where: { $0.pointerDevice.locationID == locationID }) else {
            os_log(
                "Drop receiver logical device update because no visible device matches locationID=%{public}d count=%{public}u",
                log: Self.log,
                type: .info,
                locationID,
                UInt32(identities.count)
            )
            return
        }

        let previousIdentities = receiverPairedDeviceIdentities[locationID] ?? []
        receiverPairedDeviceIdentities[locationID] = identities

        let identitiesDescription = identities.map { identity in
            let battery = identity.batteryLevel.map(String.init) ?? "(nil)"
            return "slot=\(identity.slot) name=\(identity.name) battery=\(battery)"
        }
        .joined(separator: ", ")

        os_log(
            "Receiver logical devices updated for locationID=%{public}d: %{public}@",
            log: Self.log,
            type: .info,
            locationID,
            identitiesDescription
        )

        // Only trigger forced reconfiguration when a device has actually reconnected
        // (a slot appeared that wasn't in the previous identity set), since device
        // firmware resets diversion state on reconnect.
        let previousSlots = Set(previousIdentities.map(\.slot))
        let hasReconnectedDevice = identities.contains { !previousSlots.contains($0.slot) }
        if hasReconnectedDevice {
            for (_, device) in pointerDeviceToDevice where device.pointerDevice.locationID == locationID {
                device.requestLogitechControlsForcedReconfiguration()
            }
        }
    }

    private func refreshVisibleDevices() {
        devices = pointerDeviceToDevice.values.sorted { $0.id < $1.id }
    }

    static func displayName(baseName: String, pairedDevices: [ReceiverLogicalDeviceIdentity]) -> String {
        guard !pairedDevices.isEmpty else {
            return baseName
        }

        if pairedDevices.count == 1, let pairedName = pairedDevices.first?.name {
            return "\(baseName) (\(pairedName))"
        }

        return String(
            format: NSLocalizedString("%@ (%lld devices)", comment: ""),
            baseName,
            Int64(pairedDevices.count)
        )
    }
}


================================================
FILE: LinearMouse/Device/InputReportHandler.swift
================================================
// MIT License
// Copyright (c) 2021-2026 LinearMouse

import CoreGraphics
import Foundation

typealias MouseButtonEmitter = (_ button: Int, _ down: Bool) -> Void

enum SyntheticMouseButtonEventEmitter {
    static func post(button: Int, down: Bool) {
        guard let location = CGEvent(source: nil)?.location,
              let mouseButton = CGMouseButton(rawValue: UInt32(button)),
              let event = CGEvent(
                  mouseEventSource: nil,
                  mouseType: down ? .otherMouseDown : .otherMouseUp,
                  mouseCursorPosition: location,
                  mouseButton: mouseButton
              ) else {
            return
        }

        event.flags = ModifierState.normalize(ModifierState.shared.currentFlags)
        event.setIntegerValueField(.mouseEventButtonNumber, value: Int64(button))
        event.isLinearMouseSyntheticEvent = true
        event.post(tap: .cghidEventTap)
    }
}

/// Context passed through the handler chain
class InputReportContext {
    let report: Data
    var lastButtonStates: UInt8

    init(report: Data, lastButtonStates: UInt8) {
        self.report = report
        self.lastButtonStates = lastButtonStates
    }
}

protocol InputReportHandler {
    /// Check if this handler should be used for the given device
    func matches(vendorID: Int, productID: Int) -> Bool

    /// Whether report observation is needed regardless of button count
    /// Most devices only need observation when buttonCount == 3
    func alwaysNeedsReportObservation() -> Bool

    /// Handle input report and simulate button events as needed
    /// Call `next(context)` to pass control to the next handler in the chain
    func handleReport(_ context: InputReportContext, next: (InputReportContext) -> Void)
}

extension InputReportHandler {
    func alwaysNeedsReportObservation() -> Bool {
        false
    }
}

enum InputReportHandlerRegistry {
    static let handlers: [InputReportHandler] = [
        GenericSideButtonHandler(),
        KensingtonSlimbladeHandler()
    ]

    static func handlers(for vendorID: Int, productID: Int) -> [InputReportHandler] {
        handlers.filter { $0.matches(vendorID: vendorID, productID: productID) }
    }
}


================================================
FILE: LinearMouse/Device/InputReportHandlers/GenericSideButtonHandler.swift
================================================
// MIT License
// Copyright (c) 2021-2026 LinearMouse

import Foundation

/// Handles side button fixes for devices that report only 3 buttons in HID descriptor
/// but actually have side buttons (button 3 and 4).
///
/// Supported devices:
/// - Mi Dual Mode Wireless Mouse Silent Edition (0x2717:0x5014)
/// - Delux M729DB mouse (0x248A:0x8266)
struct GenericSideButtonHandler: InputReportHandler {
    private struct Product: Hashable {
        let vendorID: Int
        let productID: Int
    }

    private static let supportedProducts: Set<Product> = [
        .init(vendorID: 0x2717, productID: 0x5014), // Mi Silent Mouse
        .init(vendorID: 0x248A, productID: 0x8266) // Delux M729DB mouse
    ]

    private let emit: MouseButtonEmitter

    init(emit: @escaping MouseButtonEmitter = SyntheticMouseButtonEventEmitter.post) {
        self.emit = emit
    }

    func matches(vendorID: Int, productID: Int) -> Bool {
        Self.supportedProducts.contains(.init(vendorID: vendorID, productID: productID))
    }

    func handleReport(_ context: InputReportContext, next: (InputReportContext) -> Void) {
        defer { next(context) }

        guard context.report.count >= 2 else {
            return
        }

        // Report format: | Button 0 (1 bit) | ... | Button 4 (1 bit) | Not Used (3 bits) |
        // We only care about bits 3 and 4 (side buttons)
        let buttonStates = context.report[1] & 0x18
        let toggled = context.lastButtonStates ^ buttonStates

        guard toggled != 0 else {
            return
        }

        for button in 3 ... 4 {
            guard toggled & (1 << button) != 0 else {
                continue
            }
            let down = buttonStates & (1 << button) != 0
            emit(button, down)
        }

        context.lastButtonStates = buttonStates
    }
}


================================================
FILE: LinearMouse/Device/InputReportHandlers/KensingtonSlimbladeHandler.swift
================================================
// MIT License
// Copyright (c) 2021-2026 LinearMouse

import Foundation

/// Handles Kensington Slimblade trackball's top buttons.
///
/// The Slimblade has vendor-defined buttons that are reported in a different
/// format than standard HID buttons. This handler parses byte 4 of the input
/// report to detect top-left and top-right button presses.
///
/// Supported devices:
/// - Kensington Slimblade (0x047D:0x2041)
struct KensingtonSlimbladeHandler: InputReportHandler {
    private static let vendorID = 0x047D
    private static let productID = 0x2041

    private static let topLeftMask: UInt8 = 0x1
    private static let topRightMask: UInt8 = 0x2

    private let emit: MouseButtonEmitter

    init(emit: @escaping MouseButtonEmitter = SyntheticMouseButtonEventEmitter.post) {
        self.emit = emit
    }

    func matches(vendorID: Int, productID: Int) -> Bool {
        vendorID == Self.vendorID && productID == Self.productID
    }

    func alwaysNeedsReportObservation() -> Bool {
        // Slimblade needs report monitoring regardless of button count
        true
    }

    func handleReport(_ context: InputReportContext, next: (InputReportContext) -> Void) {
        defer { next(context) }

        guard context.report.count >= 5 else {
            return
        }

        // For Slimblade, byte 4 contains the vendor-defined button states
        let buttonStates = context.report[4]
        let toggled = context.lastButtonStates ^ buttonStates

        guard toggled != 0 else {
            return
        }

        // Check top left button (maps to button 3)
        if toggled & Self.topLeftMask != 0 {
            let down = buttonStates & Self.topLeftMask != 0
            emit(3, down)
        }

        // Check top right button (maps to button 4)
        if toggled & Self.topRightMask != 0 {
            let down = buttonStates & Self.topRightMask != 0
            emit(4, down)
        }

        context.lastButtonStates = buttonStates
    }
}


================================================
FILE: LinearMouse/Device/ReceiverLogicalDeviceIdentity.swift
================================================
// MIT License
// Copyright (c) 2021-2026 LinearMouse

import Foundation

enum ReceiverLogicalDeviceKind: UInt8, Hashable {
    case keyboard = 0x01
    case mouse = 0x02
    case numpad = 0x03
    case trackball = 0x08
    case touchpad = 0x09

    var isPointingDevice: Bool {
        switch self {
        case .mouse, .trackball, .touchpad:
            return true
        case .keyboard, .numpad:
            return false
        }
    }
}

struct ReceiverLogicalDeviceIdentity: Hashable {
    let receiverLocationID: Int
    let slot: UInt8
    let kind: ReceiverLogicalDeviceKind
    let name: String
    let serialNumber: String?
    let productID: Int?
    let batteryLevel: Int?

    func isSameLogicalDevice(as other: Self) -> Bool {
        receiverLocationID == other.receiverLocationID && slot == other.slot
    }
}


================================================
FILE: LinearMouse/Device/ReceiverMonitor.swift
================================================
// MIT License
// Copyright (c) 2021-2026 LinearMouse

import Foundation
import os.log

final class ReceiverMonitor {
    static let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "ReceiverMonitor")
    static let initialDiscoveryTimeout: TimeInterval = 3
    static let refreshInterval: TimeInterval = 15

    private let provider = LogitechHIDPPDeviceMetadataProvider()
    private var contexts = [Int: ReceiverContext]()

    var onPointingDevicesChanged: ((Int, [ReceiverLogicalDeviceIdentity]) -> Void)?

    func startMonitoring(device: Device) {
        guard let locationID = device.pointerDevice.locationID else {
            return
        }

        guard contexts[locationID] == nil else {
            return
        }

        let context = ReceiverContext(device: device, locationID: locationID, provider: provider)
        context.onDiscoveryTimedOut = { [weak self, weak context] in
            guard let self,
                  let context,
                  self.contexts[locationID] === context
            else {
                return
            }

            self.onPointingDevicesChanged?(locationID, [])
        }
        context.onSlotsChanged = { [weak self, weak context] identities in
            guard let self,
                  let context,
                  self.contexts[locationID] === context
            else {
                return
            }

            self.onPointingDevicesChanged?(locationID, identities)
        }
        contexts[locationID] = context
        context.start()

        os_log("Started receiver monitor for %{public}@", log: Self.log, type: .info, String(describing: device))
    }

    func stopMonitoring(device: Device) {
        guard let locationID = device.pointerDevice.locationID,
              let context = contexts.removeValue(forKey: locationID)
        else {
            return
        }

        context.stop()
    }
}

struct ReceiverSlotStateStore {
    enum SlotPresenceState {
        case unknown
        case connected
        case disconnected
    }

    private var pairedIdentitiesBySlot = [UInt8: ReceiverLogicalDeviceIdentity]()
    private var slotPresenceBySlot = [UInt8: SlotPresenceState]()

    mutating func reset() {
        pairedIdentitiesBySlot = [:]
        slotPresenceBySlot = [:]
    }

    mutating func mergeDiscovery(_ discovery: LogitechHIDPPDeviceMetadataProvider.ReceiverPointingDeviceDiscovery) {
        let latestIdentitiesBySlot = Dictionary(uniqueKeysWithValues: discovery.identities.map {
            ($0.slot, $0)
        })
        let previousIdentitiesBySlot = pairedIdentitiesBySlot

        for slot in pairedIdentitiesBySlot.keys where latestIdentitiesBySlot[slot] == nil {
            pairedIdentitiesBySlot.removeValue(forKey: slot)
            slotPresenceBySlot.removeValue(forKey: slot)
        }

        for (slot, identity) in latestIdentitiesBySlot {
            pairedIdentitiesBySlot[slot] = identity
            if slotPresenceBySlot[slot] == nil {
                slotPresenceBySlot[slot] = .unknown
            }
        }

        mergeConnectionSnapshots(discovery.connectionSnapshots)

        for slot in discovery.liveReachableSlots {
            if slotPresenceBySlot[slot] != .connected {
                slotPresenceBySlot[slot] = .connected
            }
        }

        for (slot, identity) in latestIdentitiesBySlot where discovery.connectionSnapshots[slot] == nil {
            guard slotPresenceBySlot[slot] == .disconnected,
                  previousIdentitiesBySlot[slot]?.batteryLevel == nil,
                  identity.batteryLevel != nil,
                  !discovery.liveReachableSlots.contains(slot)
            else {
                continue
            }

            slotPresenceBySlot[slot] = .connected
        }
    }

    mutating func mergeConnectionSnapshots(
        _ newSnapshots: [UInt8: LogitechHIDPPDeviceMetadataProvider.ReceiverConnectionSnapshot]
    ) {
        for (slot, snapshot) in newSnapshots {
            let newPresence: SlotPresenceState = snapshot.isConnected ? .connected : .disconnected
            let oldPresence = slotPresenceBySlot[slot]
            slotPresenceBySlot[slot] = newPresence

            // Clear stale identity when a device reconnects to a slot,
            // so the next needsIdentityRefresh check will trigger a refresh.
            if newPresence == .connected, oldPresence == .disconnected {
                pairedIdentitiesBySlot.removeValue(forKey: slot)
            }
        }
    }

    mutating func updateSlotIdentity(_ identity: ReceiverLogicalDeviceIdentity) {
        pairedIdentitiesBySlot[identity.slot] = identity
        slotPresenceBySlot[identity.slot] = .connected
    }

    func needsIdentityRefresh(slot: UInt8) -> Bool {
        pairedIdentitiesBySlot[slot] == nil
    }

    func currentPublishedIdentities() -> [ReceiverLogicalDeviceIdentity] {
        pairedIdentitiesBySlot.keys.sorted().compactMap { slot in
            guard let identity = pairedIdentitiesBySlot[slot] else {
                return nil
            }

            return slotPresenceBySlot[slot] == .disconnected ? nil : identity
        }
    }
}

private final class ReceiverContext {
    let device: Device
    private let locationID: Int
    private let provider: LogitechHIDPPDeviceMetadataProvider
    private var workerThread: Thread?
    private var isRunning = false
    private let stateLock = NSLock()
    private var lastPublishedIdentities = [ReceiverLogicalDeviceIdentity]()
    private var stateStore = ReceiverSlotStateStore()

    var onDiscoveryTimedOut: (() -> Void)?
    var onSlotsChanged: (([ReceiverLogicalDeviceIdentity]) -> Void)?
    init(device: Device, locationID: Int, provider: LogitechHIDPPDeviceMetadataProvider) {
        self.device = device
        self.locationID = locationID
        self.provider = provider
    }

    func start() {
        stateLock.lock()
        defer { stateLock.unlock() }

        guard !isRunning else {
            return
        }
        isRunning = true
        lastPublishedIdentities = []
        stateStore.reset()

        let thread = Thread { [weak self] in
            self?.workerMain()
        }
        thread.name = "linearmouse.receiver-monitor.\(locationID)"
        workerThread = thread
        thread.start()
    }

    func stop() {
        stateLock.lock()
        isRunning = false
        let thread = workerThread
        workerThread = nil
        stateLock.unlock()

        thread?.cancel()
    }

    private func workerMain() {
        let initialDeadline = Date().addingTimeInterval(ReceiverMonitor.initialDiscoveryTimeout)
        var hasPublishedInitialState = false
        var currentChannel: LogitechReceiverChannel?
        var hasCompletedInitialDiscovery = false

        while shouldContinueRunning() {
            if currentChannel == nil {
                currentChannel = provider.openReceiverChannel(for: device.pointerDevice)
                hasCompletedInitialDiscovery = false
            }

            guard let receiverChannel = currentChannel else {
                os_log(
                    "Receiver monitor is waiting for channel: locationID=%{public}d device=%{public}@",
                    log: ReceiverMonitor.log,
                    type: .info,
                    locationID,
                    String(describing: device)
                )

                if !hasPublishedInitialState, Date() >= initialDeadline {
                    DispatchQueue.main.async { [weak self] in
                        self?.onDiscoveryTimedOut?()
                    }
                    hasPublishedInitialState = true
                }

                CFRunLoopRunInMode(.defaultMode, 0.5, true)
                continue
            }

            // Full discovery only once per channel open
            if !hasCompletedInitialDiscovery {
                let discovery = provider.receiverPointingDeviceDiscovery(
                    for: device.pointerDevice, using: receiverChannel
                )
                mergeDiscovery(discovery)
                hasCompletedInitialDiscovery = true

                let identities = currentPublishedIdentities()
                let identitiesDescription = identities.map { identity in
                    let battery = identity.batteryLevel.map(String.init) ?? "(nil)"
                    return "slot=\(identity.slot) name=\(identity.name) battery=\(battery)"
                }
                .joined(separator: ", ")

                os_log(
                    "Receiver initial discovery completed: locationID=%{public}d count=%{public}u identities=%{public}@",
                    log: ReceiverMonitor.log,
                    type: .info,
                    locationID,
                    UInt32(identities.count),
                    identitiesDescription
                )

                if identities != lastPublishedIdentities {
                    publish(identities)
                    hasPublishedInitialState = true
                } else if !hasPublishedInitialState, !identities.isEmpty {
                    publish(identities)
                    hasPublishedInitialState = true
                } else if !hasPublishedInitialState, Date() >= initialDeadline {
                    os_log(
                        "Receiver logical discovery timed out: locationID=%{public}d device=%{public}@",
                        log: ReceiverMonitor.log,
                        type: .info,
                        locationID,
                        String(describing: device)
                    )
                    DispatchQueue.main.async { [weak self] in
                        self?.onDiscoveryTimedOut?()
                    }
                    hasPublishedInitialState = true
                }
            }

            // Wait for connection events (event-driven, no periodic rescan)
            let connectionSnapshots = provider.waitForReceiverConnectionChange(
                using: receiverChannel,
                timeout: ReceiverMonitor.refreshInterval
            ) { [weak self] in
                self?.shouldContinueRunning() ?? false
            }

            if !shouldContinueRunning() {
                break
            }

            guard !connectionSnapshots.isEmpty else {
                // Timeout with no events — verify channel is still alive
                if receiverChannel.readNotificationFlags() == nil {
                    os_log(
                        "Receiver channel appears dead, will reopen: locationID=%{public}d device=%{public}@",
                        log: ReceiverMonitor.log,
                        type: .info,
                        locationID,
                        String(describing: device)
                    )
                    currentChannel = nil
                }
                continue
            }

            mergeConnectionSnapshots(connectionSnapshots)

            // For newly connected devices, read their identity info
            for (slot, snapshot) in connectionSnapshots where snapshot.isConnected {
                if needsIdentityRefresh(slot: slot) {
                    refreshSlotIdentity(
                        slot: slot,
                        connectionSnapshot: snapshot,
                        using: receiverChannel
                    )
                }
            }

            let snapshotDescription = connectionSnapshots.keys
                .sorted()
                .compactMap { slot -> String? in
                    guard let snapshot = connectionSnapshots[slot] else {
                        return nil
                    }

                    return "slot=\(slot) connected=\(snapshot.isConnected)"
                }
                .joined(separator: ", ")

            os_log(
                "Receiver connection change detected: locationID=%{public}d device=%{public}@ snapshots=%{public}@",
                log: ReceiverMonitor.log,
                type: .info,
                locationID,
                String(describing: device),
                snapshotDescription
            )

            let identities = currentPublishedIdentities()
            if identities != lastPublishedIdentities {
                publish(identities)
            }
        }
    }

    private func publish(_ identities: [ReceiverLogicalDeviceIdentity]) {
        lastPublishedIdentities = identities

        let identitiesDescription = identities.map { identity in
            let battery = identity.batteryLevel.map(String.init) ?? "(nil)"
            return "slot=\(identity.slot) name=\(identity.name) battery=\(battery)"
        }
        .joined(separator: ", ")

        os_log(
            "Receiver logical discovery updated: locationID=%{public}d identities=%{public}@",
            log: ReceiverMonitor.log,
            type: .info,
            locationID,
            identitiesDescription
        )

        DispatchQueue.main.async { [weak self] in
            self?.onSlotsChanged?(identities)
        }
    }

    private func shouldContinueRunning() -> Bool {
        stateLock.lock()
        defer { stateLock.unlock() }
        return isRunning
    }

    private func mergeDiscovery(_ discovery: LogitechHIDPPDeviceMetadataProvider.ReceiverPointingDeviceDiscovery) {
        stateLock.lock()
        defer { stateLock.unlock() }
        stateStore.mergeDiscovery(discovery)
    }

    private func mergeConnectionSnapshots(
        _ newSnapshots: [UInt8: LogitechHIDPPDeviceMetadataProvider.ReceiverConnectionSnapshot]
    ) {
        guard !newSnapshots.isEmpty else {
            return
        }

        stateLock.lock()
        stateStore.mergeConnectionSnapshots(newSnapshots)
        stateLock.unlock()
    }

    private func refreshSlotIdentity(
        slot: UInt8,
        connectionSnapshot: LogitechHIDPPDeviceMetadataProvider.ReceiverConnectionSnapshot?,
        using receiverChannel: LogitechReceiverChannel
    ) {
        guard let identity = provider.receiverSlotIdentity(
            for: device.pointerDevice,
            slot: slot,
            connectionSnapshot: connectionSnapshot,
            using: receiverChannel
        ) else {
            return
        }

        stateLock.lock()
        stateStore.updateSlotIdentity(identity)
        stateLock.unlock()

        os_log(
            "Refreshed slot identity: locationID=%{public}d slot=%{public}u name=%{public}@ battery=%{public}@",
            log: ReceiverMonitor.log,
            type: .info,
            locationID,
            slot,
            identity.name,
            identity.batteryLevel.map(String.init) ?? "(nil)"
        )
    }

    private func needsIdentityRefresh(slot: UInt8) -> Bool {
        stateLock.lock()
        let needs = stateStore.needsIdentityRefresh(slot: slot)
        stateLock.unlock()
        return needs
    }

    private func currentPublishedIdentities() -> [ReceiverLogicalDeviceIdentity] {
        stateLock.lock()
        let identities = stateStore.currentPublishedIdentities()
        stateLock.unlock()
        return identities
    }
}


================================================
FILE: LinearMouse/Device/VendorSpecific/BatteryDeviceMonitor.swift
================================================
// MIT License
// Copyright (c) 2021-2026 LinearMouse

import Combine
import Foundation

final class BatteryDeviceMonitor: NSObject, ObservableObject {
    static let shared = BatteryDeviceMonitor()

    @Published private(set) var devices: [ConnectedBatteryDeviceInfo] = []

    private static let pollingInterval: TimeInterval = 60

    private let queue = DispatchQueue(label: "linearmouse.battery-monitor", qos: .utility)
    private let timerQueue = DispatchQueue(label: "linearmouse.battery-monitor.timer", qos: .utility)

    private var timer: DispatchSourceTimer?
    private var isRunning = false
    private var isRefreshing = false
    private var needsRefresh = false
    private let stateLock = NSLock()
    private var subscriptions = Set<AnyCancellable>()

    override init() {
        super.init()

        DeviceManager.shared
            .$devices
            .receive(on: RunLoop.main)
            .debounce(for: .milliseconds(250), scheduler: RunLoop.main)
            .sink { [weak self] _ in
                self?.refreshIfNeeded()
            }
            .store(in: &subscriptions)

        DeviceManager.shared
            .$receiverPairedDeviceIdentities
            .receive(on: RunLoop.main)
            .debounce(for: .milliseconds(250), scheduler: RunLoop.main)
            .sink { [weak self] _ in
                self?.refreshIfNeeded()
            }
            .store(in: &subscriptions)
    }

    func start() {
        stateLock.lock()
        defer { stateLock.unlock() }

        guard !isRunning else {
            return
        }
        isRunning = true

        let timer = DispatchSource.makeTimerSource(queue: timerQueue)
        timer.schedule(deadline: .now(), repeating: Self.pollingInterval)
        timer.setEventH
Download .txt
gitextract_23xgv3jw/

├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yml
│   │   ├── config.yml
│   │   └── feature_request.yml
│   ├── release.yml
│   └── workflows/
│       ├── add-coauthor.yml
│       ├── build.yml
│       └── stale.yml
├── .gitignore
├── .swift-version
├── .swiftformat
├── .swiftlint.yml
├── .vscode/
│   └── settings.json
├── ACCESSIBILITY.md
├── CLAUDE.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING-cn.md
├── CONTRIBUTING.md
├── Config.xcconfig
├── Documentation/
│   ├── Configuration.d.ts
│   ├── Configuration.json
│   ├── Configuration.md
│   └── translate-xcstrings.md
├── ExportOptions.plist
├── LICENSE
├── LinearMouse/
│   ├── AccessibilityPermission.swift
│   ├── AppDelegate.swift
│   ├── Assets.xcassets/
│   │   ├── AccentColor.colorset/
│   │   │   └── Contents.json
│   │   ├── AccessibilityIcon.imageset/
│   │   │   └── Contents.json
│   │   ├── AppIcon.appiconset/
│   │   │   └── Contents.json
│   │   ├── AppIconDev.appiconset/
│   │   │   └── Contents.json
│   │   ├── Contents.json
│   │   ├── MenuIcon.imageset/
│   │   │   └── Contents.json
│   │   ├── Minus.imageset/
│   │   │   └── Contents.json
│   │   ├── Plus.imageset/
│   │   │   └── Contents.json
│   │   └── Sidebar/
│   │       ├── Buttons.imageset/
│   │       │   └── Contents.json
│   │       ├── Contents.json
│   │       ├── General.imageset/
│   │       │   └── Contents.json
│   │       ├── Modifier Keys.imageset/
│   │       │   └── Contents.json
│   │       ├── Pointer.imageset/
│   │       │   └── Contents.json
│   │       └── Scrolling.imageset/
│   │           └── Contents.json
│   ├── AutoUpdateManager.swift
│   ├── DefaultsKeys.swift
│   ├── Device/
│   │   ├── Device.swift
│   │   ├── DeviceManager.swift
│   │   ├── InputReportHandler.swift
│   │   ├── InputReportHandlers/
│   │   │   ├── GenericSideButtonHandler.swift
│   │   │   └── KensingtonSlimbladeHandler.swift
│   │   ├── ReceiverLogicalDeviceIdentity.swift
│   │   ├── ReceiverMonitor.swift
│   │   └── VendorSpecific/
│   │       ├── BatteryDeviceMonitor.swift
│   │       ├── ConnectedBatteryDeviceInventory.swift
│   │       ├── ConnectedLogitechDeviceInventory.swift
│   │       ├── Logitech/
│   │       │   └── LogitechHIDPPDeviceMetadataProvider.swift
│   │       ├── PointerDevice+VendorSpecificDeviceContext.swift
│   │       └── VendorSpecificDeviceMetadata.swift
│   ├── EventTap/
│   │   ├── EventTap.swift
│   │   ├── EventThread.swift
│   │   ├── EventType.swift
│   │   ├── GlobalEventTap.swift
│   │   └── GlobalEventTapWatchdog.swift
│   ├── EventTransformer/
│   │   ├── AutoScrollTransformer.swift
│   │   ├── ButtonActionsTransformer.swift
│   │   ├── ClickDebouncingTransformer.swift
│   │   ├── EventTransformer.swift
│   │   ├── EventTransformerManager.swift
│   │   ├── GestureButtonTransformer.swift
│   │   ├── LinearScrollingHorizontalTransformer.swift
│   │   ├── LinearScrollingVerticalTransformer.swift
│   │   ├── LogitechEventContext.swift
│   │   ├── ModifierActionsTransformer.swift
│   │   ├── PointerRedirectsToScrollTransformer.swift
│   │   ├── ReverseScrollingTransformer.swift
│   │   ├── ScrollingAccelerationSpeedAdjustmentTransformer.swift
│   │   ├── SmoothedScrollingEngine.swift
│   │   ├── SmoothedScrollingTransformer.swift
│   │   ├── SwitchPrimaryAndSecondaryButtonsTransformer.swift
│   │   └── UniversalBackForwardTransformer.swift
│   ├── EventView/
│   │   ├── EventView.swift
│   │   ├── MouseEventView.swift
│   │   └── ScrollWheelEventView.swift
│   ├── Info.plist
│   ├── LinearMouse-Bridging-Header.h
│   ├── LinearMouse.entitlements
│   ├── LinearMouse.swift
│   ├── Localizable.xcstrings
│   ├── Model/
│   │   ├── AppTarget.swift
│   │   ├── Configuration/
│   │   │   ├── Configuration.swift
│   │   │   ├── DeviceMatcher.swift
│   │   │   └── Scheme/
│   │   │       ├── Buttons/
│   │   │       │   ├── AutoScroll/
│   │   │       │   │   └── AutoScroll.swift
│   │   │       │   ├── Buttons.swift
│   │   │       │   ├── ClickDebouncing/
│   │   │       │   │   └── ClickDebouncing.swift
│   │   │       │   ├── Gesture/
│   │   │       │   │   └── Gesture.swift
│   │   │       │   └── Mapping/
│   │   │       │       ├── Action+Codable.swift
│   │   │       │       ├── Action+CustomStringConvertible.swift
│   │   │       │       ├── Action+Kind.swift
│   │   │       │       ├── Action.swift
│   │   │       │       └── Mapping.swift
│   │   │       ├── If/
│   │   │       │   └── If.swift
│   │   │       ├── Pointer.swift
│   │   │       ├── Scheme.swift
│   │   │       └── Scrolling/
│   │   │           ├── Bidirectional.swift
│   │   │           ├── Distance+Mode.swift
│   │   │           ├── Distance.swift
│   │   │           ├── Modifiers+Kind.swift
│   │   │           ├── Modifiers.swift
│   │   │           ├── Scrolling.swift
│   │   │           └── Smoothed.swift
│   │   └── DeviceModel.swift
│   ├── ModifierKeys.swift
│   ├── ScreenManager/
│   │   └── ScreenManager.swift
│   ├── State/
│   │   ├── ConfigurationState.swift
│   │   ├── DeviceState.swift
│   │   ├── ModifierState.swift
│   │   ├── SchemeState.swift
│   │   └── SettingsState.swift
│   ├── UI/
│   │   ├── AccessibilityPermissionView.swift
│   │   ├── AccessibilityPermissionWindow.swift
│   │   ├── Base.lproj/
│   │   │   └── Main.storyboard
│   │   ├── ButtonStyle/
│   │   │   └── SecondaryButtonStyle.swift
│   │   ├── ButtonsSettings/
│   │   │   ├── AutoScrollSection/
│   │   │   │   └── AutoScrollSection.swift
│   │   │   ├── ButtonMappingsSection/
│   │   │   │   ├── ButtonMapping.swift
│   │   │   │   ├── ButtonMappingAction/
│   │   │   │   │   ├── ButtonMappingAction+Binding.swift
│   │   │   │   │   ├── ButtonMappingAction.swift
│   │   │   │   │   ├── ButtonMappingActionKeyPress.swift
│   │   │   │   │   ├── ButtonMappingActionPicker.swift
│   │   │   │   │   ├── ButtonMappingActionRun.swift
│   │   │   │   │   ├── ButtonMappingActionScroll.swift
│   │   │   │   │   └── ScrollingDistance+Binding.swift
│   │   │   │   ├── ButtonMappingButtonRecorder.swift
│   │   │   │   ├── ButtonMappingEditSheet.swift
│   │   │   │   └── ButtonMappingsSection.swift
│   │   │   ├── ButtonsSettings.swift
│   │   │   ├── ButtonsSettingsState.swift
│   │   │   ├── ClickDebouncingSection.swift
│   │   │   ├── GestureButtonSection/
│   │   │   │   ├── GestureActionPicker.swift
│   │   │   │   └── GestureButtonSection.swift
│   │   │   ├── SwitchPrimaryAndSecondaryButtonsSection.swift
│   │   │   └── UniversalBackForwardSection.swift
│   │   ├── CheckForUpdatesView.swift
│   │   ├── DetailView/
│   │   │   └── DetailView.swift
│   │   ├── Extensions/
│   │   │   ├── NSWindow.swift
│   │   │   └── View.swift
│   │   ├── GeneralSettings/
│   │   │   ├── ConfigurationSection.swift
│   │   │   ├── GeneralSettings.swift
│   │   │   └── LoggingSection.swift
│   │   ├── Header/
│   │   │   ├── AppIndicator/
│   │   │   │   ├── AppIndicator.swift
│   │   │   │   └── AppPickerSheet/
│   │   │   │       ├── AppPicker.swift
│   │   │   │       ├── AppPickerSheet.swift
│   │   │   │       └── AppPickerState.swift
│   │   │   ├── DeviceIndicator/
│   │   │   │   ├── DeviceIndicator.swift
│   │   │   │   ├── DeviceIndicatorState.swift
│   │   │   │   └── DevicePickerSheet/
│   │   │   │       ├── DeviceButtonStyle.swift
│   │   │   │       ├── DevicePicker.swift
│   │   │   │       ├── DevicePickerBatteryCoordinator.swift
│   │   │   │       ├── DevicePickerSection.swift
│   │   │   │       ├── DevicePickerSectionItem.swift
│   │   │   │       ├── DevicePickerSheet.swift
│   │   │   │       └── DevicePickerState.swift
│   │   │   ├── DisplayIndicator/
│   │   │   │   ├── DisplayIndicator.swift
│   │   │   │   └── DisplayPickerSheet/
│   │   │   │       ├── DisplayPicker.swift
│   │   │   │       ├── DisplayPickerSheet.swift
│   │   │   │       └── DisplayPickerState.swift
│   │   │   └── SchemeIndicator.swift
│   │   ├── HelpButton/
│   │   │   └── HelpButton.swift
│   │   ├── HyperLink.swift
│   │   ├── PointerSettings/
│   │   │   ├── PointerSettings.swift
│   │   │   └── PointerSettingsState.swift
│   │   ├── ScrollingSettings/
│   │   │   ├── Header.swift
│   │   │   ├── ModifierKeysSection/
│   │   │   │   ├── ModifierAction+Binding.swift
│   │   │   │   ├── ModifierKeyActionPicker.swift
│   │   │   │   └── ModifierKeysSection.swift
│   │   │   ├── ReverseScrollingSection.swift
│   │   │   ├── ScrollingModeSection.swift
│   │   │   ├── ScrollingSettings.swift
│   │   │   ├── ScrollingSettingsState.swift
│   │   │   └── SmoothedScrollingSection.swift
│   │   ├── Settings.swift
│   │   ├── SettingsWindowController.swift
│   │   ├── Sidebar/
│   │   │   ├── Sidebar.swift
│   │   │   └── SidebarItem.swift
│   │   ├── StatusItem.swift
│   │   ├── ViewModifiers/
│   │   │   └── FormViewModifier.swift
│   │   ├── VisualEffectView.swift
│   │   └── zh-Hant-HK.lproj/
│   │       └── Main.strings
│   └── Utilities/
│       ├── Application.swift
│       ├── ApplicationBundle.swift
│       ├── CGEvent+LinearMouseSynthetic.swift
│       ├── Codable/
│       │   ├── Clamp.swift
│       │   ├── HexRepresentation.swift
│       │   ├── ImplicitOptional.swift
│       │   ├── SingleValueOrArray.swift
│       │   └── Unsettable.swift
│       ├── CustomDecodingError.swift
│       ├── Extensions.swift
│       ├── KeyboardSettingsSnapshot.swift
│       ├── Notifier.swift
│       ├── Process.h
│       ├── Process.m
│       ├── ProcessEnvironment.swift
│       ├── WeakRef.swift
│       └── WindowInfo.swift
├── LinearMouse.xcodeproj/
│   ├── project.pbxproj
│   ├── project.xcworkspace/
│   │   ├── contents.xcworkspacedata
│   │   └── xcshareddata/
│   │       ├── IDEWorkspaceChecks.plist
│   │       └── swiftpm/
│   │           └── Package.resolved
│   └── xcshareddata/
│       └── xcschemes/
│           └── LinearMouse.xcscheme
├── LinearMouseUnitTests/
│   ├── Device/
│   │   ├── InputReportHandlerTests.swift
│   │   └── VendorSpecificDeviceMetadataTests.swift
│   ├── EventTransformer/
│   │   ├── AutoScrollTransformerTests.swift
│   │   ├── ButtonActionsTransformerTests.swift
│   │   ├── EventTransformerManagerTests.swift
│   │   ├── LinearScrollingTransformerTests.swift
│   │   ├── ModifierActionsTransformerTests.swift
│   │   ├── ReverseScrollingTransformerTests.swift
│   │   ├── SmoothedScrollingEngineTests.swift
│   │   ├── SmoothedScrollingTransformerTests.swift
│   │   └── UniversalBackForwardTransformerTests.swift
│   ├── Model/
│   │   ├── ConfigurationTests.swift
│   │   └── Scheme/
│   │       ├── Buttons/
│   │       │   ├── GestureTests.swift
│   │       │   └── MappingActionKindTests.swift
│   │       └── Scrolling/
│   │           ├── BidirectionalTests.swift
│   │           ├── DistanceModeTests.swift
│   │           └── ModifiersKindTests.swift
│   ├── UI/
│   │   ├── ButtonMappingActionBindingTests.swift
│   │   ├── ModifierKeyActionPickerTests.swift
│   │   ├── ScrollingDistanceBindingTests.swift
│   │   └── StatusItemBatteryIndicatorTests.swift
│   └── Utilities/
│       ├── Codable/
│       │   └── ImplicitOptionalTests.swift
│       └── KeyboardSettingsSnapshotTests.swift
├── Makefile
├── Modules/
│   ├── DockKit/
│   │   ├── .gitignore
│   │   ├── Package.swift
│   │   ├── README.md
│   │   ├── Sources/
│   │   │   ├── DockKit/
│   │   │   │   └── DockKit.swift
│   │   │   └── DockKitC/
│   │   │       ├── DockKitC.m
│   │   │       └── include/
│   │   │           └── ApplicationServicesSPI.h
│   │   └── Tests/
│   │       └── DockKitTests/
│   │           └── DockKitTests.swift
│   ├── GestureKit/
│   │   ├── .gitignore
│   │   ├── Package.swift
│   │   ├── README.md
│   │   ├── Sources/
│   │   │   └── GestureKit/
│   │   │       ├── CGEventField+Extensions.swift
│   │   │       ├── CGEventType+Extensions.swift
│   │   │       ├── GestureEvent+NavigationSwipe.swift
│   │   │       ├── GestureEvent+Zoom.swift
│   │   │       └── GestureEvent.swift
│   │   └── Tests/
│   │       └── GestureKitTests/
│   │           └── GestureKitTests.swift
│   ├── KeyKit/
│   │   ├── .gitignore
│   │   ├── Package.swift
│   │   ├── README.md
│   │   ├── Sources/
│   │   │   ├── KeyKit/
│   │   │   │   ├── Key.swift
│   │   │   │   ├── KeyCodeResolver.swift
│   │   │   │   ├── KeySimulator.swift
│   │   │   │   ├── SymbolicHotKey.swift
│   │   │   │   └── SystemDefinedKey.swift
│   │   │   └── KeyKitC/
│   │   │       ├── CGSInternal/
│   │   │       │   ├── CGSAccessibility.h
│   │   │       │   ├── CGSCIFilter.h
│   │   │       │   ├── CGSConnection.h
│   │   │       │   ├── CGSCursor.h
│   │   │       │   ├── CGSDebug.h
│   │   │       │   ├── CGSDevice.h
│   │   │       │   ├── CGSDisplays.h
│   │   │       │   ├── CGSEvent.h
│   │   │       │   ├── CGSHotKeys.h
│   │   │       │   ├── CGSInternal.h
│   │   │       │   ├── CGSMisc.h
│   │   │       │   ├── CGSRegion.h
│   │   │       │   ├── CGSSession.h
│   │   │       │   ├── CGSSpace.h
│   │   │       │   ├── CGSSurface.h
│   │   │       │   ├── CGSTile.h
│   │   │       │   ├── CGSTransitions.h
│   │   │       │   ├── CGSWindow.h
│   │   │       │   └── CGSWorkspace.h
│   │   │       ├── KeyKitC.m
│   │   │       └── include/
│   │   │           └── KeyKitC.h
│   │   └── Tests/
│   │       └── KeyKitTests/
│   │           └── KeyKitTests.swift
│   ├── ObservationToken/
│   │   ├── .gitignore
│   │   ├── Package.swift
│   │   ├── README.md
│   │   ├── Sources/
│   │   │   └── ObservationToken/
│   │   │       ├── LifetimeAssociation.swift
│   │   │       └── ObservationToken.swift
│   │   └── Tests/
│   │       └── ObservationTokenTests/
│   │           └── ObservationTokenTests.swift
│   ├── PointerKit/
│   │   ├── .gitignore
│   │   ├── Package.swift
│   │   ├── README.md
│   │   ├── Sources/
│   │   │   ├── PointerKit/
│   │   │   │   ├── Extensions/
│   │   │   │   │   ├── Comparable+Extensions.swift
│   │   │   │   │   ├── Dictionary+Extensions.swift
│   │   │   │   │   ├── IOHIDElement+Extensions.swift
│   │   │   │   │   ├── IOHIDServiceClient+Property.swift
│   │   │   │   │   ├── IOHIDServiceClient+Service.swift
│   │   │   │   │   └── IOHIDValue+Extensions.swift
│   │   │   │   ├── PointerDevice.swift
│   │   │   │   └── PointerDeviceManager.swift
│   │   │   └── PointerKitC/
│   │   │       ├── PointerKitC.m
│   │   │       └── include/
│   │   │           └── IOKitSPIMac.h
│   │   └── Tests/
│   │       └── PointerKitTests/
│   │           └── PointerDeviceManagerTest.swift
│   └── README.md
├── README-cn.md
├── README.md
├── Scripts/
│   ├── configure-code-signing
│   ├── configure-release
│   ├── configure-version
│   ├── pre-commit
│   ├── sign-and-notarize
│   └── translate-xcstrings.mjs
├── Signing.xcconfig.tpl
├── crowdin.yml
└── package.json
Download .txt
SYMBOL INDEX (179 symbols across 20 files)

FILE: Documentation/Configuration.d.ts
  type SingleValueOrArray (line 1) | type SingleValueOrArray<T> = T | T[];
  type Int (line 4) | type Int = number;
  type IntString (line 7) | type IntString = string;
  type HexString (line 10) | type HexString = string;
  type PhysicalButton (line 12) | type PhysicalButton = Primary | Secondary | Auxiliary | Back | Forward |...
  type Unset (line 18) | type Unset = "unset";
  type Primary (line 23) | type Primary = 0;
  type Secondary (line 28) | type Secondary = 1;
  type Auxiliary (line 33) | type Auxiliary = 2;
  type Back (line 38) | type Back = 3;
  type Forward (line 43) | type Forward = 4;
  type Configuration (line 45) | type Configuration = {
  type Scheme (line 56) | type Scheme = {
  type If (line 83) | type If = {
  type Device (line 128) | type Device = {
  type Mouse (line 166) | type Mouse = "mouse";
  type Trackpad (line 172) | type Trackpad = "trackpad";
  type Category (line 174) | type Category = Mouse | Trackpad;
  type Scrolling (line 177) | type Scrolling = {
  type Bidirectional (line 214) | type Bidirectional<T> =
  type Auto (line 225) | type Auto = "auto";
  type Distance (line 227) | type Distance = Auto | Distance.Line | Distance.Pixel;
  type Line (line 233) | type Line = Int | IntString;
  type Pixel (line 239) | type Pixel = string;
  type Smoothed (line 242) | type Smoothed = {
  type Preset (line 277) | type Preset =
  type Modifiers (line 305) | type Modifiers = {
  type None (line 332) | type None = { type: "none" };
  type Auto (line 337) | type Auto = { type: "auto" };
  type Ignore (line 342) | type Ignore = { type: "ignore" };
  type PreventDefault (line 347) | type PreventDefault = { type: "preventDefault" };
  type AlterOrientation (line 352) | type AlterOrientation = {
  type ChangeSpeed (line 359) | type ChangeSpeed = {
  type Zoom (line 371) | type Zoom = {
  type PinchZoom (line 378) | type PinchZoom = {
  type Action (line 382) | type Action =
  type Pointer (line 394) | type Pointer = {
  type Buttons (line 426) | type Buttons = {
  type AutoScroll (line 467) | type AutoScroll = {
  type Toggle (line 503) | type Toggle = "toggle";
  type Hold (line 508) | type Hold = "hold";
  type Mode (line 510) | type Mode = Toggle | Hold;
  type Trigger (line 512) | type Trigger = {
  type Mapping (line 541) | type Mapping = (
  type Button (line 594) | type Button = PhysicalButton | LogitechControlButton;
  type LogitechControlButton (line 596) | type LogitechControlButton = {
  type Action (line 618) | type Action =
  type SimpleAction (line 627) | type SimpleAction =
  type Auto (line 663) | type Auto = "auto";
  type None (line 668) | type None = "none";
  type MissionControl (line 673) | type MissionControl = "missionControl";
  type MissionControlSpaceLeft (line 678) | type MissionControlSpaceLeft = "missionControl.spaceLeft";
  type MissionControlSpaceRight (line 683) | type MissionControlSpaceRight = "missionControl.spaceRight";
  type AppExpose (line 688) | type AppExpose = "appExpose";
  type Launchpad (line 693) | type Launchpad = "launchpad";
  type ShowDesktop (line 698) | type ShowDesktop = "showDesktop";
  type LookUpAndDataDetectors (line 703) | type LookUpAndDataDetectors = "lookUpAndDataDetectors";
  type SmartZoom (line 708) | type SmartZoom = "smartZoom";
  type DisplayBrightnessUp (line 713) | type DisplayBrightnessUp = "display.brightnessUp";
  type DisplayBrightnessDown (line 718) | type DisplayBrightnessDown = "display.brightnessDown";
  type MediaVolumeUp (line 723) | type MediaVolumeUp = "media.volumeUp";
  type MediaVolumeDown (line 728) | type MediaVolumeDown = "media.volumeDown";
  type MediaMute (line 733) | type MediaMute = "media.mute";
  type MediaPlayPause (line 738) | type MediaPlayPause = "media.playPause";
  type MediaNext (line 743) | type MediaNext = "media.next";
  type MediaPrevious (line 748) | type MediaPrevious = "media.previous";
  type MediaFastForward (line 753) | type MediaFastForward = "media.fastForward";
  type MediaRewind (line 758) | type MediaRewind = "media.rewind";
  type KeyboardBrightnessUp (line 763) | type KeyboardBrightnessUp = "keyboard.brightnessUp";
  type KeyboardBrightnessDown (line 768) | type KeyboardBrightnessDown = "keyboard.brightnessDown";
  type MouseWheelScrollUp (line 773) | type MouseWheelScrollUp = "mouse.wheel.scrollUp";
  type MouseWheelScrollDown (line 778) | type MouseWheelScrollDown = "mouse.wheel.scrollDown";
  type MouseWheelScrollLeft (line 783) | type MouseWheelScrollLeft = "mouse.wheel.scrollLeft";
  type MouseWheelScrollRight (line 788) | type MouseWheelScrollRight = "mouse.wheel.scrollRight";
  type MouseButtonLeft (line 793) | type MouseButtonLeft = "mouse.button.left";
  type MouseButtonMiddle (line 798) | type MouseButtonMiddle = "mouse.button.middle";
  type MouseButtonRight (line 803) | type MouseButtonRight = "mouse.button.right";
  type MouseButtonBack (line 808) | type MouseButtonBack = "mouse.button.back";
  type MouseButtonForward (line 813) | type MouseButtonForward = "mouse.button.forward";
  type Run (line 815) | type Run = {
  type MouseWheelScrollUpWithDistance (line 822) | type MouseWheelScrollUpWithDistance = {
  type MouseWheelScrollDownWithDistance (line 829) | type MouseWheelScrollDownWithDistance = {
  type MouseWheelScrollLeftWithDistance (line 836) | type MouseWheelScrollLeftWithDistance = {
  type MouseWheelScrollRightWithDistance (line 843) | type MouseWheelScrollRightWithDistance = {
  type KeyPress (line 850) | type KeyPress = {
  type ScrollDirection (line 860) | type ScrollDirection = "up" | "down" | "left" | "right";
  type Key (line 862) | type Key =
  type UniversalBackForward (line 965) | type UniversalBackForward =
  type BackOnly (line 974) | type BackOnly = "backOnly";
  type ForwardOnly (line 979) | type ForwardOnly = "forwardOnly";
  type ClickDebouncing (line 982) | type ClickDebouncing = {
  type Gesture (line 999) | type Gesture = {
  type Trigger (line 1055) | type Trigger = {
  type Actions (line 1083) | type Actions = {
  type GestureAction (line 1113) | type GestureAction =

FILE: LinearMouse/LinearMouse-Bridging-Header.h
  type IOHIDEventType (line 26) | typedef uint32_t IOHIDEventType;
  type CFTypeRef (line 27) | typedef CFTypeRef IOHIDEventRef;
  type IOHIDFloat (line 28) | typedef double IOHIDFloat;
  type IOHIDEventField (line 29) | typedef uint32_t IOHIDEventField;

FILE: LinearMouse/Utilities/Process.h
  type ProcessInfo (line 6) | typedef struct ProcessInfo {

FILE: Modules/KeyKit/Sources/KeyKitC/CGSInternal/CGSCIFilter.h
  type CGSCIFilterID (line 35) | typedef enum {

FILE: Modules/KeyKit/Sources/KeyKitC/CGSInternal/CGSConnection.h
  type CGSConnectionID (line 37) | typedef int CGSConnectionID;
  type CGSTransitionID (line 41) | typedef int CGSTransitionID;
  type CGSProcessNotificationData (line 161) | typedef struct {
  type CGSDebugNotificationData (line 179) | typedef struct {
  type CGSTransitionNotificationData (line 185) | typedef struct {

FILE: Modules/KeyKit/Sources/KeyKitC/CGSInternal/CGSCursor.h
  type CGSCursorID (line 35) | typedef enum : NSInteger {

FILE: Modules/KeyKit/Sources/KeyKitC/CGSInternal/CGSDebug.h
  type CGSDebugOption (line 38) | typedef enum {

FILE: Modules/KeyKit/Sources/KeyKitC/CGSInternal/CGSDisplays.h
  type CGSDisplayQuery (line 36) | typedef enum {
  type CGSDisplayModeDescription (line 40) | typedef struct {
  type CGSDisplayMode (line 53) | typedef int CGSDisplayMode;

FILE: Modules/KeyKit/Sources/KeyKitC/CGSInternal/CGSEvent.h
  type CGSByteCount (line 15) | typedef unsigned long CGSByteCount;
  type CGSEventRecordVersion (line 16) | typedef unsigned short CGSEventRecordVersion;
  type CGSEventRecordTime (line 17) | typedef unsigned long long CGSEventRecordTime;
  type CGSEventFlag (line 18) | typedef unsigned long CGSEventFlag;
  type CGSError (line 19) | typedef unsigned long  CGSError;
  type CGSEventRecord (line 208) | typedef struct _CGSEventRecord {

FILE: Modules/KeyKit/Sources/KeyKitC/CGSInternal/CGSHotKeys.h
  type CGSSymbolicHotKey (line 38) | typedef enum {
  type CGSGlobalHotKeyOperatingMode (line 111) | typedef enum {

FILE: Modules/KeyKit/Sources/KeyKitC/CGSInternal/CGSRegion.h
  type CFTypeRef (line 33) | typedef CFTypeRef CGSRegionRef;
  type CFTypeRef (line 34) | typedef CFTypeRef CGSRegionEnumeratorRef;

FILE: Modules/KeyKit/Sources/KeyKitC/CGSInternal/CGSSession.h
  type CGSSessionID (line 35) | typedef int CGSSessionID;

FILE: Modules/KeyKit/Sources/KeyKitC/CGSInternal/CGSSpace.h
  type CGSSpaceID (line 16) | typedef size_t CGSSpaceID;
  type CGSSpaceType (line 19) | typedef enum {
  type CGSSpaceMask (line 29) | typedef enum {
  type CGSSpaceManagementMode (line 42) | typedef enum {

FILE: Modules/KeyKit/Sources/KeyKitC/CGSInternal/CGSSurface.h
  type CGSSurfaceID (line 15) | typedef int CGSSurfaceID;

FILE: Modules/KeyKit/Sources/KeyKitC/CGSInternal/CGSTile.h
  type CGSTileID (line 15) | typedef size_t CGSTileID;

FILE: Modules/KeyKit/Sources/KeyKitC/CGSInternal/CGSTransitions.h
  type CGSTransitionType (line 35) | typedef enum {
  type CGSTransitionFlags (line 59) | typedef enum {
  type CGSTransitionSpec (line 74) | struct CGSTransitionSpec {

FILE: Modules/KeyKit/Sources/KeyKitC/CGSInternal/CGSWindow.h
  type CFTypeRef (line 36) | typedef CFTypeRef CGSAnimationRef;
  type CFTypeRef (line 37) | typedef CFTypeRef CGSWindowBackdropRef;
  type CGSWarpPoint (line 38) | typedef struct CGSWarpPoint CGSWarpPoint;
  type CGSSharingState (line 42) | typedef enum {
  type CGSWindowOrderingMode (line 48) | typedef enum {
  type CGSBackingType (line 55) | typedef enum {
  type CGSWindowSaveWeighting (line 61) | typedef enum {
  type CGSWindowTagBit (line 69) | typedef enum : int {
  type CGSWarpPoint (line 240) | struct CGSWarpPoint {

FILE: Modules/KeyKit/Sources/KeyKitC/CGSInternal/CGSWorkspace.h
  type CGSWorkspaceID (line 37) | typedef unsigned int CGSWorkspaceID;

FILE: Modules/PointerKit/Sources/PointerKitC/include/IOKitSPIMac.h
  type CF_BRIDGED_TYPE (line 39) | struct CF_BRIDGED_TYPE
  type CF_BRIDGED_TYPE (line 40) | struct CF_BRIDGED_TYPE
  type kIOHIDEventSystemClientTypeAdmin (line 43) | typedef CF_ENUM(int, IOHIDEventSystemClientType)
  type IOHIDEventType (line 82) | typedef uint32_t IOHIDEventType;
  type IOHIDEventField (line 84) | typedef uint32_t IOHIDEventField;
  type IOHIDEventSenderID (line 85) | typedef uint64_t IOHIDEventSenderID;
  type IOHIDEventScrollMomentumBits (line 91) | typedef uint8_t IOHIDEventScrollMomentumBits;
  type IOHIDFloat (line 94) | typedef double IOHIDFloat;
  type IOHIDFloat (line 96) | typedef float IOHIDFloat;

FILE: Scripts/translate-xcstrings.mjs
  constant DEFAULT_PROJECT_PATH (line 11) | const DEFAULT_PROJECT_PATH = 'LinearMouse.xcodeproj'
  constant DEFAULT_BASE_URL (line 12) | const DEFAULT_BASE_URL = 'https://openrouter.ai/api/v1'
  constant DEFAULT_MODEL (line 13) | const DEFAULT_MODEL = 'openai/gpt-4.1-mini'
  constant DEFAULT_BATCH_SIZE (line 14) | const DEFAULT_BATCH_SIZE = 25
  constant DEFAULT_RETRIES (line 15) | const DEFAULT_RETRIES = 3
  function printHelp (line 17) | function printHelp() {
  function parseArgs (line 43) | function parseArgs(argv) {
  function parsePositiveInteger (line 112) | function parsePositiveInteger(value, flagName) {
  function parseList (line 120) | function parseList(value) {
  function isObject (line 127) | function isObject(value) {
  function runCommand (line 131) | async function runCommand(command, args, options = {}) {
  function createClient (line 152) | function createClient(options) {
  function exportLocalizations (line 173) | async function exportLocalizations(projectPath, exportPath, languages) {
  function listProjectLanguages (line 184) | async function listProjectLanguages(projectPath) {
  function importLocalizations (line 198) | async function importLocalizations(projectPath, xclocPaths) {
  function findExportedXLIFFFiles (line 212) | async function findExportedXLIFFFiles(exportPath) {
  function getFirstChildByTagName (line 243) | function getFirstChildByTagName(node, tagName) {
  function shouldKeepUnchanged (line 252) | function shouldKeepUnchanged(source, id) {
  function filterDocumentToXCStrings (line 277) | function filterDocumentToXCStrings(document) {
  function collectPendingUnits (line 288) | function collectPendingUnits(document, language) {
  function buildMessages (line 330) | function buildMessages(language, batch) {
  function extractJson (line 359) | function extractJson(text) {
  function validateBatchResponse (line 383) | function validateBatchResponse(batch, response) {
  function translateBatch (line 399) | async function translateBatch(client, language, batch, options, attempt ...
  function createTargetNode (line 422) | function createTargetNode(document, transUnit) {
  function chunk (line 435) | function chunk(items, size) {
  function processXLIFFFile (line 443) | async function processXLIFFFile(fileInfo, client, options, remainingBudg...
  function removeDirectoryIfNeeded (line 499) | async function removeDirectoryIfNeeded(targetPath, keep) {
  function main (line 508) | async function main() {
Condensed preview — 308 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (3,058K chars).
[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "chars": 1264,
    "preview": "name: Bug report\ndescription: Create a report to help us improve.\nlabels: bug\nbody:\n- type: input\n  id: os\n  attributes:"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 150,
    "preview": "contact_links:\n  - name: Discussions\n    url: https://github.com/linearmouse/linearmouse/discussions\n    about: Please a"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "chars": 1369,
    "preview": "name: Feature request\ndescription: Suggest an idea for this project.\nlabels: enhancement\nbody:\n- type: input\n  id: os\n  "
  },
  {
    "path": ".github/release.yml",
    "chars": 243,
    "preview": "changelog:\n  exclude:\n    labels:\n      - ignore for release\n  categories:\n    - title: New features\n      labels:\n     "
  },
  {
    "path": ".github/workflows/add-coauthor.yml",
    "chars": 5480,
    "preview": "name: Add Co-Author to PR\n\non:\n  issue_comment:\n    types: [created]\n\njobs:\n  add-coauthor:\n    runs-on: ubuntu-latest\n "
  },
  {
    "path": ".github/workflows/build.yml",
    "chars": 3021,
    "preview": "name: Build\non:\n  push:\n    branches:\n      - main\n    tags:\n      - v*\n  pull_request:\n    branches:\n      - main\n\njobs"
  },
  {
    "path": ".github/workflows/stale.yml",
    "chars": 1084,
    "preview": "name: \"Close stale issues and PRs\"\non:\n  schedule:\n    - cron: \"0 0 * * *\"\n  issue_comment:\n    types: [created]\n\njobs:\n"
  },
  {
    "path": ".gitignore",
    "chars": 638,
    "preview": "# Xcode\n#\n# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore\n\n"
  },
  {
    "path": ".swift-version",
    "chars": 4,
    "preview": "5.6\n"
  },
  {
    "path": ".swiftformat",
    "chars": 281,
    "preview": "--commas inline\n--disable wrapMultilineStatementBraces\n--enable wrapConditionalBodies\n--enable wrapMultilineFunctionChai"
  },
  {
    "path": ".swiftlint.yml",
    "chars": 4567,
    "preview": "only_rules:\n  - accessibility_trait_for_button\n  - array_init\n  - blanket_disable_command\n  - block_based_kvo\n  - class_"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 34,
    "preview": "{\n  \"editor.formatOnSave\": true\n}\n"
  },
  {
    "path": "ACCESSIBILITY.md",
    "chars": 1008,
    "preview": "# Accessibility permission\n\nLinearMouse requires accessibility features to work properly.\nYou need to grant Accessibilit"
  },
  {
    "path": "CLAUDE.md",
    "chars": 3981,
    "preview": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## "
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 5489,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
  },
  {
    "path": "CONTRIBUTING-cn.md",
    "chars": 752,
    "preview": "# 贡献指南\n\n感谢你投入时间为 LinearMouse 做出贡献。\n\n阅读我们的[行为准则](CODE_OF_CONDUCT.md),以保持我们的社区平易近人,受到尊重。\n\n## 构建指南\n\n在 macOS 上构建 LinearMouse"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1912,
    "preview": "# Contributing guide\n\nThank you for investing your time in contributing to LinearMouse!\n\nRead our [Code of Conduct](CODE"
  },
  {
    "path": "Config.xcconfig",
    "chars": 269,
    "preview": "CURRENT_PROJECT_VERSION = dev\nMARKETING_VERSION = $(CURRENT_PROJECT_VERSION)\n\nPRODUCT_BUNDLE_IDENTIFIER = com.lujjjh.dev"
  },
  {
    "path": "Documentation/Configuration.d.ts",
    "chars": 27323,
    "preview": "type SingleValueOrArray<T> = T | T[];\n\n/** @asType number */\ntype Int = number;\n\n/** @pattern ^\\d+$ */\ntype IntString = "
  },
  {
    "path": "Documentation/Configuration.json",
    "chars": 47477,
    "preview": "{\n  \"$ref\": \"#/definitions/Configuration\",\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n  \"definitions\": {\n  "
  },
  {
    "path": "Documentation/Configuration.md",
    "chars": 18558,
    "preview": "# Configuration\n\nThe LinearMouse configuration is stored in `~/.config/linearmouse/linearmouse.json`.\n\nIf the configurat"
  },
  {
    "path": "Documentation/translate-xcstrings.md",
    "chars": 2043,
    "preview": "# XCStrings LLM Translation Script\n\n`Scripts/translate-xcstrings.mjs` uses Xcode's native localization export/import flo"
  },
  {
    "path": "ExportOptions.plist",
    "chars": 459,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "LICENSE",
    "chars": 1073,
    "preview": "MIT License\n\nCopyright (c) 2021-2024 LinearMouse\n\nPermission is hereby granted, free of charge, to any person obtaining "
  },
  {
    "path": "LinearMouse/AccessibilityPermission.swift",
    "chars": 1039,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport os.log\nimport SwiftUI\n\nenum Accessibilit"
  },
  {
    "path": "LinearMouse/AppDelegate.swift",
    "chars": 2949,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport AppMover\nimport Combine\nimport LaunchAtLogin\nimport os.log"
  },
  {
    "path": "LinearMouse/Assets.xcassets/AccentColor.colorset/Contents.json",
    "chars": 326,
    "preview": "{\n  \"colors\" : [\n    {\n      \"color\" : {\n        \"color-space\" : \"srgb\",\n        \"components\" : {\n          \"alpha\" : \"1"
  },
  {
    "path": "LinearMouse/Assets.xcassets/AccessibilityIcon.imageset/Contents.json",
    "chars": 320,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"UniversalAcces"
  },
  {
    "path": "LinearMouse/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 1217,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"filename\""
  },
  {
    "path": "LinearMouse/Assets.xcassets/AppIconDev.appiconset/Contents.json",
    "chars": 939,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"idiom\" : "
  },
  {
    "path": "LinearMouse/Assets.xcassets/Contents.json",
    "chars": 63,
    "preview": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "LinearMouse/Assets.xcassets/MenuIcon.imageset/Contents.json",
    "chars": 451,
    "preview": "{\n  \"images\" : [\n    {\n      \"filename\" : \"MenuIcon.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {"
  },
  {
    "path": "LinearMouse/Assets.xcassets/Minus.imageset/Contents.json",
    "chars": 372,
    "preview": "{\n  \"images\" : [\n    {\n      \"filename\" : \"minus.svg\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n  "
  },
  {
    "path": "LinearMouse/Assets.xcassets/Plus.imageset/Contents.json",
    "chars": 371,
    "preview": "{\n  \"images\" : [\n    {\n      \"filename\" : \"plus.svg\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n   "
  },
  {
    "path": "LinearMouse/Assets.xcassets/Sidebar/Buttons.imageset/Contents.json",
    "chars": 389,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"button.program"
  },
  {
    "path": "LinearMouse/Assets.xcassets/Sidebar/Contents.json",
    "chars": 63,
    "preview": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "LinearMouse/Assets.xcassets/Sidebar/General.imageset/Contents.json",
    "chars": 384,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"gearshape.fill"
  },
  {
    "path": "LinearMouse/Assets.xcassets/Sidebar/Modifier Keys.imageset/Contents.json",
    "chars": 377,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"command@2x.png"
  },
  {
    "path": "LinearMouse/Assets.xcassets/Sidebar/Pointer.imageset/Contents.json",
    "chars": 381,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"cursorarrow@2x"
  },
  {
    "path": "LinearMouse/Assets.xcassets/Sidebar/Scrolling.imageset/Contents.json",
    "chars": 387,
    "preview": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"arrow.up.and.d"
  },
  {
    "path": "LinearMouse/AutoUpdateManager.swift",
    "chars": 1365,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Defaults\nimport Foundation\nimport Sparkle\nimport Version\n\n"
  },
  {
    "path": "LinearMouse/DefaultsKeys.swift",
    "chars": 1116,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Defaults\n\nenum MenuBarBatteryDisplayMode: String, Codable,"
  },
  {
    "path": "LinearMouse/Device/Device.swift",
    "chars": 12299,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Defaults\nimport Foundation\nimport ObservationToken\nimport "
  },
  {
    "path": "LinearMouse/Device/DeviceManager.swift",
    "chars": 15680,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport AppKit\nimport Combine\nimport Foundation\nimport os.log\nimpo"
  },
  {
    "path": "LinearMouse/Device/InputReportHandler.swift",
    "chars": 2217,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport CoreGraphics\nimport Foundation\n\ntypealias MouseButtonEmitt"
  },
  {
    "path": "LinearMouse/Device/InputReportHandlers/GenericSideButtonHandler.swift",
    "chars": 1835,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\n/// Handles side button fixes for devices that"
  },
  {
    "path": "LinearMouse/Device/InputReportHandlers/KensingtonSlimbladeHandler.swift",
    "chars": 1988,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\n/// Handles Kensington Slimblade trackball's t"
  },
  {
    "path": "LinearMouse/Device/ReceiverLogicalDeviceIdentity.swift",
    "chars": 830,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\nenum ReceiverLogicalDeviceKind: UInt8, Hashabl"
  },
  {
    "path": "LinearMouse/Device/ReceiverMonitor.swift",
    "chars": 15118,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport os.log\n\nfinal class ReceiverMonitor {\n  "
  },
  {
    "path": "LinearMouse/Device/VendorSpecific/BatteryDeviceMonitor.swift",
    "chars": 7078,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Combine\nimport Foundation\n\nfinal class BatteryDeviceMonito"
  },
  {
    "path": "LinearMouse/Device/VendorSpecific/ConnectedBatteryDeviceInventory.swift",
    "chars": 9496,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport IOKit.hid\nimport PointerKit\n\nprivate typ"
  },
  {
    "path": "LinearMouse/Device/VendorSpecific/ConnectedLogitechDeviceInventory.swift",
    "chars": 1766,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport PointerKit\n\nenum ConnectedLogitechDevice"
  },
  {
    "path": "LinearMouse/Device/VendorSpecific/Logitech/LogitechHIDPPDeviceMetadataProvider.swift",
    "chars": 104419,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport AppKit\nimport Combine\nimport Foundation\nimport IOKit.hid\ni"
  },
  {
    "path": "LinearMouse/Device/VendorSpecific/PointerDevice+VendorSpecificDeviceContext.swift",
    "chars": 130,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport PointerKit\n\nextension PointerDevice: VendorSpecificDeviceC"
  },
  {
    "path": "LinearMouse/Device/VendorSpecific/VendorSpecificDeviceMetadata.swift",
    "chars": 2345,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\nprotocol VendorSpecificDeviceContext {\n    var"
  },
  {
    "path": "LinearMouse/EventTap/EventTap.swift",
    "chars": 5170,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport ObservationToken\nimport os.log\n\nenum Eve"
  },
  {
    "path": "LinearMouse/EventTap/EventThread.swift",
    "chars": 7174,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport os.log\n\nprivate final class EventThreadR"
  },
  {
    "path": "LinearMouse/EventTap/EventType.swift",
    "chars": 476,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nclass EventType {\n    static let all: [CGEventType] = [\n        ."
  },
  {
    "path": "LinearMouse/EventTap/GlobalEventTap.swift",
    "chars": 2944,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport AppKit\nimport Foundation\nimport ObservationToken\nimport os"
  },
  {
    "path": "LinearMouse/EventTap/GlobalEventTapWatchdog.swift",
    "chars": 882,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport AppKit\nimport os.log\n\nclass GlobalEventTapWatchdog {\n    p"
  },
  {
    "path": "LinearMouse/EventTransformer/AutoScrollTransformer.swift",
    "chars": 35639,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport AppKit\nimport ApplicationServices\nimport Foundation\nimport"
  },
  {
    "path": "LinearMouse/EventTransformer/ButtonActionsTransformer.swift",
    "chars": 24521,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport AppKit\nimport DockKit\nimport Foundation\nimport GestureKit\n"
  },
  {
    "path": "LinearMouse/EventTransformer/ClickDebouncingTransformer.swift",
    "chars": 2270,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport os.log\n\nclass ClickDebouncingTransformer"
  },
  {
    "path": "LinearMouse/EventTransformer/EventTransformer.swift",
    "chars": 1525,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport LRUCache\nimport os.log\n\nprotocol EventTr"
  },
  {
    "path": "LinearMouse/EventTransformer/EventTransformerManager.swift",
    "chars": 16194,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Combine\nimport Defaults\nimport Foundation\nimport LRUCache\n"
  },
  {
    "path": "LinearMouse/EventTransformer/GestureButtonTransformer.swift",
    "chars": 10845,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport AppKit\nimport DockKit\nimport Foundation\nimport KeyKit\nimpo"
  },
  {
    "path": "LinearMouse/EventTransformer/LinearScrollingHorizontalTransformer.swift",
    "chars": 1870,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport os.log\n\nclass LinearScrollingHorizontalT"
  },
  {
    "path": "LinearMouse/EventTransformer/LinearScrollingVerticalTransformer.swift",
    "chars": 1866,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport os.log\n\nclass LinearScrollingVerticalTra"
  },
  {
    "path": "LinearMouse/EventTransformer/LogitechEventContext.swift",
    "chars": 315,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\nstruct LogitechEventContext {\n    let device: "
  },
  {
    "path": "LinearMouse/EventTransformer/ModifierActionsTransformer.swift",
    "chars": 4488,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport GestureKit\nimport KeyKit\nimport os.log\n\n"
  },
  {
    "path": "LinearMouse/EventTransformer/PointerRedirectsToScrollTransformer.swift",
    "chars": 1076,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport CoreGraphics\nimport Foundation\n\nclass PointerRedirectsToSc"
  },
  {
    "path": "LinearMouse/EventTransformer/ReverseScrollingTransformer.swift",
    "chars": 713,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\nclass ReverseScrollingTransformer: EventTransf"
  },
  {
    "path": "LinearMouse/EventTransformer/ScrollingAccelerationSpeedAdjustmentTransformer.swift",
    "chars": 2839,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport os.log\n\nclass ScrollingAccelerationSpeed"
  },
  {
    "path": "LinearMouse/EventTransformer/SmoothedScrollingEngine.swift",
    "chars": 15207,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport CoreGraphics\nimport Foundation\n\nfinal class SmoothedScroll"
  },
  {
    "path": "LinearMouse/EventTransformer/SmoothedScrollingTransformer.swift",
    "chars": 10213,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport os.log\n\nfinal class SmoothedScrollingTra"
  },
  {
    "path": "LinearMouse/EventTransformer/SwitchPrimaryAndSecondaryButtonsTransformer.swift",
    "chars": 1105,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport os.log\n\nclass SwitchPrimaryAndSecondaryB"
  },
  {
    "path": "LinearMouse/EventTransformer/UniversalBackForwardTransformer.swift",
    "chars": 4524,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport GestureKit\nimport os.log\n\nclass Universa"
  },
  {
    "path": "LinearMouse/EventView/EventView.swift",
    "chars": 831,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nclass EventView {\n    let event: CGEvent\n\n    init(_ event: CGEve"
  },
  {
    "path": "LinearMouse/EventView/MouseEventView.swift",
    "chars": 1373,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport AppKit\nimport Foundation\n\nclass MouseEventView: EventView "
  },
  {
    "path": "LinearMouse/EventView/ScrollWheelEventView.swift",
    "chars": 5516,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport os.log\nimport SceneKit\n\nclass ScrollWhee"
  },
  {
    "path": "LinearMouse/Info.plist",
    "chars": 1274,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "LinearMouse/LinearMouse-Bridging-Header.h",
    "chars": 1249,
    "preview": "//\n//  Touch-Bridging-Header.h\n//  LinearMouse\n//\n//  Created by lujjjh on 2021/8/5.\n//\n\n#include <CoreGraphics/CoreGrap"
  },
  {
    "path": "LinearMouse/LinearMouse.entitlements",
    "chars": 258,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "LinearMouse/LinearMouse.swift",
    "chars": 520,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\nenum LinearMouse {\n    static var appBundleIde"
  },
  {
    "path": "LinearMouse/Localizable.xcstrings",
    "chars": 1509103,
    "preview": "{\n  \"sourceLanguage\" : \"en\",\n  \"strings\" : {\n    \"- %@\" : {\n      \"localizations\" : {\n        \"af\" : {\n          \"string"
  },
  {
    "path": "LinearMouse/Model/AppTarget.swift",
    "chars": 155,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\nenum AppTarget: Hashable {\n    case bundle(Str"
  },
  {
    "path": "LinearMouse/Model/Configuration/Configuration.swift",
    "chars": 5239,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport AppKit\nimport Defaults\nimport Foundation\nimport JSONPatche"
  },
  {
    "path": "LinearMouse/Model/Configuration/DeviceMatcher.swift",
    "chars": 1983,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Defaults\n\nstruct DeviceMatcher: Codable, Equatable, Hashab"
  },
  {
    "path": "LinearMouse/Model/Configuration/Scheme/Buttons/AutoScroll/AutoScroll.swift",
    "chars": 2863,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\nextension Scheme.Buttons {\n    struct AutoScro"
  },
  {
    "path": "LinearMouse/Model/Configuration/Scheme/Buttons/Buttons.swift",
    "chars": 2968,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\nextension Scheme {\n    struct Buttons: Codable"
  },
  {
    "path": "LinearMouse/Model/Configuration/Scheme/Buttons/ClickDebouncing/ClickDebouncing.swift",
    "chars": 260,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nextension Scheme.Buttons {\n    struct ClickDebouncing: Codable, E"
  },
  {
    "path": "LinearMouse/Model/Configuration/Scheme/Buttons/Gesture/Gesture.swift",
    "chars": 2698,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\nextension Scheme.Buttons {\n    struct Gesture:"
  },
  {
    "path": "LinearMouse/Model/Configuration/Scheme/Buttons/Mapping/Action+Codable.swift",
    "chars": 3709,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport KeyKit\n\nextension Scheme.Buttons.Mapping"
  },
  {
    "path": "LinearMouse/Model/Configuration/Scheme/Buttons/Mapping/Action+CustomStringConvertible.swift",
    "chars": 4798,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\nextension Scheme.Buttons.Mapping.Action: Custo"
  },
  {
    "path": "LinearMouse/Model/Configuration/Scheme/Buttons/Mapping/Action+Kind.swift",
    "chars": 1524,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nextension Scheme.Buttons.Mapping.Action {\n    enum Kind: Equatabl"
  },
  {
    "path": "LinearMouse/Model/Configuration/Scheme/Buttons/Mapping/Action.swift",
    "chars": 2277,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport KeyKit\n\nextension Scheme.Buttons.Mapping"
  },
  {
    "path": "LinearMouse/Model/Configuration/Scheme/Buttons/Mapping/Mapping.swift",
    "chars": 9581,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nextension Scheme.Buttons {\n    struct Mapping: Equatable, Hashabl"
  },
  {
    "path": "LinearMouse/Model/Configuration/Scheme/If/If.swift",
    "chars": 1965,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport AppKit\nimport Foundation\n\nextension Scheme {\n    struct If"
  },
  {
    "path": "LinearMouse/Model/Configuration/Scheme/Pointer.swift",
    "chars": 2001,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\nextension Scheme {\n    struct Acceleration: Eq"
  },
  {
    "path": "LinearMouse/Model/Configuration/Scheme/Scheme.swift",
    "chars": 5513,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\n/// A scheme is a set of settings to be applie"
  },
  {
    "path": "LinearMouse/Model/Configuration/Scheme/Scrolling/Bidirectional.swift",
    "chars": 3013,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nextension Scheme.Scrolling {\n    struct Bidirectional<T: Codable "
  },
  {
    "path": "LinearMouse/Model/Configuration/Scheme/Scrolling/Distance+Mode.swift",
    "chars": 404,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nextension Scheme.Scrolling.Distance {\n    enum Mode: CaseIterable"
  },
  {
    "path": "LinearMouse/Model/Configuration/Scheme/Scrolling/Distance.swift",
    "chars": 3610,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\nextension Scheme.Scrolling {\n    enum Distance"
  },
  {
    "path": "LinearMouse/Model/Configuration/Scheme/Scrolling/Modifiers+Kind.swift",
    "chars": 1281,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nextension Scheme.Scrolling.Modifiers.Action {\n    enum Kind: Case"
  },
  {
    "path": "LinearMouse/Model/Configuration/Scheme/Scrolling/Modifiers.swift",
    "chars": 3071,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\nextension Scheme.Scrolling {\n    struct Modifi"
  },
  {
    "path": "LinearMouse/Model/Configuration/Scheme/Scrolling/Scrolling.swift",
    "chars": 2424,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\nextension Scheme {\n    struct Scrolling: Codab"
  },
  {
    "path": "LinearMouse/Model/Configuration/Scheme/Scrolling/Smoothed.swift",
    "chars": 8655,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport SwiftUI\n\nextension Scheme.Scrolling {\n  "
  },
  {
    "path": "LinearMouse/Model/DeviceModel.swift",
    "chars": 3544,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Combine\nimport Foundation\nimport SwiftUI\n\nclass DeviceMode"
  },
  {
    "path": "LinearMouse/ModifierKeys.swift",
    "chars": 702,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\nstruct ModifierKeyAction: Codable {\n    var ty"
  },
  {
    "path": "LinearMouse/ScreenManager/ScreenManager.swift",
    "chars": 5277,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport AppKit\nimport Combine\nimport CoreGraphics\nimport os.log\n\nc"
  },
  {
    "path": "LinearMouse/State/ConfigurationState.swift",
    "chars": 9488,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport AppKit\nimport Combine\nimport Darwin\nimport Defaults\nimport"
  },
  {
    "path": "LinearMouse/State/DeviceState.swift",
    "chars": 2686,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Combine\nimport Defaults\nimport SwiftUI\n\nextension Defaults"
  },
  {
    "path": "LinearMouse/State/ModifierState.swift",
    "chars": 1738,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport AppKit\nimport Foundation\n\nfinal class ModifierState {\n    "
  },
  {
    "path": "LinearMouse/State/SchemeState.swift",
    "chars": 6329,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Combine\nimport Foundation\n\nclass SchemeState: ObservableOb"
  },
  {
    "path": "LinearMouse/State/SettingsState.swift",
    "chars": 3037,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport CoreGraphics\nimport Foundation\nimport SwiftUI\n\nclass Setti"
  },
  {
    "path": "LinearMouse/UI/AccessibilityPermissionView.swift",
    "chars": 1442,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport os.log\nimport SwiftUI\n\nstruct AccessibilityPermissionView:"
  },
  {
    "path": "LinearMouse/UI/AccessibilityPermissionWindow.swift",
    "chars": 1332,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport SwiftUI\n\nclass AccessibilityPermissionWi"
  },
  {
    "path": "LinearMouse/UI/Base.lproj/Main.storyboard",
    "chars": 58407,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB\" version=\"3.0\" t"
  },
  {
    "path": "LinearMouse/UI/ButtonStyle/SecondaryButtonStyle.swift",
    "chars": 658,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct SecondaryButtonStyle: ButtonStyle {\n    fu"
  },
  {
    "path": "LinearMouse/UI/ButtonsSettings/AutoScrollSection/AutoScrollSection.swift",
    "chars": 4255,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct AutoScrollSection: View {\n    @ObservedObj"
  },
  {
    "path": "LinearMouse/UI/ButtonsSettings/ButtonMappingsSection/ButtonMapping.swift",
    "chars": 3831,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct ButtonMappingListItem: View {\n    @Binding"
  },
  {
    "path": "LinearMouse/UI/ButtonsSettings/ButtonMappingsSection/ButtonMappingAction/ButtonMappingAction+Binding.swift",
    "chars": 3050,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport KeyKit\nimport SwiftUI\n\nextension Binding where Value == Sc"
  },
  {
    "path": "LinearMouse/UI/ButtonsSettings/ButtonMappingsSection/ButtonMappingAction/ButtonMappingAction.swift",
    "chars": 752,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct ButtonMappingAction: View {\n    @Binding v"
  },
  {
    "path": "LinearMouse/UI/ButtonsSettings/ButtonMappingsSection/ButtonMappingAction/ButtonMappingActionKeyPress.swift",
    "chars": 3616,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport KeyKit\nimport ObservationToken\nimport SwiftUI\n\nstruct Butt"
  },
  {
    "path": "LinearMouse/UI/ButtonsSettings/ButtonMappingsSection/ButtonMappingAction/ButtonMappingActionPicker.swift",
    "chars": 3603,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct ButtonMappingActionPicker: View, Equatable"
  },
  {
    "path": "LinearMouse/UI/ButtonsSettings/ButtonMappingsSection/ButtonMappingAction/ButtonMappingActionRun.swift",
    "chars": 283,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct ButtonMappingActionRun: View {\n    @Bindin"
  },
  {
    "path": "LinearMouse/UI/ButtonsSettings/ButtonMappingsSection/ButtonMappingAction/ButtonMappingActionScroll.swift",
    "chars": 1707,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct ButtonMappingActionScroll: View {\n    @Bin"
  },
  {
    "path": "LinearMouse/UI/ButtonsSettings/ButtonMappingsSection/ButtonMappingAction/ScrollingDistance+Binding.swift",
    "chars": 1522,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport SwiftUI\n\nextension Binding where Value ="
  },
  {
    "path": "LinearMouse/UI/ButtonsSettings/ButtonMappingsSection/ButtonMappingButtonRecorder.swift",
    "chars": 5378,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Combine\nimport ObservationToken\nimport SwiftUI\n\nstruct But"
  },
  {
    "path": "LinearMouse/UI/ButtonsSettings/ButtonMappingsSection/ButtonMappingEditSheet.swift",
    "chars": 4364,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct ButtonMappingEditSheet: View {\n    @Bindin"
  },
  {
    "path": "LinearMouse/UI/ButtonsSettings/ButtonMappingsSection/ButtonMappingsSection.swift",
    "chars": 2234,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct ButtonMappingsSection: View {\n    @Observe"
  },
  {
    "path": "LinearMouse/UI/ButtonsSettings/ButtonsSettings.swift",
    "chars": 507,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct ButtonsSettings: View {\n    var body: some"
  },
  {
    "path": "LinearMouse/UI/ButtonsSettings/ButtonsSettingsState.swift",
    "chars": 11246,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Combine\nimport Foundation\nimport PublishedObject\nimport Sw"
  },
  {
    "path": "LinearMouse/UI/ButtonsSettings/ClickDebouncingSection.swift",
    "chars": 2523,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct ClickDebouncingSection: View {\n    @Observ"
  },
  {
    "path": "LinearMouse/UI/ButtonsSettings/GestureButtonSection/GestureActionPicker.swift",
    "chars": 809,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct GestureActionPicker: View {\n    typealias "
  },
  {
    "path": "LinearMouse/UI/ButtonsSettings/GestureButtonSection/GestureButtonSection.swift",
    "chars": 3142,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct GestureButtonSection: View {\n    @Observed"
  },
  {
    "path": "LinearMouse/UI/ButtonsSettings/SwitchPrimaryAndSecondaryButtonsSection.swift",
    "chars": 435,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct SwitchPrimaryAndSecondaryButtonsSection: V"
  },
  {
    "path": "LinearMouse/UI/ButtonsSettings/UniversalBackForwardSection.swift",
    "chars": 651,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct UniversalBackForwardSection: View {\n    @O"
  },
  {
    "path": "LinearMouse/UI/CheckForUpdatesView.swift",
    "chars": 2947,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Combine\nimport Defaults\nimport Sparkle\nimport SwiftUI\n\nfin"
  },
  {
    "path": "LinearMouse/UI/DetailView/DetailView.swift",
    "chars": 701,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct DetailView<T: View>: View {\n    var scheme"
  },
  {
    "path": "LinearMouse/UI/Extensions/NSWindow.swift",
    "chars": 223,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nextension NSWindow {\n    func bringToFront() {\n  "
  },
  {
    "path": "LinearMouse/UI/Extensions/View.swift",
    "chars": 3406,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\n// https://gist.github.com/marcprux/afd2f80baa5b6"
  },
  {
    "path": "LinearMouse/UI/GeneralSettings/ConfigurationSection.swift",
    "chars": 605,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct ConfigurationSection: View {\n    @Observed"
  },
  {
    "path": "LinearMouse/UI/GeneralSettings/GeneralSettings.swift",
    "chars": 5659,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Defaults\nimport LaunchAtLogin\nimport SwiftUI\n\nstruct Gener"
  },
  {
    "path": "LinearMouse/UI/GeneralSettings/LoggingSection.swift",
    "chars": 3387,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Defaults\nimport OSLog\nimport SwiftUI\n\nstruct LoggingSectio"
  },
  {
    "path": "LinearMouse/UI/Header/AppIndicator/AppIndicator.swift",
    "chars": 722,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct AppIndicator: View {\n    @State private va"
  },
  {
    "path": "LinearMouse/UI/Header/AppIndicator/AppPickerSheet/AppPicker.swift",
    "chars": 5214,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct AppPicker: View {\n    @ObservedObject var "
  },
  {
    "path": "LinearMouse/UI/Header/AppIndicator/AppPickerSheet/AppPickerSheet.swift",
    "chars": 2744,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct AppPickerSheet: View {\n    @Binding var is"
  },
  {
    "path": "LinearMouse/UI/Header/AppIndicator/AppPickerSheet/AppPickerState.swift",
    "chars": 3331,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport AppKit\nimport Combine\nimport Foundation\n\nclass AppPickerSt"
  },
  {
    "path": "LinearMouse/UI/Header/DeviceIndicator/DeviceIndicator.swift",
    "chars": 703,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct DeviceIndicator: View {\n    @ObservedObjec"
  },
  {
    "path": "LinearMouse/UI/Header/DeviceIndicator/DeviceIndicatorState.swift",
    "chars": 1630,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Combine\nimport SwiftUI\n\nclass DeviceIndicatorState: Observ"
  },
  {
    "path": "LinearMouse/UI/Header/DeviceIndicator/DevicePickerSheet/DeviceButtonStyle.swift",
    "chars": 834,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct DeviceButtonStyle: ButtonStyle {\n    var i"
  },
  {
    "path": "LinearMouse/UI/Header/DeviceIndicator/DevicePickerSheet/DevicePicker.swift",
    "chars": 1010,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct DevicePicker: View {\n    @ObservedObject v"
  },
  {
    "path": "LinearMouse/UI/Header/DeviceIndicator/DevicePickerSheet/DevicePickerBatteryCoordinator.swift",
    "chars": 1378,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Combine\nimport Foundation\nimport PointerKit\n\nfinal class D"
  },
  {
    "path": "LinearMouse/UI/Header/DeviceIndicator/DevicePickerSheet/DevicePickerSection.swift",
    "chars": 1209,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct DevicePickerSection: View {\n    @Binding v"
  },
  {
    "path": "LinearMouse/UI/Header/DeviceIndicator/DevicePickerSheet/DevicePickerSectionItem.swift",
    "chars": 5757,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct DevicePickerSectionItem: View {\n    @Obser"
  },
  {
    "path": "LinearMouse/UI/Header/DeviceIndicator/DevicePickerSheet/DevicePickerSheet.swift",
    "chars": 5100,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Defaults\nimport SwiftUI\n\nstruct DevicePickerSheet: View {\n"
  },
  {
    "path": "LinearMouse/UI/Header/DeviceIndicator/DevicePickerSheet/DevicePickerState.swift",
    "chars": 1792,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Combine\nimport SwiftUI\n\nclass DevicePickerState: Observabl"
  },
  {
    "path": "LinearMouse/UI/Header/DisplayIndicator/DisplayIndicator.swift",
    "chars": 750,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct DisplayIndicator: View {\n    @State privat"
  },
  {
    "path": "LinearMouse/UI/Header/DisplayIndicator/DisplayPickerSheet/DisplayPicker.swift",
    "chars": 492,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct DisplayPicker: View {\n    @ObservedObject "
  },
  {
    "path": "LinearMouse/UI/Header/DisplayIndicator/DisplayPickerSheet/DisplayPickerSheet.swift",
    "chars": 2737,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct DisplayPickerSheet: View {\n    @Binding va"
  },
  {
    "path": "LinearMouse/UI/Header/DisplayIndicator/DisplayPickerSheet/DisplayPickerState.swift",
    "chars": 457,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport AppKit\nimport Combine\nimport Foundation\n\nclass DisplayPick"
  },
  {
    "path": "LinearMouse/UI/Header/SchemeIndicator.swift",
    "chars": 333,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct SchemeIndicator: View {\n    var body: some"
  },
  {
    "path": "LinearMouse/UI/HelpButton/HelpButton.swift",
    "chars": 782,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct HelpButton: NSViewRepresentable {\n    let "
  },
  {
    "path": "LinearMouse/UI/HyperLink.swift",
    "chars": 714,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct HyperLink<Content: View>: View {\n    var u"
  },
  {
    "path": "LinearMouse/UI/PointerSettings/PointerSettings.swift",
    "chars": 7855,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct PointerSettings: View {\n    @ObservedObjec"
  },
  {
    "path": "LinearMouse/UI/PointerSettings/PointerSettingsState.swift",
    "chars": 3172,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Combine\nimport Foundation\nimport PublishedObject\n\nclass Po"
  },
  {
    "path": "LinearMouse/UI/ScrollingSettings/Header.swift",
    "chars": 811,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nextension ScrollingSettings {\n    struct Header: "
  },
  {
    "path": "LinearMouse/UI/ScrollingSettings/ModifierKeysSection/ModifierAction+Binding.swift",
    "chars": 1887,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport SwiftUI\n\nextension Binding where Value ="
  },
  {
    "path": "LinearMouse/UI/ScrollingSettings/ModifierKeysSection/ModifierKeyActionPicker.swift",
    "chars": 1288,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport SwiftUI\n\nextension ScrollingSettings.Mod"
  },
  {
    "path": "LinearMouse/UI/ScrollingSettings/ModifierKeysSection/ModifierKeysSection.swift",
    "chars": 753,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nextension ScrollingSettings {\n    struct Modifier"
  },
  {
    "path": "LinearMouse/UI/ScrollingSettings/ReverseScrollingSection.swift",
    "chars": 721,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nextension ScrollingSettings {\n    struct ReverseS"
  },
  {
    "path": "LinearMouse/UI/ScrollingSettings/ScrollingModeSection.swift",
    "chars": 5264,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nextension ScrollingSettings {\n    struct Scrollin"
  },
  {
    "path": "LinearMouse/UI/ScrollingSettings/ScrollingSettings.swift",
    "chars": 471,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct ScrollingSettings: View {\n    var body: so"
  },
  {
    "path": "LinearMouse/UI/ScrollingSettings/ScrollingSettingsState.swift",
    "chars": 9933,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Combine\nimport Foundation\nimport PublishedObject\nimport Sw"
  },
  {
    "path": "LinearMouse/UI/ScrollingSettings/SmoothedScrollingSection.swift",
    "chars": 17709,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport AppKit\nimport SwiftUI\n\nextension ScrollingSettings {\n    s"
  },
  {
    "path": "LinearMouse/UI/Settings.swift",
    "chars": 976,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Defaults\nimport SwiftUI\n\nstruct Settings: View {\n    @Stat"
  },
  {
    "path": "LinearMouse/UI/SettingsWindowController.swift",
    "chars": 20492,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Cocoa\nimport Combine\nimport Defaults\nimport SwiftUI\n\nclass"
  },
  {
    "path": "LinearMouse/UI/Sidebar/Sidebar.swift",
    "chars": 1071,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct Sidebar: View {\n    @ObservedObject var st"
  },
  {
    "path": "LinearMouse/UI/Sidebar/SidebarItem.swift",
    "chars": 1292,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct SidebarItem: View {\n    @ObservedObject va"
  },
  {
    "path": "LinearMouse/UI/StatusItem.swift",
    "chars": 11417,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Combine\nimport Defaults\nimport LaunchAtLogin\nimport SwiftU"
  },
  {
    "path": "LinearMouse/UI/ViewModifiers/FormViewModifier.swift",
    "chars": 2026,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct FormViewModifier: ViewModifier {\n    func "
  },
  {
    "path": "LinearMouse/UI/VisualEffectView.swift",
    "chars": 727,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport SwiftUI\n\nstruct VisualEffectView: NSViewRepresentable {\n  "
  },
  {
    "path": "LinearMouse/UI/zh-Hant-HK.lproj/Main.strings",
    "chars": 14519,
    "preview": "/* Class = \"NSMenu\"; title = \"Find\"; ObjectID = \"1b7-l0-nxx\"; */\n\"1b7-l0-nxx.title\" = \"Find\";\n\n/* Class = \"NSMenuItem\"; "
  },
  {
    "path": "LinearMouse/Utilities/Application.swift",
    "chars": 725,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport AppKit\n\nenum Application {\n    static func restart() {\n   "
  },
  {
    "path": "LinearMouse/Utilities/ApplicationBundle.swift",
    "chars": 1177,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport AppKit\nimport Foundation\n\nstruct InstalledApp: Identifiabl"
  },
  {
    "path": "LinearMouse/Utilities/CGEvent+LinearMouseSynthetic.swift",
    "chars": 4375,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport CoreGraphics\nimport Foundation\n\nstruct LogitechControlIden"
  },
  {
    "path": "LinearMouse/Utilities/Codable/Clamp.swift",
    "chars": 1711,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nprotocol ClampRange {\n    associatedtype Value: Codable\n    assoc"
  },
  {
    "path": "LinearMouse/Utilities/Codable/HexRepresentation.swift",
    "chars": 2244,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\n@propertyWrapper\nstruct HexRepresentation<Valu"
  },
  {
    "path": "LinearMouse/Utilities/Codable/ImplicitOptional.swift",
    "chars": 1851,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nprotocol ImplicitInitable {\n    init()\n}\n\n@propertyWrapper\nstruct"
  },
  {
    "path": "LinearMouse/Utilities/Codable/SingleValueOrArray.swift",
    "chars": 1936,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\n// SwiftFormat will eat the empty lines between the file header a"
  },
  {
    "path": "LinearMouse/Utilities/Codable/Unsettable.swift",
    "chars": 1023,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\nenum Unsettable<T: Codable & Equatable>: Equat"
  },
  {
    "path": "LinearMouse/Utilities/CustomDecodingError.swift",
    "chars": 1051,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\nstruct CustomDecodingError: Error {\n    var co"
  },
  {
    "path": "LinearMouse/Utilities/Extensions.swift",
    "chars": 5099,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport AppKit\nimport Foundation\nimport LRUCache\nimport SwiftUI\n\np"
  },
  {
    "path": "LinearMouse/Utilities/KeyboardSettingsSnapshot.swift",
    "chars": 1082,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport AppKit\nimport Foundation\n\n/// Main-thread snapshot of keyb"
  },
  {
    "path": "LinearMouse/Utilities/Notifier.swift",
    "chars": 2030,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\nimport UserNotifications\n\nclass Notifier: NSObj"
  },
  {
    "path": "LinearMouse/Utilities/Process.h",
    "chars": 238,
    "preview": "#ifndef LINEARMOUSE_PROCESS_H\n#define LINEARMOUSE_PROCESS_H\n\n#include <sys/sysctl.h>\n\ntypedef struct ProcessInfo {\n    p"
  },
  {
    "path": "LinearMouse/Utilities/Process.m",
    "chars": 402,
    "preview": "#include \"Process.h\"\n\nProcessInfo getProcessInfo(pid_t pid) {\n    ProcessInfo pi = { 0 };\n\n    struct kinfo_proc info;\n "
  },
  {
    "path": "LinearMouse/Utilities/ProcessEnvironment.swift",
    "chars": 595,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\nenum ProcessEnvironment {\n    static var isPre"
  },
  {
    "path": "LinearMouse/Utilities/WeakRef.swift",
    "chars": 640,
    "preview": "// MIT License\n// Copyright (c) 2021-2026 LinearMouse\n\nimport Foundation\n\nclass WeakRef<T: AnyObject> {\n    weak var val"
  }
]

// ... and 108 more files (download for full content)

About this extraction

This page contains the full source code of the lujjjh/LinearMouse GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 308 files (2.6 MB), approximately 698.7k tokens, and a symbol index with 179 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!