Full Code of socsieng/sendkeys for AI

main c5c8dd98ee8d cached
87 files
213.7 KB
57.1k tokens
2 symbols
1 requests
Download .txt
Showing preview only (235K chars total). Download the full file or copy to clipboard to get everything.
Repository: socsieng/sendkeys
Branch: main
Commit: c5c8dd98ee8d
Files: 87
Total size: 213.7 KB

Directory structure:
gitextract_cwwrie3z/

├── .editorconfig
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yaml
│   │   └── feature_request.yaml
│   └── workflows/
│       └── build.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.yml
├── .swift-format
├── .vscode/
│   ├── extensions.json
│   ├── launch.json
│   ├── settings.json
│   └── tasks.json
├── CHANGELOG.md
├── Formula/
│   └── sendkeys_template.rb
├── LICENSE
├── Makefile
├── Package.resolved
├── Package.swift
├── README.md
├── Sources/
│   ├── SendKeysLib/
│   │   ├── Animator.swift
│   │   ├── AppActivator.swift
│   │   ├── AppLister.swift
│   │   ├── Bridge.swift
│   │   ├── Commands/
│   │   │   ├── Command.swift
│   │   │   ├── CommandExecutor.swift
│   │   │   ├── CommandFactory.swift
│   │   │   ├── CommandsIterator.swift
│   │   │   ├── CommandsProcessor.swift
│   │   │   ├── ContinuationCommand.swift
│   │   │   ├── DefaultCommand.swift
│   │   │   ├── KeyDownCommand.swift
│   │   │   ├── KeyPressCommand.swift
│   │   │   ├── KeyUpCommand.swift
│   │   │   ├── MouseClickCommand.swift
│   │   │   ├── MouseDownCommand.swift
│   │   │   ├── MouseDragCommand.swift
│   │   │   ├── MouseFocusCommand.swift
│   │   │   ├── MouseMoveCommand.swift
│   │   │   ├── MousePathCommand.swift
│   │   │   ├── MouseScrollCommand.swift
│   │   │   ├── MouseUpCommand.swift
│   │   │   ├── NewlineCommand.swift
│   │   │   ├── PauseCommand.swift
│   │   │   └── StickyPauseCommand.swift
│   │   ├── Configuration/
│   │   │   ├── AllConfiguration.swift
│   │   │   ├── ConfigLoader.swift
│   │   │   ├── MousePositionConfig.swift
│   │   │   ├── SendConfig.swift
│   │   │   └── TransformerConfig.swift
│   │   ├── KeyCodes.swift
│   │   ├── KeyMappings.swift
│   │   ├── KeyPresser.swift
│   │   ├── MouseController.swift
│   │   ├── MouseEventProcessor.swift
│   │   ├── MousePosition.swift
│   │   ├── Path/
│   │   │   ├── Extensions.swift
│   │   │   ├── PathCommands.swift
│   │   │   ├── PathData.swift
│   │   │   └── PathParser.swift
│   │   ├── RuntimeError.swift
│   │   ├── SendKeysCli.swift
│   │   ├── Sender.swift
│   │   ├── Sleeper.swift
│   │   ├── TerminationListener.swift
│   │   ├── Transformer.swift
│   │   └── Utilities.swift
│   └── sendkeys/
│       └── main.swift
├── Tests/
│   ├── LinuxMain.swift
│   ├── SendKeysTests/
│   │   ├── CommandIteratorTests.swift
│   │   ├── CommandsProcessorTests.swift
│   │   ├── KeyPresserTests.swift
│   │   ├── PathDataTests.swift
│   │   ├── PathParserTests.swift
│   │   ├── TransformerTests.swift
│   │   ├── XCTestManifests.swift
│   │   └── sendkeysTests.swift
│   └── keys.txt
├── examples/
│   ├── .sendkeysrc.yml
│   └── node.js
├── scripts/
│   ├── bottle.sh
│   ├── code-coverage.sh
│   ├── format.sh
│   ├── install-pre-commit.sh
│   ├── pre-commit.sh
│   ├── update-version.sh
│   └── verify-output.sh
└── version.txt

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

================================================
FILE: .editorconfig
================================================
root = true

[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.swift]
indent_style = space
indent_size = 4


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yaml
================================================
name: Bug Report
description: Report a bug you've found
title: "[bug]: "
labels: [bug]
body:
  - type: markdown
    attributes:
      value: |
        Thanks for taking the time to file a bug.
  - type: textarea
    attributes:
      label: What went wrong?
      description: Describe the issue including reproduction steps.
      placeholder: |
        Executing `sendkeys ...`

        Fails with error: ...
    validations:
      required: true
  - type: textarea
    attributes:
      label: "Expected result:"
      description: Describe what you expected to happen.
  - type: textarea
    attributes:
      label: "Actual result:"
      description: Describe what actually happened.
  - type: textarea
    attributes:
      label: "Other information:"
      description: |
        Include any other useful information that would help to reproduce/fix the issue.
      value: |
        - Sendkeys version: ...
        - Operating system: ...
        - Processor (e.g. Intel, M1): ...


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yaml
================================================
name: Feature Request
description: Suggest an idea for this project
title: "[feature]: "
labels: [enhancement]
body:
  - type: markdown
    attributes:
      value: |
        Thank you for using sendkeys.
  - type: textarea
    attributes:
      label: What feature would you like to see in sendkeys?
      description: Describe the feature and the problem that you would like to see solved.
      placeholder: |
        I would like to be able to ...
        
        So that I can ...
    validations:
      required: true
  - type: textarea
    attributes:
      label: "Other information:"
      description: |
        Include any other information that may be useful for prioritizing the feature request. Examples may include your own proposed solution, other alternatives, references to similar features in other tools, or screenshots.


================================================
FILE: .github/workflows/build.yml
================================================
name: build

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build:
    runs-on: macos-14

    env:
      DEVELOPER_DIR: /Applications/Xcode_16.0.app/Contents/Developer

    name: Build

    steps:
      - name: checkout
        uses: actions/checkout@v4

      - uses: actions/cache@v4
        with:
          path: .build
          key: ${{ runner.os }}-xcode-${{ hashFiles('**/Package.resolved') }}
          restore-keys: |
            ${{ runner.os }}-xcode-

      - name: build
        run: |
          ls -n /Applications/ | grep Xcode*
          make build

      - name: test
        run: |
          ./scripts/code-coverage.sh

  create_release:
    needs:
      - build

    runs-on: macos-14
    if: github.ref == 'refs/heads/main'

    name: Create release

    outputs:
      release_created: ${{ steps.release.outputs.release_created }}
      tag_name: ${{ steps.release.outputs.tag_name }}
      sha: ${{ steps.release.outputs.sha }}
      upload_url: ${{ steps.release.outputs.upload_url }}

    steps:
      - id: release
        uses: google-github-actions/release-please-action@v3
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          release-type: simple
          package-name: sendkeys
          changelog-types: |
            [
              {"type":"feat","section":"Features","hidden":false},
              {"type":"fix","section":"Bug Fixes","hidden":false},
              {"type":"docs","section":"Documentation","hidden":false},
              {"type":"misc","section":"Miscellaneous","hidden":false}
            ]

  create_bottle:
    needs:
      - create_release

    runs-on: macos-14
    if: ${{ needs.create_release.outputs.release_created }}

    name: Create bottle

    outputs:
      sha: ${{ steps.bottle.outputs.sha }}
      root_url: ${{ steps.bottle.outputs.root_url }}

    steps:
      - uses: actions/checkout@v4

      - id: bottle
        name: Create bottle
        run: |
          ./scripts/update-version.sh ${{ needs.create_release.outputs.tag_name }}
          ./scripts/bottle.sh ${{ needs.create_release.outputs.tag_name }}

      - name: Upload bottle big_sur
        if: ${{ needs.create_release.outputs.release_created }}
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ needs.create_release.outputs.upload_url }}
          asset_path: ./${{ steps.bottle.outputs.big_sur }}
          asset_name: ${{ steps.bottle.outputs.big_sur }}
          asset_content_type: application/gzip

      - name: Upload bottle arm64_big_sur
        if: ${{ needs.create_release.outputs.release_created }}
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ needs.create_release.outputs.upload_url }}
          asset_path: ./${{ steps.bottle.outputs.arm64_big_sur }}
          asset_name: ${{ steps.bottle.outputs.arm64_big_sur }}
          asset_content_type: application/gzip

  homebrew:
    needs:
      - create_release
      - create_bottle

    runs-on: macos-14
    if: ${{ needs.create_release.outputs.release_created }}

    name: Update homebrew formula

    steps:
      - uses: actions/checkout@v4

      - name: Update homebrew formula
        run: |
          git config user.name github-actions[bot]
          git config user.email socsieng-github-actions[bot]@users.noreply.github.com
          git clone "https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/socsieng/homebrew-tap.git"
          cd homebrew-tap

          git checkout -B main
          formula='Formula/sendkeys.rb'
          version=`echo '${{ needs.create_release.outputs.tag_name }}' | sed -E 's/^v//g'`
          revision='${{ needs.create_release.outputs.sha }}'
          sed_root_url=`echo '${{ needs.create_bottle.outputs.root_url }}' | sed 's/\\//\\\\\//g'`
          sha='${{ needs.create_bottle.outputs.sha }}'

          sed -E -i "" "s/tag: \"[^\"]+\"/tag: \"v$version\"/g" $formula
          sed -E -i "" "s/revision: \"[^\"]+\"/revision: \"$revision\"/g" $formula
          sed -E -i "" "s/version \"[^\"]+\"/version \"$version\"/g" $formula
          sed -E -i "" "s/root_url \"[^\"]+\"/root_url \"$sed_root_url\"/g" $formula
          sed -E -i "" "s/sha256 cellar: :any_skip_relocation, arm64_big_sur: \"[^\"]+\"/sha256 cellar: :any_skip_relocation, arm64_big_sur: \"$sha\"/g" $formula
          sed -E -i "" "s/sha256 cellar: :any_skip_relocation, big_sur:       \"[^\"]+\"/sha256 cellar: :any_skip_relocation, big_sur:       \"$sha\"/g" $formula

          git commit -am "chore: update sendkeys to ${{ needs.create_release.outputs.tag_name }}"
          git push origin main


================================================
FILE: .gitignore
================================================
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/

.swiftpm/

*.bak

Formula/sendkeys.rb
*.tar.gz

/.output


================================================
FILE: .prettierignore
================================================
node_modules/
.build/

CHANGELOG.md


================================================
FILE: .prettierrc.yml
================================================
arrowParens: avoid
printWidth: 120
quoteProps: consistent
semi: true
singleQuote: true
tabWidth: 2
trailingComma: all
overrides:
  - files: '*.md'
    options:
      parser: markdown
      proseWrap: always


================================================
FILE: .swift-format
================================================
{
  "fileScopedDeclarationPrivacy": {
    "accessLevel": "private"
  },
  "indentation": {
    "spaces": 4
  },
  "indentConditionalCompilationBlocks": true,
  "indentSwitchCaseLabels": false,
  "lineBreakAroundMultilineExpressionChainComponents": false,
  "lineBreakBeforeControlFlowKeywords": false,
  "lineBreakBeforeEachArgument": false,
  "lineBreakBeforeEachGenericRequirement": false,
  "lineLength": 120,
  "maximumBlankLines": 1,
  "prioritizeKeepingFunctionOutputTogether": false,
  "respectsExistingLineBreaks": true,
  "rules": {
    "AllPublicDeclarationsHaveDocumentation": false,
    "AlwaysUseLowerCamelCase": true,
    "AmbiguousTrailingClosureOverload": true,
    "BeginDocumentationCommentWithOneLineSummary": true,
    "DoNotUseSemicolons": true,
    "DontRepeatTypeInStaticProperties": true,
    "FileScopedDeclarationPrivacy": true,
    "FullyIndirectEnum": true,
    "GroupNumericLiterals": true,
    "IdentifiersMustBeASCII": true,
    "NeverForceUnwrap": false,
    "NeverUseForceTry": false,
    "NeverUseImplicitlyUnwrappedOptionals": false,
    "NoAccessLevelOnExtensionDeclaration": true,
    "NoBlockComments": true,
    "NoCasesWithOnlyFallthrough": true,
    "NoEmptyTrailingClosureParentheses": true,
    "NoLabelsInCasePatterns": true,
    "NoLeadingUnderscores": false,
    "NoParensAroundConditions": true,
    "NoVoidReturnOnFunctionSignature": true,
    "OneCasePerLine": true,
    "OneVariableDeclarationPerLine": true,
    "OnlyOneTrailingClosureArgument": true,
    "OrderedImports": true,
    "ReturnVoidInsteadOfEmptyTuple": true,
    "UseLetInEveryBoundCaseVariable": true,
    "UseShorthandTypeNames": true,
    "UseSingleLinePropertyGetter": true,
    "UseSynthesizedInitializer": true,
    "UseTripleSlashForDocumentationComments": true,
    "ValidateDocumentationComments": false
  },
  "tabWidth": 8,
  "version": 1
}


================================================
FILE: .vscode/extensions.json
================================================
{
  "recommendations": ["vknabel.vscode-apple-swift-format", "sswg.swift-lang", "editorconfig.editorconfig"]
}


================================================
FILE: .vscode/launch.json
================================================
{
  "configurations": [
    {
      "type": "lldb",
      "request": "launch",
      "sourceLanguages": ["swift"],
      "name": "Debug sendkeys",
      "program": "${workspaceFolder:sendkeys}/.build/debug/sendkeys",
      "args": [],
      "cwd": "${workspaceFolder:sendkeys}",
      "preLaunchTask": "swift: Build Debug sendkeys"
    },
    {
      "type": "lldb",
      "request": "launch",
      "sourceLanguages": ["swift"],
      "name": "Release sendkeys",
      "program": "${workspaceFolder:sendkeys}/.build/release/sendkeys",
      "args": [],
      "cwd": "${workspaceFolder:sendkeys}",
      "preLaunchTask": "swift: Build Release sendkeys"
    }
  ]
}


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


================================================
FILE: .vscode/tasks.json
================================================
{
  // See https://go.microsoft.com/fwlink/?LinkId=733558
  // for the documentation about the tasks.json format
  "version": "2.0.0",
  "tasks": [
    {
      "label": "build",
      "type": "shell",
      "command": "swift build"
    }
  ]
}


================================================
FILE: CHANGELOG.md
================================================
# Changelog

## [4.3.1](https://github.com/socsieng/sendkeys/compare/v4.3.0...v4.3.1) (2024-12-14)


### Bug Fixes

* add esc alias for escape key ([ff88df1](https://github.com/socsieng/sendkeys/commit/ff88df1022633e3ab4f7e85a05801ac607024872)), closes [#93](https://github.com/socsieng/sendkeys/issues/93)

## [4.3.0](https://github.com/socsieng/sendkeys/compare/v4.2.0...v4.3.0) (2024-10-28)


### Features

* add option to specify arbitrary configuration file ([61133bf](https://github.com/socsieng/sendkeys/commit/61133bf7ec8089d6f3e631a69f35a198cc8c644b))
* add support for custom key mappings in configuration file ([cc21cce](https://github.com/socsieng/sendkeys/commit/cc21ccee8b10109c74735a8458f8f7161515992d))
* add support for reading configuration from home directory ([01908f1](https://github.com/socsieng/sendkeys/commit/01908f11f3b85a551f643b2f679f1e595afbf8e3))

## [4.2.0](https://github.com/socsieng/sendkeys/compare/v4.1.2...v4.2.0) (2024-10-26)


### Features

* add support for alternate keyboard layouts ([7db41a8](https://github.com/socsieng/sendkeys/commit/7db41a829d1f403dc77206f6c737db11c2bb064e))

## [4.1.2](https://github.com/socsieng/sendkeys/compare/v4.1.1...v4.1.2) (2024-10-24)


### Bug Fixes

* add support for function modifier key ([db36c92](https://github.com/socsieng/sendkeys/commit/db36c9200f2bb982c2e0077df42436b3f7a1086e))
* **build:** restore arm64 binary in homebrew formula ([e38451a](https://github.com/socsieng/sendkeys/commit/e38451aad1f9a2b8c8d4ab5afb25c9de26147d8f))

## [4.1.1](https://github.com/socsieng/sendkeys/compare/v4.1.0...v4.1.1) (2024-10-22)


### Bug Fixes

* **build:** fix broken build ([234b441](https://github.com/socsieng/sendkeys/commit/234b441a210bf038b6cfcd60e9ac691d9dcc0d6c))

## [4.1.0](https://github.com/socsieng/sendkeys/compare/v4.0.4...v4.1.0) (2024-10-22)


### Features

* add argument to terminate execution ([1b8c1f1](https://github.com/socsieng/sendkeys/commit/1b8c1f1aa2ceb9ae666aaa74142df150d7a85b67)), closes [#79](https://github.com/socsieng/sendkeys/issues/79)


### Bug Fixes

* **build:** update macos runners ([2af43cd](https://github.com/socsieng/sendkeys/commit/2af43cda3362757dc1fb2404bd456ae34292e955))
* update package versions ([b1e1319](https://github.com/socsieng/sendkeys/commit/b1e1319259cef524e5c83632eabecdbcd353b0bb))

## [4.0.4](https://github.com/socsieng/sendkeys/compare/v4.0.3...v4.0.4) (2023-10-07)


### Bug Fixes

* **build:** add cache busting parameter ([c2a4f59](https://github.com/socsieng/sendkeys/commit/c2a4f598f0677a0dd490339eddbfaf22083e5b30))

## [4.0.3](https://github.com/socsieng/sendkeys/compare/v4.0.2...v4.0.3) (2023-10-07)


### Bug Fixes

* **build:** ignore errors if archive does not exist ([7f2fc4b](https://github.com/socsieng/sendkeys/commit/7f2fc4b5cd5c865228f5455892d7f1cabdad2612))

## [4.0.2](https://github.com/socsieng/sendkeys/compare/v4.0.1...v4.0.2) (2023-10-07)


### Bug Fixes

* **build:** clean up archives before bottling ([408db73](https://github.com/socsieng/sendkeys/commit/408db7323ab29733552fe7140405804bb4207e51))

## [4.0.1](https://github.com/socsieng/sendkeys/compare/v4.0.0...v4.0.1) (2023-10-07)


### Bug Fixes

* **build:** clean up archives after bottling ([1ef78ed](https://github.com/socsieng/sendkeys/commit/1ef78ed4297d0c69ab1327ed6796fdddda1d2543))

## [4.0.0](https://github.com/socsieng/sendkeys/compare/v3.0.2...v4.0.0) (2023-10-07)


### ⚠ BREAKING CHANGES

* fix handling of key strokes to use shift key when appropriate

### Bug Fixes

* fix handling of key strokes to use shift key when appropriate ([beb535b](https://github.com/socsieng/sendkeys/commit/beb535b2693085cf50bc479276a4d806e55d288c)), closes [#62](https://github.com/socsieng/sendkeys/issues/62)
* fix issue with unspecified application name ([440153d](https://github.com/socsieng/sendkeys/commit/440153d073c10888fd33990197434d9565b430d1))

## [3.0.2](https://github.com/socsieng/sendkeys/compare/v3.0.1...v3.0.2) (2023-10-06)


### Bug Fixes

* introduce a small delay to allow commands to be processed before terminating ([9ffc2b2](https://github.com/socsieng/sendkeys/commit/9ffc2b2a012314d983c7e2a94385747abce8ef24)), closes [#60](https://github.com/socsieng/sendkeys/issues/60)

## [3.0.1](https://github.com/socsieng/sendkeys/compare/v3.0.0...v3.0.1) (2023-10-06)


### Bug Fixes

* update key handling to use keyboardEventSource ([c26bbfa](https://github.com/socsieng/sendkeys/commit/c26bbfaaab700a6063b8fbb967622241595eb5a0))

## [3.0.0](https://www.github.com/socsieng/sendkeys/compare/v2.9.1...v3.0.0) (2023-10-06)


### ⚠ BREAKING CHANGES

* drop support for building on macOS catalina

### Features

* add support for sending keys to an application without activation ([b42d4bc](https://www.github.com/socsieng/sendkeys/commit/b42d4bc347d1800068e8753bc6daa13b255319b2)), closes [#67](https://www.github.com/socsieng/sendkeys/issues/67)


### Bug Fixes

* remove dependency on macos-10.15 ([14aa5a8](https://www.github.com/socsieng/sendkeys/commit/14aa5a82ba2fd34e2fa49d5b749b0fb941d1f902))


### Miscellaneous

* drop support for building on macOS catalina ([8c027cb](https://www.github.com/socsieng/sendkeys/commit/8c027cb14e4ca9bfe1515a41191c1ecfa4499112))

### [2.9.1](https://www.github.com/socsieng/sendkeys/compare/v2.9.0...v2.9.1) (2023-04-15)


### Bug Fixes

* fix homebrew update script ([dac393f](https://www.github.com/socsieng/sendkeys/commit/dac393fe982b7b5ce39a9450277a36b0ea46d58e))

## [2.9.0](https://www.github.com/socsieng/sendkeys/compare/v2.8.0...v2.9.0) (2023-04-15)


### Features

* add support for activating application by process id ([0a44470](https://www.github.com/socsieng/sendkeys/commit/0a4447044d2acddba176db74d9698cbef4dfc872)), closes [#63](https://www.github.com/socsieng/sendkeys/issues/63)

## [2.8.0](https://www.github.com/socsieng/sendkeys/compare/v2.7.1...v2.8.0) (2021-09-01)


### Features

* convert mouse coordinates from integers to doubles ([bbd4534](https://www.github.com/socsieng/sendkeys/commit/bbd45342cf0f1974ed2a2365d386b44b3225841d))
* make scaleY option in path command ([f1fe840](https://www.github.com/socsieng/sendkeys/commit/f1fe8406a13c7abee8844123d0d5aeda903b6620))
* output mouse positions as decimals ([d723082](https://www.github.com/socsieng/sendkeys/commit/d723082a068f4806bad9363098727af287c067d7))


### Documentation

* add sample command for mouse path command ([9c9e6cb](https://www.github.com/socsieng/sendkeys/commit/9c9e6cbe0b4c7ca313116eb4b7966824c681ed17))

### [2.7.1](https://www.github.com/socsieng/sendkeys/compare/v2.7.0...v2.7.1) (2021-08-31)


### Bug Fixes

* remove debug print statements ([5109a12](https://www.github.com/socsieng/sendkeys/commit/5109a12045bb65d18fe570db53c9ce88e6fa4d3d))

## [2.7.0](https://www.github.com/socsieng/sendkeys/compare/v2.6.2...v2.7.0) (2021-08-31)


### Features

* add mouse path command ([6c5c0b9](https://www.github.com/socsieng/sendkeys/commit/6c5c0b9ab5e25d795ee470884a25de2b28d5988d))


### Documentation

* add example animation for mouse path command ([2c49b8d](https://www.github.com/socsieng/sendkeys/commit/2c49b8d16735d60186c1b1c842ff31607ec265bb))

### [2.6.2](https://www.github.com/socsieng/sendkeys/compare/v2.6.1...v2.6.2) (2021-08-22)


### Features

* improve application name matching algorithm ([456586a](https://www.github.com/socsieng/sendkeys/commit/456586a746bea2294fd64db238cf595d9f7bf417)), closes [#50](https://www.github.com/socsieng/sendkeys/issues/50)


### Documentation

* update README to include details on  apps sub command ([40f49be](https://www.github.com/socsieng/sendkeys/commit/40f49bec4ce9574ee7e7ecafafd7facba067f64c))

### [2.6.1](https://www.github.com/socsieng/sendkeys/compare/v2.6.0...v2.6.1) (2021-08-21)


### chore

* bump version ([ee86888](https://www.github.com/socsieng/sendkeys/commit/ee86888368363598d51fbd5b1b49ca59a68ef541))

## [2.6.0](https://www.github.com/socsieng/sendkeys/compare/v2.5.2...v2.6.0) (2021-08-21)


### Features

* display a list applications that can be used with sendkeys ([94626fa](https://www.github.com/socsieng/sendkeys/commit/94626fa39a30b9b51f03548aa0b8dbb62bd5cfb6)), closes [#46](https://www.github.com/socsieng/sendkeys/issues/46)

### [2.5.2](https://www.github.com/socsieng/sendkeys/compare/v2.5.1...v2.5.2) (2021-06-19)


### Bug Fixes

* **build:** remove usage of realpath ([c9019d7](https://www.github.com/socsieng/sendkeys/commit/c9019d7d3ac9a3776b15a50708365de86d6f3b72))

### [2.5.1](https://www.github.com/socsieng/sendkeys/compare/v2.5.0...v2.5.1) (2021-06-19)


### Bug Fixes

* **build:** apply changes to address broken homebrew build ([536603a](https://www.github.com/socsieng/sendkeys/commit/536603a0e4f224ffe87487954318f4b617264e87))

## [2.5.0](https://www.github.com/socsieng/sendkeys/compare/v2.4.0...v2.5.0) (2021-06-18)


### Features

* add option to output mouse position commands with predefined duration ([f662869](https://www.github.com/socsieng/sendkeys/commit/f662869cf207bca9978407758eadef244c9d026f))

## [2.4.0](https://www.github.com/socsieng/sendkeys/compare/v2.3.10...v2.4.0) (2021-03-27)


### Features

* add support for apple m1 processors ([6706f85](https://www.github.com/socsieng/sendkeys/commit/6706f85b340168783ecb5e4fe56bde545c2bf86a))

### [2.3.10](https://www.github.com/socsieng/sendkeys/compare/v2.3.0...v2.3.10) (2021-03-27)


### Documentation

* add example file for using transform ([1a2c8e8](https://www.github.com/socsieng/sendkeys/commit/1a2c8e82cd6b04321aadb78979c73a120d340bf4))
* update readme to include installation instructions for alternate versions ([72030ab](https://www.github.com/socsieng/sendkeys/commit/72030abc018a7bd81e31509ac7085e77620d6528))

## [2.3.0](https://www.github.com/socsieng/sendkeys/compare/v2.2.0...v2.3.0) (2021-01-20)


### Features

* add mouse focus command ([1bbab2d](https://www.github.com/socsieng/sendkeys/commit/1bbab2d0a9377260d892a78cfbc41713064ac736))

## [2.2.0](https://www.github.com/socsieng/sendkeys/compare/v2.1.1...v2.2.0) (2021-01-16)


### Features

* add support for triggering mouse up and down events independently ([bee0fbe](https://www.github.com/socsieng/sendkeys/commit/bee0fbe5032a97fe63090fedf9cc7c9d537f60f6))

### [2.1.1](https://www.github.com/socsieng/sendkeys/compare/v2.1.0...v2.1.1) (2021-01-07)


### Bug Fixes

* fix typo in key mappings ([c4a4997](https://www.github.com/socsieng/sendkeys/commit/c4a4997375c55cc4217c1a381df39ff94d9ff751))
* handle `a` key correctly as keycode 0 ([29209a3](https://www.github.com/socsieng/sendkeys/commit/29209a34a495afdc0028e353ccf1b8375b32f005))

## [2.1.0](https://www.github.com/socsieng/sendkeys/compare/v2.0.0...v2.1.0) (2021-01-06)


### Features

* add option to change animation refresh rate ([73a2c29](https://www.github.com/socsieng/sendkeys/commit/73a2c294a878f9e8e64c14a4b6664ddb1586e13b)), closes [#21](https://www.github.com/socsieng/sendkeys/issues/21)
* add transform subcommand ([3893313](https://www.github.com/socsieng/sendkeys/commit/38933135a3746343d501cd720d4b66f7ee1ac552))


### Bug Fixes

* start mouse timing when mouse-position command is executed ([1437aac](https://www.github.com/socsieng/sendkeys/commit/1437aac909e289a782d16677601a81c49d443d85))
* support negative values for mouse click and drag events ([f50209a](https://www.github.com/socsieng/sendkeys/commit/f50209ae1e8924dbd189a11a6ecad388082c1d17)), closes [#23](https://www.github.com/socsieng/sendkeys/issues/23)


### Documentation

* update documentation to state that the application should be running ([3e0d973](https://www.github.com/socsieng/sendkeys/commit/3e0d9736559d48e45c3287c48830bd743926e3ef))

## [2.0.0](https://www.github.com/socsieng/sendkeys/compare/v1.3.0...v2.0.0) (2021-01-04)


### Features

* use click timings when producing mouse commands ([970e1df](https://www.github.com/socsieng/sendkeys/commit/970e1df52a903c93da62a572edc99caea1ffc1b5)), closes [#18](https://www.github.com/socsieng/sendkeys/issues/18)


### Bug Fixes

* check file exists before activating application ([94cda37](https://www.github.com/socsieng/sendkeys/commit/94cda379c54175e1b59a87633512456b327e63fa)), closes [#17](https://www.github.com/socsieng/sendkeys/issues/17)


### Documentation

* include example recording and replaying mouse commands ([5019461](https://www.github.com/socsieng/sendkeys/commit/501946176dab36189e05b49c6e09800ec3bd77ad)), closes [#19](https://www.github.com/socsieng/sendkeys/issues/19)

## [1.3.0](https://www.github.com/socsieng/sendkeys/compare/v1.2.0...v1.3.0) (2021-01-04)


### Features

* add option to listen to mouse clicks ([d1d129f](https://www.github.com/socsieng/sendkeys/commit/d1d129fed23f969b10bf0475aeb77f9752a4a5bd))


### Documentation

* use expanded argument names in examples ([f4839a3](https://www.github.com/socsieng/sendkeys/commit/f4839a3c027bc78fb13e6f717147f206c3c043d8))

## [1.2.0](https://www.github.com/socsieng/sendkeys/compare/v1.1.1...v1.2.0) (2021-01-03)


### Features

* defer accesibility check to execution of the command ([670d091](https://www.github.com/socsieng/sendkeys/commit/670d091d17100fd9b9cdb74ba0eebf69cae6af51))

### [1.1.1](https://www.github.com/socsieng/sendkeys/compare/v1.1.0...v1.1.1) (2021-01-02)


### Bug Fixes

* address modifier key issue on key up ([0bfa58a](https://www.github.com/socsieng/sendkeys/commit/0bfa58ad87ebcff46b905900584877544a533463))
* double key entry issue ([26ad67e](https://www.github.com/socsieng/sendkeys/commit/26ad67e80aaa30fcd9c2a1bb20f5987288760bbc))

## [1.1.0](https://www.github.com/socsieng/sendkeys/compare/v1.0.0...v1.1.0) (2021-01-02)


### Features

* add support for key down and up commands ([2c5cefb](https://www.github.com/socsieng/sendkeys/commit/2c5cefb28c802dea94d55920c4092b232f50e62e))
* add support for mouse clicks with modifier keys ([1654fd7](https://www.github.com/socsieng/sendkeys/commit/1654fd7217c7588c16c22ff779d26564aee634f9))
* add support for mouse drag with modifier keys ([32964a7](https://www.github.com/socsieng/sendkeys/commit/32964a725312bfa04d203c4b56d3cf20cf237b52))
* add support for mouse move with modifier keys ([7f4c891](https://www.github.com/socsieng/sendkeys/commit/7f4c891be9aed26f37c38d6275247830d9f04b5c))
* add support for mouse scroll with modifier keys ([27afc5b](https://www.github.com/socsieng/sendkeys/commit/27afc5bb7c50b762d4d1333f3c6eeef6b92a8d8d))


### Bug Fixes

* add event source attribution to related events ([d443fdf](https://www.github.com/socsieng/sendkeys/commit/d443fdf6c2e64b8258e76c7a2ea23a94de62a695))
* make right click work consistently across applications ([464a401](https://www.github.com/socsieng/sendkeys/commit/464a401d532ef2afc1cfa6a206d84479aa92888a))
* use click count when triggering mouse click ([f824a98](https://www.github.com/socsieng/sendkeys/commit/f824a98823551c451fcb4e8f6c9196d9fe26b8e6))


### Documentation

* add example of mouse move command ([2470c7b](https://www.github.com/socsieng/sendkeys/commit/2470c7bc56feb207fda2b1f2e39b1377ccf4ffd2))
* **modifier keys:** add documentation for mouse modifier keys ([7c5e9d9](https://www.github.com/socsieng/sendkeys/commit/7c5e9d917d5eb18b003095a8a361e9ac99078826))

## [1.0.0](https://www.github.com/socsieng/sendkeys/compare/v0.5.0...v1.0.0) (2020-12-31)


### Features

* stable release 1.0.0 ([27bcfc6](https://www.github.com/socsieng/sendkeys/commit/27bcfc68bc183b7a0d6e466b32ea619d7eee7aed))


### Bug Fixes

* read file relative to current directory ([49e1253](https://www.github.com/socsieng/sendkeys/commit/49e12537b288a34e7eb3bc060bc513ae36a86a82))


### Miscellaneous

* append newline when raising a fatal error ([b33f495](https://www.github.com/socsieng/sendkeys/commit/b33f495d7c60810cc82a540b920467dd61f8b869))

## [0.5.0](https://www.github.com/socsieng/sendkeys/compare/v0.4.0...v0.5.0) (2020-12-31)


### Features

* add support for scrolling ([b438e00](https://www.github.com/socsieng/sendkeys/commit/b438e0089b947339c935a21ba39c66375dea4b5d))

## [0.4.0](https://www.github.com/socsieng/sendkeys/compare/v0.3.0...v0.4.0) (2020-12-31)


### Features

* add check to see if accessibility permissions have been enabled ([409e0fb](https://www.github.com/socsieng/sendkeys/commit/409e0fbe935113329b1d61cdbe3f2cd186781117))
* add sub command to display the current mouse position ([507f8b8](https://www.github.com/socsieng/sendkeys/commit/507f8b8aa1ccedbee2936295c2d192f1a1c33ede))
* add wait option for mouse-position ([2c0aa8f](https://www.github.com/socsieng/sendkeys/commit/2c0aa8fab484de46b835cc0ec52afc22699f9033))


### Bug Fixes

* display help when no commands supplied ([c3175ba](https://www.github.com/socsieng/sendkeys/commit/c3175ba182ab98831dfbb29afcd2241f6e22cdfc))
* only perform accessibility check if stdin is tty ([6043a78](https://www.github.com/socsieng/sendkeys/commit/6043a784a9f2245bd826b30787d494a687899428))


### Documentation

* add documentation on how to use mouse-position command ([42e9955](https://www.github.com/socsieng/sendkeys/commit/42e9955b4f3efbc2d56c062f85134d17deea68b7))

## [0.3.0](https://www.github.com/socsieng/sendkeys/compare/v0.2.4...v0.3.0) (2020-12-31)


### ⚠ BREAKING CHANGES

* update build step to update homebrew formula

### build

* update build step to update homebrew formula ([5fd8722](https://www.github.com/socsieng/sendkeys/commit/5fd8722409c6b536ec2388db7e99e0dfa6a134ea))

### [0.2.4](https://www.github.com/socsieng/sendkeys/compare/v0.2.3...v0.2.4) (2020-12-31)


### Documentation

* add brew install instructions ([95023b1](https://www.github.com/socsieng/sendkeys/commit/95023b1a8533ab7f2fb6e7ec4f650a312e0ab828))

### [0.2.3](https://www.github.com/socsieng/sendkeys/compare/v0.2.2...v0.2.3) (2020-12-31)


### Bug Fixes

* update bottle name to work with brew ([259c140](https://www.github.com/socsieng/sendkeys/commit/259c14051895c5672f290e2f8f2bf299395e31a4))

### [0.2.2](https://www.github.com/socsieng/sendkeys/compare/v0.2.1...v0.2.2) (2020-12-30)


### Bug Fixes

* **build:** fix bad substitution in sed ([254161e](https://www.github.com/socsieng/sendkeys/commit/254161e567bfb8a8063d0dd8c8b976ec0d2c09da))

### [0.2.1](https://www.github.com/socsieng/sendkeys/compare/v0.2.0...v0.2.1) (2020-12-30)


### Bug Fixes

* **build:** update scripts to handle differences in tag_name ([13fbf6b](https://www.github.com/socsieng/sendkeys/commit/13fbf6bb377ada5becd25399b75e9367cbbc4302))

## [0.2.0](https://www.github.com/socsieng/sendkeys/compare/v0.1.0...v0.2.0) (2020-12-30)


### ⚠ BREAKING CHANGES

* update build step names

### Build System

* update build step names ([4d6a0f4](https://www.github.com/socsieng/sendkeys/commit/4d6a0f476d5331a1d99bfd150dcf88281d0f1dd3))

## [0.1.0](https://www.github.com/socsieng/sendkeys/compare/v0.0.3...v0.1.0) (2020-12-30)


### Features

* add support for reading from stdin ([463d777](https://www.github.com/socsieng/sendkeys/commit/463d7775e1bc11a293917c43e7c00335d2e77404))


================================================
FILE: Formula/sendkeys_template.rb
================================================
# Documentation: https://docs.brew.sh/Formula-Cookbook
#                https://rubydoc.brew.sh/Formula
class Sendkeys < Formula
  desc "Command line tool for automating keystrokes and mouse events"
  homepage "https://github.com/socsieng/sendkeys"
  url ""
  version "0.0.0"
  license "Apache-2.0"

  depends_on :xcode => ["12.0", :build]

  def install
    system "make", "install", "prefix=#{prefix}"
  end

  test do
    system "sendkeys" "--help"
  end
end


================================================
FILE: LICENSE
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: Makefile
================================================
prefix ?= /usr/local
bindir ?= $(prefix)/bin

.PHONY: build
build:
	@scripts/update-version.sh
	@swift build -c release --disable-sandbox --arch arm64 --arch x86_64

.PHONY: verify
verify:
	@swift test
	@scripts/verify-output.sh

.PHONY: install
install: build
	@install -d "$(bindir)"
	@install ".build/apple/Products/Release/sendkeys" "$(bindir)/sendkeys"

.PHONY: uninstall
uninstall:
	@rm -rf "$(bindir)/sendkeys"

.PHONY: clean
clean:
	@rm -rf .build


================================================
FILE: Package.resolved
================================================
{
  "object": {
    "pins": [
      {
        "package": "swift-argument-parser",
        "repositoryURL": "https://github.com/apple/swift-argument-parser",
        "state": {
          "branch": null,
          "revision": "41982a3656a71c768319979febd796c6fd111d5c",
          "version": "1.5.0"
        }
      },
      {
        "package": "cmark-gfm",
        "repositoryURL": "https://github.com/apple/swift-cmark.git",
        "state": {
          "branch": null,
          "revision": "3ccff77b2dc5b96b77db3da0d68d28068593fa53",
          "version": "0.5.0"
        }
      },
      {
        "package": "swift-format",
        "repositoryURL": "https://github.com/apple/swift-format.git",
        "state": {
          "branch": null,
          "revision": "65f9da9aad84adb7e2028eb32ca95164aa590e3b",
          "version": "600.0.0"
        }
      },
      {
        "package": "swift-markdown",
        "repositoryURL": "https://github.com/apple/swift-markdown.git",
        "state": {
          "branch": null,
          "revision": "8f79cb175981458a0a27e76cb42fee8e17b1a993",
          "version": "0.5.0"
        }
      },
      {
        "package": "swift-syntax",
        "repositoryURL": "https://github.com/swiftlang/swift-syntax.git",
        "state": {
          "branch": null,
          "revision": "0687f71944021d616d34d922343dcef086855920",
          "version": "600.0.1"
        }
      },
      {
        "package": "Yams",
        "repositoryURL": "https://github.com/jpsim/Yams.git",
        "state": {
          "branch": null,
          "revision": "3036ba9d69cf1fd04d433527bc339dc0dc75433d",
          "version": "5.1.3"
        }
      }
    ]
  },
  "version": 1
}


================================================
FILE: Package.swift
================================================
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "sendkeys",
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        .package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"),
        .package(url: "https://github.com/apple/swift-format", from: "600.0.0"),
        .package(url: "https://github.com/jpsim/Yams.git", from: "5.1.3"),
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages which this package depends on.
        .executableTarget(
            name: "sendkeys",
            dependencies: [
                .product(name: "ArgumentParser", package: "swift-argument-parser"),
                "SendKeysLib",
            ]),
        .target(
            name: "SendKeysLib",
            dependencies: [
                .product(name: "ArgumentParser", package: "swift-argument-parser"),
                .product(name: "Yams", package: "Yams"),
            ]),
        .testTarget(
            name: "SendKeysTests",
            dependencies: ["sendkeys", "SendKeysLib"]),
    ]
)


================================================
FILE: README.md
================================================
# SendKeys

![Build status](https://github.com/socsieng/sendkeys/workflows/build/badge.svg)
![Homebrew installs](https://img.shields.io/github/downloads/socsieng/sendkeys/total.svg?label=%F0%9F%8D%BA+installs&labelColor=32393F&color=brightgreen)

SendKeys is a macOS command line application used to automate the keystrokes and mouse events.

It is a great tool for automating input and mouse events for screen recordings.

This is a Swift rewrite of [`sendkeys-macos`](https://github.com/socsieng/sendkeys-macos).

## Usage

Basic usage:

```sh
sendkeys --application-name "Notes" --characters "Hello<p:1> world<c:left:option,shift><c:i:command>"
```

![hello world example](https://github.com/socsieng/sendkeys/raw/main/docs/images/example1.gif)

_Activates the Notes application (assuming Notes is already running) and types `Hello` (followed by a 1 second pause)
and `world`, and then selects the word `world` and changes the font to italics with `command` + `i`._

Input can be read from a file:

```sh
sendkeys --application-name "Code" --input-file example.txt
```

_Activates Visual Studio Code and sends keystrokes loaded from `example.txt`._

Input can also be piped to `stdin`:

```sh
cat example.txt | sendkeys --application-name "Notes"
```

_Activates the Notes application and sends keystrokes piped from `stdout` of the preceding command._

### Arguments

- `--application-name <application-name>`: The application name to activate or target when sending commands. Note that a
  list of applications that can be used in `--application-name` can be found using the
  [`apps` sub command](#list-of-applications-names).
- `--pid <process-id>`: The process id of the application to target when sending commands. Note that this if this
  argument is supplied with `--application-name`, `--pid` takes precedence.
- `--targeted`: If supplied, the application keystrokes will only be sent to the targeted application.
- `--no-activate`: If supplied, the specified application will not be activated before sending commands.
- `--input-file <file-name>`: The path to a file containing the commands to send to the application.
- `--characters <characters>`: The characters to send to the application. Note that this argument is ignored if
  `--input-file` is supplied.
- `--delay <delay>`: The delay between keystrokes and instructions. Defaults to `0.1` seconds.
- `--initial-delay <initial-delay>`: The initial delay before sending the first keystroke or instruction. Defaults to
  `1` second.
- `--animation-interval <interval-in-seconds>`: The time between mouse movements when animating mouse commands. Lower
  values results in smoother animations. Defaults to `0.01` seconds.
- `--terminate-command <command>`: The command that should be used to terminate the application. Not set by default.
  Follows a similar convention to `--characters`. (e.g. `f12:command,shift`).
- `--keyboard-layout <layout>`: Use alternate keyboard layout. Defaults to `qwerty`. `colemak` and `dvorak` are also
  supported, pull requests for other common keyboard layouts may be considered. If a specific keyboard layout is not
  supported, a custom layout can be defined in using the `--config` option or using the
  [`.sendkeysrc.yml`](./examples/.sendkeysrc.yml) configuration file (`send.remap`).
- `--config <yaml-file>`: Configuration file to load settings from.

## Installation

### Homebrew (recommended)

Install using [homebrew](https://brew.sh/):

```sh
brew install socsieng/tap/sendkeys
```

### Manual installation

Alternatively, install from source:

```sh
git clone https://github.com/socsieng/sendkeys.git
cd sendkeys
make install
```

## Markup

Most printable characters will be sent as keystrokes to the active application. Support for additional instructions is
provided by some basic markup which is unlikely to be used in other markup languages to avoid conflicts.

### Key codes and modifier keys

Support for special key codes and modifier keys is provided with the following markup structure: `<c:key[:modifiers]>`

- `key` can include any printable character or, one of the following key names: `f1`, `f2`, `f3`, `f4`, `f5`, `f6`,
  `f7`, `f8`, `f9`, `f10`, `f11`, `f12`, `esc`, `return`, `enter`, `delete`, `space`, `tab`, `up`, `down`, `left`,
  `right`, `home`, `end`, `pgup`, and `pgdown`. See list of
  [mapped keys](https://github.com/socsieng/sendkeys/blob/main/Sources/SendKeysLib/KeyCodes.swift#L127) for a full list.
- `modifiers` is an optional list of comma separated values that can include `command`, `shift`, `control`, `option`,
  and `function`.

Example key combinations:

- `tab`: `<c:tab>`
- `command` + `a`: `<c:a:command>`
- `option` + `shift` + `left arrow`: `<c:left:option,shift>`

#### Key down and up

Some applications expect modifier keys to be pressed explicitly before invoking actions like mouse click. An example of
this is Pixelmator which expect the `option` key to be pressed before executing the alternate click action. This can be
achieved with key down `<kd:key[:modifiers]>` and key up `<ku:key[:modifiers]>`.

Note that these command shoulds only be used in these special cases when the mouse action and modifier keys are not
supported natively.

An example of how to trigger alternate click behavior in Pixelmator as described above:
`<kd:option><m:left:option><ku:option>`.

### Mouse commands

#### Move mouse cursor

The mouse cursor can be moved using the following markup: `<m:[x1,y1,]x2,y2[:duration][:modifiers]>`

- `x1` and `y1` are optional x and y coordinates to move the mouse from. Defaults to the current mouse position.
- `x2` and `y2` are x and y coordinates to move the mouse to. These values are required.
- `duration` is optional and determines the number of seconds (supports partial seconds) that should be used to move the
  mouse cursor (larger number means slower movement). Defaults to `0`.
- `modifiers` is an optional list of comma separated values that can include `command`, `shift`, `control`, and
  `option`.

Example usage:

- `<m:400,400:0.5>`: Move mouse cursor from current position to 400, 400 over 0.5 seconds.
- `<m:400,400,0,0:2>`: Move mouse cursor from 400, 400 position to 0, 0 over 2 seconds.
- `<m:400,400>`: Move mouse cursor to 400, 400 instantly.

![mouse move example](https://github.com/socsieng/sendkeys/raw/main/docs/images/mouse.gif) <br>_Sample command:
`sendkeys -c "<m:100,300,300,300:0.5><p:0.5><m:100,300:0.5>"`_

#### Mouse click

A mouse click can be activated using the following markup: `<m:button[:modifiers][:clicks]>`

- `button` is required and refers to the mouse button to click. Supported values include `left`, `center`, and `right`.
- `modifiers` is an optional list of comma separated values that can include `command`, `shift`, `control`, and
  `option`.
- `clicks` is optional and specifies the number of times the button should be clicked. Defaults to `1`.

Example usage:

- `<m:right>`: Right mouse click at the current mouse location.
- `<m:left:2>`: Double click the left button at the current mouse location.

#### Mouse drag

A mouse drag be initiated with: `<d:[x1,y1,]x2,y2[:duration][:button[:modifiers]]>`

The argument structure is similar to moving the mouse cursor.

- `x1` and `y1` are optional x and y coordinates to start the drage. Defaults to the current mouse position.
- `x2` and `y2` are x and y coordinates to end the drag. These values are required.
- `duration` is optional and determines the number of seconds (supports partial seconds) that should be used to drag the
  mouse (larger number means slower movement). Defaults to `0`.
- `button` is optional and refers to the mouse button to use when initiating the mouse drag. Supported values include
  `left`, `center`, and `right`. Defaults to `left`.
- `modifiers` is an optional list of comma separated values that can include `command`, `shift`, `control`, and
  `option`. Note that modifiers can only be used if `button` is explicitly set.

Example usage:

- `<d:400,400:0.5>`: Drag the mouse using the left mouse button from current position to 400, 400 over 0.5 seconds.
- `<d:400,400,0,0:2:right>`: Drag the mouse using the right mouse button from 400, 400 position to 0, 0 over 2 seconds.
- `<d:400,400:2:left:shift>`: Drag the mouse using the left mouse button to 400, 400 over 2 seconds with the `shift` key
  down.

![mouse drag example](https://github.com/socsieng/sendkeys/raw/main/docs/images/mouse-drag.gif)

#### Mouse scrolling

A mouse scroll can be initiated with: `<s:x,y[:duration][:modifiers]>`

- `x` is required and controls horizontal scrolling. Positive values scroll to the right, while negative values scroll
  to the left.
- `y` is required and controls vertical scrolling. Positive values scroll down, while negative values scroll up.
- `duration` is optional and determines the number of seconds (supports partial seconds) that should be used to drag the
  mouse (larger number means slower movement). Defaults to `0`.
- `modifiers` is an optional list of comma separated values that can include `command`, `shift`, `control`, and
  `option`.

Example usage:

- `<s:0,400:0.5>`: Scrolls down 400 pixels over 0.5 seconds.
- `<s:0,-100:0.2>`: Scrolls up 400 pixels over 0.2 seconds.
- `<s:100,0>`: Scrolls 100 pixel to the right instantly.

#### Mouse focus

The mouse focus command can be used to draw attention to an area of the screen by moving the cursor in a circular
pattern. The mouse focus command uses the following markup:
`<mf:centerX,centerY:radiusX[,radiusY]:angleFrom,angleTo:duration>`

- `centerX` is required and represents the center x coordinate of the circular path.
- `centerY` is required and represents the center y coordinate of the circular path.
- `radiusX` is required and represents the size of the radius along the x axis of the circular path.
- `radiusY` is optional and represents the size of the radius along the y axis of the circular path. If omitted,
  `radiusX` will be used indicating that the circular path will be a regular circle. An elipse can be achieved by having
  different values for `radiusX` and `radiusY`.
- `angleFrom` is required and represents the start angle/position of the circular path. Angle is defined using degrees
  where `0` represents 12 o'clock on an analog clock, and positive are applied in a clockwize direction. (e.g. 90
  degrees is 3 o'clock).
- `angleTo` is required and represents the end angle/position of the circular path.
- `duration` is required and determines the number of seconds (supports partial seconds) used to complete the animation
  between `angleFrom` to `angleTo`.

Example usage:

- `<mf:1000,200:50,20:180,900:2>`: Draws attention to position 1000, 200 by moving the mouse along an eliptical 50
  pixels wide by 20 pixels high starting at the bottom (180 degrees) to 900 degrees (delta of 720 degrees) over a period
  of 2 seconds.

![mouse focus example](https://github.com/socsieng/sendkeys/raw/main/docs/images/mouse-focus.gif)

#### Mouse path

The mouse path command can be used move the mouse cursor along a path. The mouse path command uses the following markup:
`<mpath:path[:ofssetX,offsetY[,scaleX[,scaleY]]]:duration>`

- `path` is required and defines path for the mouse cursor to follow. The path is described using
  [SVG Path data](https://www.w3.org/TR/SVG/paths.html#PathData)
- `ofssetX` and `offsetY` are optional and can be used to offset path coordinates by their respective `x` and `y`
  values. Defaults to `0,0`.
- `scaleX` and `scaleY` are also optional and can be used to scale path coordinates by their respective `x` and `y`
  values. Defaults to `1,1`. If `scaleY` is omitted while `scaleX` is provided, a uniform scale will be assumed. i.e.
  `x` = `y`.
- `duration` is required and determines the number of seconds (supports partial seconds) used to complete the animation
  along the `path`.

Example usage:

- `<mpath:c0,40 200,40 200,0:2>`: Moves the mouse from its current position along a cubic bezier path with control
  points `0,40` and `200,40` to the final position of `200,1`.

![mouse path example](https://github.com/socsieng/sendkeys/raw/main/docs/images/mouse-path.gif) <br>_Sample command:
`sendkeys -c "<mpath:M100,100 h 100 l5,30 10,-60 5,30 h 100:2><mpath:c0,40 -220,40 -220,0:1.5>"`_

#### Mouse down and up

Mouse down and up events can be used to manually initiate a drag event or multiple mouse move commands while the mouse
button is down. This can be achieved with mouse down `<md:button[:modifiers]>` and mouse up `<mu:button[:modifiers]>`.

Note that the drag command is recommended for basic drag functionality..

An example of how include multiple mouse movements while the mouse button is down:
`<md:left><m:0,0,100,0:1><m:100,100:1><mu:left>`.

### Pauses

The default time between keystrokes and instructions is determined by the `--delay`/`-d` argument (default value is
`0.1`). Pauses can be customized with: `<p:duration>`

- `duration` is required and controls the amount of time to pause before the next keystroke/instruction is executed.

`<P:seconds>` (note upper case `P`) can be used to modify the default delay between subsequent keystrokes.

### Continuation

A continuation can be used to ignore the next keystroke or instruction. This is useful to help with formatting a long
sequence of character and inserting a new line for readability.

Insert a continuation using the character sequence `<\>`. The following instruction the sequence will be skipped over
(including another continuation).

## Transforming text for text editors

Some text editors like Visual Studio Code will automatically indent or insert closing brackets which can cause
duplication of whitespace and characters. The `transform` subcommand can help transform text files for better
compatibility with similar text editors.

Example:

```sh
sendkeys transform --input-file examples/node.js
```

You can also pipe the output of the `transform` command directly to your editor of choice. Example:

```sh
sendkeys transform --input-file examples/node.js | sendkeys --application-name "Code"
```

## Retrieving mouse position

The `mouse-position` sub command can be used to help determine which mouse coordinates to use in your scripts.

For a one off read, move your mouse to the desired position, switch to your terminal app using `command` + `tab` and
execute the following command:

```sh
sendkeys mouse-position
```

Use the `--watch` option to capture the location of mouse clicks, and combine it with `--output commands` to output
approximate mouse commands that can be used to _replay_ mouse actions.

```sh
# capture mouse commands
sendkeys mouse-position --watch --output commands > mouse_commands.txt

# replay mouse commands
sendkeys --input-file mouse_commands.txt
```

## List of applications names

A list of the current applications that can be activated by SendKeys (`--application-name`) can be displayed using the
`apps` command.

```sh
# list apps that can be activated with --application-name
sendkeys apps
```

Sample output:

```text
Code             id:com.microsoft.VSCode
Finder           id:com.apple.finder
Google Chrome    id:com.google.Chrome
Safari           id:com.apple.Safari
```

The first column includes the application name and the second column includes the application's bundle ID.

SendKeys will use `--application-name` to activate the first application instance that matches either the application
name or bundle id (case insensitive). If there are no exact matches, it will attempt to match on whole words for the
application name, followed by the bundle id.

## Configuration

Common arguments can be stored in a [`.sendkeysrc.yml`](./examples/.senkeysrc.yml) configuration file. Configuration
values are applied and merged in the following priority order:

1. Command line arguments
2. Configuration file defined with `--config` option
3. Configuration file defined in `~/.sendkeysrc.yml`
4. Default values

## Prerequisites

This application will only run on macOS 10.11 or later.

When running from the terminal, ensure that the terminal has permission to use accessibility features. This can be done
by navigating to System Preferences > Security & Privacy > Privacy > Accessibility and adding your terminal application
there.

![accessibility settings](https://github.com/socsieng/sendkeys/raw/main/docs/images/accessibility.gif)

## Installing previous versions

A specific version of the package can be installed by targeting the appropriate release artifact. Here's an example of
the command:

```sh
brew install --force-bottle https://github.com/socsieng/sendkeys/releases/download/v2.3.0/sendkeys-2.3.0.catalina.bottle.tar.gz
```


================================================
FILE: Sources/SendKeysLib/Animator.swift
================================================
import Foundation

class Animator {
    typealias AnimationCallback = (_ progress: Double) -> Void

    let duration: TimeInterval
    let frequency: TimeInterval
    let animateFn: AnimationCallback

    init(_ duration: TimeInterval, _ frequency: TimeInterval, _ animateFn: @escaping AnimationCallback) {
        self.duration = duration
        self.frequency = frequency
        self.animateFn = animateFn
    }

    func animate() {
        let startDate = Date()

        while -startDate.timeIntervalSinceNow < duration {
            let progress = min(-startDate.timeIntervalSinceNow as Double / duration as Double, 1)
            let easedValue = easeInOut(progress)

            Sleeper.sleep(seconds: frequency)
            animateFn(easedValue)
        }

        animateFn(1)
    }

    func easeInOut(_ x: Double) -> Double {
        return x < 0.5 ? 2 * x * x : 1 - pow(-2 * x + 2, 2) / 2
    }
}


================================================
FILE: Sources/SendKeysLib/AppActivator.swift
================================================
import Cocoa

class AppActivator: NSObject {
    private var application: NSRunningApplication!
    private let appName: String?
    private let processId: Int?

    init(appName: String?, processId: Int?) {
        self.appName = appName?.lowercased()
        self.processId = processId
    }

    func find() throws -> NSRunningApplication? {
        let apps = NSWorkspace.shared.runningApplications.filter({ a in
            return a.activationPolicy == .regular
        })

        var app: NSRunningApplication?

        if processId != nil {
            app =
                apps.filter({ a in
                    return a.processIdentifier == pid_t(processId!)
                }).first

            if app == nil {
                throw RuntimeError(
                    "Application with process id \(processId!) could not be found."
                )
            }
        } else if appName != nil {
            // exact match (case insensitive)
            app =
                apps.filter({ a in
                    return a.localizedName?.lowercased() == appName
                        || a.bundleIdentifier?.lowercased() == appName
                }).first

            let expression = try! NSRegularExpression(
                pattern: "\\b\(NSRegularExpression.escapedPattern(for: appName!))\\b", options: .caseInsensitive)

            // partial name match
            if app == nil {
                app =
                    apps.filter({ a in
                        let nameMatch = expression.firstMatch(
                            in: a.localizedName ?? "", options: [],
                            range: NSMakeRange(0, a.localizedName?.utf16.count ?? 0)
                        )
                        return nameMatch != nil
                    }).first
            }

            // patial bundle id match
            if app == nil {
                app =
                    apps.filter({ a in
                        let bundleMatch = expression.firstMatch(
                            in: a.bundleIdentifier ?? "", options: [],
                            range: NSMakeRange(0, a.bundleIdentifier?.utf16.count ?? 0))
                        return bundleMatch != nil
                    }).first
            }
        }

        return app
    }

    func activate() throws {
        let app = try! self.find()

        if app == nil && appName != nil {
            throw RuntimeError(
                "Application \(appName!) cannot be activated. Run `sendkeys apps` to see a list of applications that can be activated."
            )
        }

        if app != nil {
            self.application = app

            self.unhideAppIfNeeded()
            self.activateAppIfNeeded()
        }
    }

    private func unhideAppIfNeeded() {
        if application.isHidden {
            application.addObserver(self, forKeyPath: "isHidden", options: .new, context: nil)
            application.unhide()
        }
    }

    private func activateAppIfNeeded() {
        if !application.isHidden && !application.isActive {
            application.addObserver(self, forKeyPath: "isActive", options: .new, context: nil)
            application.activate(options: .activateIgnoringOtherApps)
        }
    }

    override func observeValue(
        forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?,
        context: UnsafeMutableRawPointer?
    ) {
        if keyPath == "isHidden" {
            application.removeObserver(self, forKeyPath: "isHidden")
            activateAppIfNeeded()
        } else if keyPath == "isActive" {
            application.removeObserver(self, forKeyPath: "isActive")
        }
    }
}


================================================
FILE: Sources/SendKeysLib/AppLister.swift
================================================
import ArgumentParser
import Cocoa
import Foundation

class AppLister: ParsableCommand {
    public static let configuration = CommandConfiguration(
        commandName: "apps",
        abstract:
            "Lists apps that can be used with the send command."
    )

    struct AppInfo: Hashable {
        let name: String?
        let id: String?

        init(name: String?, id: String?) {
            self.name = name
            self.id = id
        }

        static func == (lhs: AppInfo, rhs: AppInfo) -> Bool {
            return lhs.name == rhs.name && lhs.id == rhs.id
        }

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

    required init() {
    }

    func run() {
        let apps = Set(
            NSWorkspace.shared.runningApplications.filter { app in
                return app.activationPolicy == .regular
            }
            .map { app in
                return AppInfo(name: app.localizedName, id: app.bundleIdentifier)
            }
        )
        .sorted { a, b in
            return a.name?.lowercased() ?? "" < b.name?.lowercased() ?? ""
        }

        let maxLength = apps.reduce(
            0,
            { max, info in
                return info.name?.count ?? 0 > max ? info.name!.count : max
            })

        apps.forEach { info in
            print(
                "\((info.name ?? "-").padding(toLength: maxLength + 4, withPad: " ", startingAt: 0))id:\(info.id ?? "-")"
            )
        }
    }
}


================================================
FILE: Sources/SendKeysLib/Bridge.swift
================================================
func bridge<T: AnyObject>(obj: T) -> UnsafeRawPointer {
    return UnsafeRawPointer(Unmanaged.passUnretained(obj).toOpaque())
}

func bridge<T: AnyObject>(ptr: UnsafeRawPointer) -> T {
    return Unmanaged<T>.fromOpaque(ptr).takeUnretainedValue()
}

func bridgeRetained<T: AnyObject>(obj: T) -> UnsafeRawPointer {
    return UnsafeRawPointer(Unmanaged.passRetained(obj).toOpaque())
}

func bridgeTransfer<T: AnyObject>(ptr: UnsafeRawPointer) -> T {
    return Unmanaged<T>.fromOpaque(ptr).takeRetainedValue()
}


================================================
FILE: Sources/SendKeysLib/Commands/Command.swift
================================================
import Foundation

public enum CommandType {
    case undefined
    case keyPress
    case keyDown
    case keyUp
    case pause
    case stickyPause
    case mouseMove
    case mousePath
    case mouseClick
    case mouseDrag
    case mouseScroll
    case mouseDown
    case mouseUp
    case continuation
}

public protocol CommandProtocol {
    static var commandType: CommandType { get }
    static var expression: NSRegularExpression { get }

    init(arguments: [String?])
    func execute() throws
    func equals(_ comparison: Command) -> Bool
}

protocol RequiresKeyPresser {
    var keyPresser: KeyPresser? { get set }
}

protocol RequiresMouseController {
    var mouseController: MouseController? { get set }
}

public class Command: Equatable, CustomStringConvertible {
    public class var commandType: CommandType { return .undefined }

    private static let _expression = try! NSRegularExpression(pattern: ".")
    public class var expression: NSRegularExpression { return _expression }

    init() {}

    required public init(arguments: [String?]) {
    }

    public func execute() throws {
    }

    public func equals(_ comparison: Command) -> Bool {
        return type(of: self) == type(of: comparison)
    }

    public static func == (lhs: Command, rhs: Command) -> Bool {
        return lhs.equals(rhs) && rhs.equals(lhs)
    }

    public var description: String {
        let output = "\(type(of: self)): \(type(of: self).commandType)"
        let members = describeMembers()

        if !members.isEmpty {
            return "\(output) (\(members))"
        }

        return output
    }

    func describeMembers() -> String {
        return ""
    }
}


================================================
FILE: Sources/SendKeysLib/Commands/CommandExecutor.swift
================================================
import Foundation

public protocol CommandExecutorProtocol {
    func execute(_ command: Command)
}

public class CommandExecutor: CommandExecutorProtocol {
    public func execute(_ command: Command) {
        try! command.execute()
    }
}


================================================
FILE: Sources/SendKeysLib/Commands/CommandFactory.swift
================================================
public class CommandFactory {
    public static let commands: [Command.Type] = [
        KeyPressCommand.self,
        KeyDownCommand.self,
        KeyUpCommand.self,
        StickyPauseCommand.self,
        PauseCommand.self,
        ContinuationCommand.self,
        NewlineCommand.self,
        MouseMoveCommand.self,
        MousePathCommand.self,
        MouseClickCommand.self,
        MouseDragCommand.self,
        MouseScrollCommand.self,
        MouseDownCommand.self,
        MouseUpCommand.self,
        MouseFocusCommand.self,
        DefaultCommand.self,
    ]

    let keyPresser: KeyPresser
    let mouseController: MouseController

    init(keyPresser: KeyPresser, mouseController: MouseController) {
        self.keyPresser = keyPresser
        self.mouseController = mouseController
    }

    convenience public init(keyPresser: KeyPresser) {
        self.init(
            keyPresser: keyPresser,
            mouseController: MouseController(animationRefreshInterval: 0.01, keyPresser: keyPresser))
    }

    public func create(_ commandType: Command.Type, arguments: [String?]) -> Command {
        let command = commandType.init(arguments: arguments)

        if var keyCommand = command as? RequiresKeyPresser {
            keyCommand.keyPresser = keyPresser
        }

        if var mouseCommand = command as? RequiresMouseController {
            mouseCommand.mouseController = mouseController
        }

        return command
    }
}


================================================
FILE: Sources/SendKeysLib/Commands/CommandsIterator.swift
================================================
import Foundation

public class CommandsIterator: IteratorProtocol {
    public typealias Element = Command

    let commandString: String
    let commandFactory: CommandFactory

    var index = 0

    public init(_ commandString: String, commandFactory: CommandFactory) {
        self.commandString = commandString
        self.commandFactory = commandFactory
    }

    public func next() -> Element? {
        let length = commandString.utf16.count
        if index < length {
            var matchResult: NSTextCheckingResult?
            if let commandType = CommandFactory.commands.first(where: { (commandType: Command.Type) -> Bool in
                matchResult = commandType.expression.firstMatch(
                    in: commandString, options: .anchored, range: NSMakeRange(index, length - index))
                return matchResult != nil
            }
            ) {
                let args = getArguments(commandString, matchResult!)
                let command = commandFactory.create(commandType, arguments: args)

                if matchResult != nil {
                    let range = Range(matchResult!.range, in: commandString)
                    index = range!.upperBound.utf16Offset(in: commandString)
                }

                return command
            } else {
                fatalError("Unmatched sequence.\n")
            }
        }
        return nil
    }

    private func getArguments(_ commandString: String, _ matchResult: NSTextCheckingResult) -> [String?] {
        var args: [String?] = []
        let numberOfRanges = matchResult.numberOfRanges

        for i in 0..<numberOfRanges {
            let range = Range(matchResult.range(at: i), in: commandString)
            let arg = range == nil ? nil : String(commandString[range!])
            args.append(arg)
        }

        return args
    }
}


================================================
FILE: Sources/SendKeysLib/Commands/CommandsProcessor.swift
================================================
import Foundation

public class CommandsProcessor {
    var defaultPause: TimeInterval

    let numberFormatter = NumberFormatter()
    let commandExecutor: CommandExecutorProtocol
    let keyPresser: KeyPresser
    let mouseController: MouseController

    init(
        defaultPause: Double, keyPresser: KeyPresser, mouseController: MouseController,
        commandExecutor: CommandExecutorProtocol? = nil
    ) {
        self.defaultPause = defaultPause
        self.commandExecutor = commandExecutor ?? CommandExecutor()
        self.keyPresser = keyPresser
        self.mouseController = mouseController

        numberFormatter.usesSignificantDigits = true
        numberFormatter.minimumSignificantDigits = 1
        numberFormatter.maximumSignificantDigits = 3
    }

    convenience public init(
        defaultPause: Double, keyPresser: KeyPresser, commandExecutor: CommandExecutorProtocol? = nil
    ) {
        self.init(
            defaultPause: defaultPause, keyPresser: keyPresser,
            mouseController: MouseController(animationRefreshInterval: 0.01, keyPresser: keyPresser),
            commandExecutor: commandExecutor)
    }

    private func getDefaultPauseCommand() -> Command {
        return PauseCommand(duration: defaultPause)
    }

    public func process(_ commandString: String) {
        let commandFactory = CommandFactory(keyPresser: keyPresser, mouseController: mouseController)
        let commands = IteratorSequence(CommandsIterator(commandString, commandFactory: commandFactory))
        var shouldDefaultPause = false
        var shouldIgnoreNextCommand = false

        for command in commands {
            if shouldIgnoreNextCommand {
                shouldIgnoreNextCommand = false
                continue
            }

            if command is ContinuationCommand {
                shouldIgnoreNextCommand = true
                continue
            }

            if command is StickyPauseCommand {
                shouldDefaultPause = false
                defaultPause = (command as! StickyPauseCommand).duration
            } else if command is PauseCommand {
                shouldDefaultPause = false
            } else if shouldDefaultPause {
                commandExecutor.execute(getDefaultPauseCommand())
                shouldDefaultPause = true
            } else {
                shouldDefaultPause = true
            }

            commandExecutor.execute(command)
        }
    }
}


================================================
FILE: Sources/SendKeysLib/Commands/ContinuationCommand.swift
================================================
import Foundation

public class ContinuationCommand: Command {
    public override class var commandType: CommandType { return .continuation }

    private static let _expression = try! NSRegularExpression(pattern: "\\<\\\\\\>")
    public override class var expression: NSRegularExpression { return _expression }

    public override init() {
        super.init()
    }

    required public init(arguments: [String?]) {
        super.init(arguments: arguments)
    }
}


================================================
FILE: Sources/SendKeysLib/Commands/DefaultCommand.swift
================================================
import Foundation

public class DefaultCommand: KeyPressCommand {
    private static let _expression = try! NSRegularExpression(pattern: ".")
    public override class var expression: NSRegularExpression { return _expression }

    public init(key: String) {
        super.init()

        self.key = key
    }

    required public init(arguments: [String?]) {
        super.init()
        self.key = arguments[0]!
    }
}


================================================
FILE: Sources/SendKeysLib/Commands/KeyDownCommand.swift
================================================
import Foundation

public class KeyDownCommand: KeyPressCommand {
    public override class var commandType: CommandType { return .keyDown }

    private static let _expression = try! NSRegularExpression(pattern: "\\<kd:(.|[\\w]+)(:([,\\w⌘^⌥⇧]+))?\\>")
    public override class var expression: NSRegularExpression { return _expression }

    public override init(key: String, modifiers: [String]) {
        super.init(key: key, modifiers: modifiers)
    }

    required public init(arguments: [String?]) {
        super.init(arguments: arguments)
    }

    public override func execute() throws {
        let _ = try! keyPresser!.keyDown(key: key!, modifiers: modifiers)
    }
}


================================================
FILE: Sources/SendKeysLib/Commands/KeyPressCommand.swift
================================================
import Foundation

public class KeyPressCommand: Command, RequiresKeyPresser {
    public override class var commandType: CommandType { return .keyPress }

    private static let _expression = try! NSRegularExpression(pattern: "\\<[ck]:(.|[\\w]+)(:([,\\w⌘^⌥⇧]+))?\\>")
    public override class var expression: NSRegularExpression { return _expression }

    var key: String?
    var modifiers: [String] = []

    var keyPresser: KeyPresser?

    override init() {
        super.init()
    }

    public init(key: String, modifiers: [String]) {
        super.init()

        self.key = key
        self.modifiers = modifiers
    }

    required public init(arguments: [String?]) {
        super.init()

        self.key = arguments[1]!
        self.modifiers = arguments[3]?.components(separatedBy: ",").filter({ !$0.isEmpty }) ?? []
    }

    public override func execute() throws {
        let _ = try! keyPresser!.keyPress(key: key!, modifiers: modifiers)
    }

    public override func equals(_ comparison: Command) -> Bool {
        return super.equals(comparison)
            && {
                if let command = comparison as? KeyPressCommand {
                    return key == command.key
                        && modifiers == command.modifiers
                }
                return false
            }()
    }

    public override func describeMembers() -> String {
        return "key: \(key ?? "''")), modifiers: \(modifiers)"
    }
}


================================================
FILE: Sources/SendKeysLib/Commands/KeyUpCommand.swift
================================================
import Foundation

public class KeyUpCommand: KeyPressCommand {
    public override class var commandType: CommandType { return .keyUp }

    private static let _expression = try! NSRegularExpression(pattern: "\\<ku:(.|[\\w]+)(:([,\\w⌘^⌥⇧]+))?\\>")
    public override class var expression: NSRegularExpression { return _expression }

    public override init(key: String, modifiers: [String]) {
        super.init(key: key, modifiers: modifiers)
    }

    required public init(arguments: [String?]) {
        super.init(arguments: arguments)
    }

    public override func execute() throws {
        let _ = try! keyPresser!.keyUp(key: key!, modifiers: modifiers)
    }
}


================================================
FILE: Sources/SendKeysLib/Commands/MouseClickCommand.swift
================================================
import Cocoa
import Foundation

public class MouseClickCommand: Command, RequiresMouseController {
    public override class var commandType: CommandType { return .mouseClick }

    private static let _expression = try! NSRegularExpression(pattern: "\\<m:([a-z]+)(:([a-z,]+))?(:(\\d+))?\\>")
    public override class var expression: NSRegularExpression { return _expression }

    var button: String?
    var modifiers: [String] = []
    var clicks: Int = 1

    var mouseController: MouseController?

    override init() {
        super.init()
    }

    public init(button: String?, modifiers: [String], clicks: Int) {
        super.init()

        self.button = button
        self.modifiers = modifiers
        self.clicks = clicks
    }

    required public init(arguments: [String?]) {
        super.init()

        self.button = arguments[1]!
        self.modifiers = arguments[3]?.components(separatedBy: ",").filter({ !$0.isEmpty }) ?? []
        self.clicks = Int(arguments[5] ?? "1")!
    }

    public override func execute() throws {
        try! mouseController!.click(
            nil,
            button: getMouseButton(button: button!),
            flags: try! KeyPresser.getModifierFlags(modifiers),
            clickCount: clicks
        )
    }

    public override func equals(_ comparison: Command) -> Bool {
        return super.equals(comparison)
            && {
                if let command = comparison as? MouseClickCommand {
                    return button == command.button
                        && modifiers == command.modifiers
                        && clicks == command.clicks
                }
                return false
            }()
    }

    public override func describeMembers() -> String {
        return "button: \(button ?? "''")), modifiers: \(modifiers), clicks: \(clicks)"
    }

    func getMouseButton(button: String) throws -> CGMouseButton {
        switch button {
        case "left":
            return CGMouseButton.left
        case "center":
            return CGMouseButton.center
        case "right":
            return CGMouseButton.right
        default:
            throw RuntimeError("Unknown mouse button: \(button)")
        }
    }
}


================================================
FILE: Sources/SendKeysLib/Commands/MouseDownCommand.swift
================================================
import Foundation

public class MouseDownCommand: MouseClickCommand {
    public override class var commandType: CommandType { return .mouseDown }

    private static let _expression = try! NSRegularExpression(pattern: "\\<md:([a-z]+)(:([a-z,]+))?\\>")
    public override class var expression: NSRegularExpression { return _expression }

    override init() {
        super.init()
    }

    public init(button: String?, modifiers: [String]) {
        super.init()

        self.button = button
        self.modifiers = modifiers
    }

    required public init(arguments: [String?]) {
        super.init()

        self.button = arguments[1]!
        self.modifiers = arguments[3]?.components(separatedBy: ",").filter({ !$0.isEmpty }) ?? []
    }

    public override func execute() throws {
        try! mouseController!.down(
            nil,
            button: getMouseButton(button: button!),
            flags: try! KeyPresser.getModifierFlags(modifiers)
        )
    }
}


================================================
FILE: Sources/SendKeysLib/Commands/MouseDragCommand.swift
================================================
import Foundation

public class MouseDragCommand: MouseMoveCommand {
    public override class var commandType: CommandType { return .mouseDrag }

    private static let _expression = try! NSRegularExpression(
        pattern: "\\<d:((-?[.\\d]+),(-?[.\\d]+),)?(-?[.\\d]+),(-?[.\\d]+)(:([\\d.]+))?(:([a-z]+))?(:([a-z,]+))?\\>")
    public override class var expression: NSRegularExpression { return _expression }

    public init(
        x1: Double?, y1: Double?, x2: Double, y2: Double, duration: TimeInterval, button: String?, modifiers: [String]
    ) {
        super.init()

        self.x1 = x1
        self.y1 = y1
        self.x2 = x2
        self.y2 = y2
        self.duration = duration
        self.button = button
        self.modifiers = modifiers
    }

    required public init(arguments: [String?]) {
        super.init()
        self.x1 = Double(arguments[2] ?? "")
        self.y1 = Double(arguments[3] ?? "")
        self.x2 = Double(arguments[4]!)!
        self.y2 = Double(arguments[5]!)!
        self.duration = TimeInterval(arguments[7] ?? "0")!
        self.button = arguments[9] ?? "left"
        self.modifiers = arguments[11]?.components(separatedBy: ",").filter({ !$0.isEmpty }) ?? []
    }

    public override func describeMembers() -> String {
        return
            "x1: \(x1?.description ?? "nil")), y1: \(y1?.description ?? "nil"), x2: \(x2), y2: \(y2), duration: \(duration), button: \(button ?? "''")), modifiers: \(modifiers)"
    }

    public override func execute() throws {
        try! mouseController!.drag(
            start: x1 == nil || y1 == nil ? nil : CGPoint(x: x1!, y: y1!),
            end: CGPoint(x: x2, y: y2),
            duration: duration,
            button: getMouseButton(button: button!),
            flags: try! KeyPresser.getModifierFlags(modifiers)
        )
    }
}


================================================
FILE: Sources/SendKeysLib/Commands/MouseFocusCommand.swift
================================================
import Foundation

public class MouseFocusCommand: MouseClickCommand {
    public override class var commandType: CommandType { return .mouseScroll }

    // <mf:centerX,centerY:radiusX[,radiusY]:angleFrom,angleTo:duration>
    private static let _expression = try! NSRegularExpression(
        pattern: "\\<mf:(-?[.\\d]+),(-?[.\\d]+):([.\\d]+)(,([.\\d]+))?:(-?[.\\d]+),(-?[.\\d]+):([.\\d]+)\\>")
    public override class var expression: NSRegularExpression { return _expression }

    var x: Double
    var y: Double
    var rx: Double
    var ry: Double
    var from: Double
    var to: Double
    var duration: TimeInterval

    public init(
        x: Double, y: Double, rx: Double, ry: Double, angleFrom: Double, angleTo: Double, duration: TimeInterval
    ) {
        self.x = x
        self.y = y
        self.rx = rx
        self.ry = ry
        self.from = angleFrom
        self.to = angleTo
        self.duration = duration

        super.init()
    }

    required public init(arguments: [String?]) {
        self.x = Double(arguments[1]!)!
        self.y = Double(arguments[2]!)!
        self.rx = Double(arguments[3]!)!
        self.ry = Double(arguments[5] ?? arguments[3]!)!
        self.from = Double(arguments[6]!)!
        self.to = Double(arguments[7]!)!
        self.duration = TimeInterval(arguments[8]!)!

        super.init()
    }

    public override func execute() throws {
        mouseController!.circle(CGPoint(x: x, y: y), CGPoint(x: rx, y: ry), from, to, duration)
    }

    public override func describeMembers() -> String {
        return "x: \(x), y: \(y), rx: \(rx), ry: \(ry), from: \(from), to: \(to), duration: \(duration)"
    }

    public override func equals(_ comparison: Command) -> Bool {
        return super.equals(comparison)
            && {
                if let command = comparison as? MouseFocusCommand {
                    return x == command.x
                        && y == command.y
                        && rx == command.rx
                        && ry == command.ry
                        && from == command.from
                        && to == command.to
                        && duration == command.duration
                }
                return false
            }()
    }
}


================================================
FILE: Sources/SendKeysLib/Commands/MouseMoveCommand.swift
================================================
import Foundation

public class MouseMoveCommand: MouseClickCommand {
    public override class var commandType: CommandType { return .mouseMove }

    private static let _expression = try! NSRegularExpression(
        pattern: "\\<m:((-?[.\\d]+),(-?[.\\d]+),)?(-?[.\\d]+),(-?[.\\d]+)(:([\\d.]+))?(:([a-z,]+))?\\>")
    public override class var expression: NSRegularExpression { return _expression }

    var x1: Double?
    var y1: Double?
    var x2: Double = 0
    var y2: Double = 0
    var duration: TimeInterval = 0

    override init() {
        super.init()
    }

    public init(x1: Double?, y1: Double?, x2: Double, y2: Double, duration: TimeInterval, modifiers: [String]) {
        super.init()

        self.x1 = x1
        self.y1 = y1
        self.x2 = x2
        self.y2 = y2
        self.duration = duration
        self.modifiers = modifiers
    }

    required public init(arguments: [String?]) {
        super.init()
        self.x1 = Double(arguments[2] ?? "")
        self.y1 = Double(arguments[3] ?? "")
        self.x2 = Double(arguments[4]!)!
        self.y2 = Double(arguments[5]!)!
        self.duration = TimeInterval(arguments[7] ?? "0")!
        self.modifiers = arguments[9]?.components(separatedBy: ",").filter({ !$0.isEmpty }) ?? []
    }

    public override func execute() throws {
        mouseController!.move(
            start: x1 == nil || y1 == nil ? nil : CGPoint(x: x1!, y: y1!),
            end: CGPoint(x: x2, y: y2),
            duration: duration,
            flags: try! KeyPresser.getModifierFlags(modifiers)
        )
    }

    public override func equals(_ comparison: Command) -> Bool {
        return super.equals(comparison)
            && {
                if let command = comparison as? MouseMoveCommand {
                    return x1 == command.x1
                        && y1 == command.y1
                        && x2 == command.x2
                        && y2 == command.y2
                        && duration == command.duration
                }
                return false
            }()
    }

    public override func describeMembers() -> String {
        return
            "x1: \(x1?.description ?? "nil")), y1: \(y1?.description ?? "nil"), x2: \(x2), y2: \(y2), duration: \(duration)"
    }
}


================================================
FILE: Sources/SendKeysLib/Commands/MousePathCommand.swift
================================================
import Foundation

public class MousePathCommand: MouseClickCommand {
    public override class var commandType: CommandType { return .mousePath }

    private static let _expression = try! NSRegularExpression(
        pattern:
            "\\<mpath:([^:\\>]+)(:(-?[\\d.]+),(-?[\\d.]+)(,(-?[\\d.]+)(,(-?[\\d.]+))?)?)?:([\\d.]+)(:([a-z,]+))?\\>")
    public override class var expression: NSRegularExpression { return _expression }

    var path: String
    var offsetX: Double = 0
    var offsetY: Double = 0
    var scaleX: Double = 1
    var scaleY: Double = 1
    var duration: TimeInterval = 0

    public init(
        path: String, offsetX: Double, offsetY: Double, scaleX: Double, scaleY: Double, duration: TimeInterval,
        modifiers: [String]
    ) {
        self.path = path
        self.offsetX = offsetX
        self.offsetY = offsetY
        self.scaleX = scaleX
        self.scaleY = scaleY
        self.duration = duration

        super.init()
        self.modifiers = modifiers
    }

    required public init(arguments: [String?]) {
        self.path = arguments[1]!
        self.offsetX = Double(arguments[3] ?? "0")!
        self.offsetY = Double(arguments[4] ?? "0")!
        self.scaleX = Double(arguments[6] ?? "1")!
        self.scaleY = Double(arguments[8] ?? arguments[6] ?? "1")!
        self.duration = TimeInterval(arguments[9] ?? "0")!

        super.init()
        self.modifiers = arguments[10]?.components(separatedBy: ",").filter({ !$0.isEmpty }) ?? []
    }

    public override func execute() throws {
        mouseController!.move(
            start: nil,
            path: path,
            offset: CGPoint(x: offsetX, y: offsetY),
            scale: CGPoint(x: scaleX, y: scaleY),
            duration: duration,
            flags: try! KeyPresser.getModifierFlags(modifiers)
        )
    }

    public override func equals(_ comparison: Command) -> Bool {
        return super.equals(comparison)
            && {
                if let command = comparison as? MousePathCommand {
                    return path == command.path
                        && offsetX == command.offsetX
                        && offsetY == command.offsetY
                        && scaleX == command.scaleX
                        && scaleY == command.scaleY
                        && duration == command.duration
                }
                return false
            }()
    }

    public override func describeMembers() -> String {
        return
            "path: \(path), offsetX: \(offsetX), offsetY: \(offsetY), scaleX: \(scaleX), scaleY: \(scaleY), duration: \(duration)"
    }
}


================================================
FILE: Sources/SendKeysLib/Commands/MouseScrollCommand.swift
================================================
import Foundation

public class MouseScrollCommand: MouseClickCommand {
    public override class var commandType: CommandType { return .mouseScroll }

    private static let _expression = try! NSRegularExpression(
        pattern: "\\<s:(-?[.\\d]+),(-?[.\\d]+)(:([.\\d]+))?(:([a-z,]+))?\\>")
    public override class var expression: NSRegularExpression { return _expression }

    var x: Double
    var y: Double
    var duration: TimeInterval

    public init(x: Double, y: Double, duration: TimeInterval, modifiers: [String]) {
        self.x = x
        self.y = y
        self.duration = duration

        super.init()
        self.modifiers = modifiers
    }

    required public init(arguments: [String?]) {
        self.x = Double(arguments[1]!)!
        self.y = Double(arguments[2]!)!
        self.duration = TimeInterval(arguments[4] ?? "0")!

        super.init()
        self.modifiers = arguments[6]?.components(separatedBy: ",").filter({ !$0.isEmpty }) ?? []
    }

    public override func execute() throws {
        mouseController!.scroll(
            CGPoint(x: x, y: y),
            duration,
            flags: try! KeyPresser.getModifierFlags(modifiers)
        )
    }

    public override func describeMembers() -> String {
        return "x: \(x), y: \(y), duration: \(duration), modifiers: \(modifiers)"
    }

    public override func equals(_ comparison: Command) -> Bool {
        return super.equals(comparison)
            && {
                if let command = comparison as? MouseScrollCommand {
                    return x == command.x
                        && y == command.y
                        && duration == command.duration
                }
                return false
            }()
    }
}


================================================
FILE: Sources/SendKeysLib/Commands/MouseUpCommand.swift
================================================
import Foundation

public class MouseUpCommand: MouseClickCommand {
    public override class var commandType: CommandType { return .mouseUp }

    private static let _expression = try! NSRegularExpression(pattern: "\\<mu:([a-z]+)(:([a-z,]+))?\\>")
    public override class var expression: NSRegularExpression { return _expression }

    override init() {
        super.init()
    }

    public init(button: String?, modifiers: [String]) {
        super.init()

        self.button = button
        self.modifiers = modifiers
    }

    required public init(arguments: [String?]) {
        super.init()

        self.button = arguments[1]!
        self.modifiers = arguments[3]?.components(separatedBy: ",").filter({ !$0.isEmpty }) ?? []
    }

    public override func execute() throws {
        try! mouseController!.up(
            nil,
            button: getMouseButton(button: button!),
            flags: try! KeyPresser.getModifierFlags(modifiers)
        )
    }
}


================================================
FILE: Sources/SendKeysLib/Commands/NewlineCommand.swift
================================================
import Foundation

public class NewlineCommand: KeyPressCommand {
    private static let _expression = try! NSRegularExpression(pattern: "\\r?\\n")
    public override class var expression: NSRegularExpression { return _expression }

    public override init() {
        super.init()
        self.key = "return"
    }

    required public convenience init(arguments: [String?]) {
        self.init()
    }
}


================================================
FILE: Sources/SendKeysLib/Commands/PauseCommand.swift
================================================
import Foundation

public class PauseCommand: Command {
    public override class var commandType: CommandType { return .pause }

    private static let _expression = try! NSRegularExpression(pattern: "\\<p:([\\d.]+)\\>")
    public override class var expression: NSRegularExpression { return _expression }

    var duration: TimeInterval = 0

    init(duration: TimeInterval) {
        super.init()

        self.duration = duration
    }

    required public init(arguments: [String?]) {
        super.init()

        self.duration = TimeInterval(arguments[1]!)!
    }

    public override func execute() throws {
        Sleeper.sleep(seconds: duration)
    }

    public override func equals(_ comparison: Command) -> Bool {
        return super.equals(comparison)
            && {
                if let command = comparison as? PauseCommand {
                    return duration == command.duration
                }
                return false
            }()
    }

    public override func describeMembers() -> String {
        return "duration: \(duration)"
    }
}


================================================
FILE: Sources/SendKeysLib/Commands/StickyPauseCommand.swift
================================================
import Foundation

public class StickyPauseCommand: PauseCommand {
    public override class var commandType: CommandType { return .stickyPause }

    private static let _expression = try! NSRegularExpression(pattern: "\\<P:([\\d.]+)\\>")
    public override class var expression: NSRegularExpression { return _expression }
}


================================================
FILE: Sources/SendKeysLib/Configuration/AllConfiguration.swift
================================================
struct AllConfiguration: Codable {
    var send: SendConfig?
    var mousePosition: MousePositionConfig?
    var transformer: TransformerConfig?

    init(send: SendConfig? = nil, mousePosition: MousePositionConfig? = nil, transformer: TransformerConfig? = nil) {
        self.send = send
        self.mousePosition = mousePosition
        self.transformer = transformer
    }

    func merge(with other: AllConfiguration?) -> AllConfiguration {
        return AllConfiguration(
            send: other?.send?.merge(with: self.send) ?? self.send,
            mousePosition: other?.mousePosition?.merge(with: self.mousePosition) ?? self.mousePosition,
            transformer: other?.transformer?.merge(with: self.transformer) ?? self.transformer
        )
    }
}


================================================
FILE: Sources/SendKeysLib/Configuration/ConfigLoader.swift
================================================
import Foundation
import Yams

let defaultConfigFiles = [
    NSString("~/.sendkeysrc.yml").expandingTildeInPath,
    NSString("~/.sendkeysrc.yaml").expandingTildeInPath,
]

struct ConfigLoader {
    static func loadConfig(_ file: String? = nil) -> AllConfiguration {
        var config = AllConfiguration()

        let configFiles =
            if file != nil {
                [file!]
            } else {
                defaultConfigFiles
            }

        for configFile in configFiles {
            if !configFile.isEmpty && FileManager.default.fileExists(atPath: configFile) {
                if let contents = FileManager.default.contents(atPath: configFile) {
                    do {
                        let decoder = YAMLDecoder()
                        config = config.merge(with: try decoder.decode(AllConfiguration.self, from: contents))
                    } catch {
                        print("Unable to read \(configFile): \(error)")
                    }
                }
            }
        }

        return config
    }
}


================================================
FILE: Sources/SendKeysLib/Configuration/MousePositionConfig.swift
================================================
struct MousePositionConfig: Codable {
    var watch: Bool?
    var output: OutputMode?
    var duration: Double?

    init(watch: Bool? = nil, output: OutputMode? = nil, duration: Double? = nil) {
        self.watch = watch
        self.output = output
        self.duration = duration
    }

    func merge(with other: MousePositionConfig?) -> MousePositionConfig {
        return MousePositionConfig(
            watch: other?.watch ?? self.watch,
            output: other?.output ?? self.output,
            duration: other?.duration ?? self.duration
        )
    }
}


================================================
FILE: Sources/SendKeysLib/Configuration/SendConfig.swift
================================================
struct SendConfig: Codable {
    var activate: Bool?
    var animationInterval: Double?
    var delay: Double?
    var initialDelay: Double?
    var keyboardLayout: KeyMappings.Layouts?
    var remap: [String: String]?
    var targeted: Bool?
    var terminateCommand: String?

    init(
        activate: Bool? = nil, animationInterval: Double? = nil, delay: Double? = nil, initialDelay: Double? = nil,
        keyboardLayout: KeyMappings.Layouts? = nil, remap: [String: String]? = nil, targeted: Bool? = nil,
        terminateCommand: String? = nil
    ) {
        self.activate = activate
        self.animationInterval = animationInterval
        self.delay = delay
        self.initialDelay = initialDelay
        self.keyboardLayout = keyboardLayout
        self.remap = remap
        self.targeted = targeted
        self.terminateCommand = terminateCommand
    }

    func merge(with other: SendConfig?) -> SendConfig {
        return SendConfig(
            activate: other?.activate ?? self.activate,
            animationInterval: other?.animationInterval ?? self.animationInterval,
            delay: other?.delay ?? self.delay,
            initialDelay: other?.initialDelay ?? self.initialDelay,
            keyboardLayout: other?.keyboardLayout ?? self.keyboardLayout,
            remap: other?.remap ?? self.remap,
            targeted: other?.targeted ?? self.targeted,
            terminateCommand: other?.terminateCommand ?? self.terminateCommand
        )
    }
}


================================================
FILE: Sources/SendKeysLib/Configuration/TransformerConfig.swift
================================================
struct TransformerConfig: Codable {
    var indent: Bool?
    var autoClose: String?

    init(indent: Bool? = nil, autoClose: String? = nil) {
        self.indent = indent
        self.autoClose = autoClose
    }

    func merge(with other: TransformerConfig?) -> TransformerConfig {
        return TransformerConfig(
            indent: other?.indent ?? self.indent,
            autoClose: other?.autoClose ?? self.autoClose
        )
    }
}


================================================
FILE: Sources/SendKeysLib/KeyCodes.swift
================================================
import Cocoa

// From: https://gist.github.com/swillits/df648e87016772c7f7e5dbed2b345066
struct KeyCodes {
    // Layout-independent Keys
    // eg.These key codes are always the same key on all layouts.
    static let returnKey: UInt16 = 0x24
    static let enter: UInt16 = 0x4C
    static let tab: UInt16 = 0x30
    static let space: UInt16 = 0x31
    static let delete: UInt16 = 0x33
    static let escape: UInt16 = 0x35
    static let command: UInt16 = 0x37
    static let shift: UInt16 = 0x38
    static let capsLock: UInt16 = 0x39
    static let option: UInt16 = 0x3A
    static let control: UInt16 = 0x3B
    static let rightShift: UInt16 = 0x3C
    static let rightOption: UInt16 = 0x3D
    static let rightControl: UInt16 = 0x3E
    static let leftArrow: UInt16 = 0x7B
    static let rightArrow: UInt16 = 0x7C
    static let downArrow: UInt16 = 0x7D
    static let upArrow: UInt16 = 0x7E
    static let volumeUp: UInt16 = 0x48
    static let volumeDown: UInt16 = 0x49
    static let mute: UInt16 = 0x4A
    static let help: UInt16 = 0x72
    static let home: UInt16 = 0x73
    static let pageUp: UInt16 = 0x74
    static let forwardDelete: UInt16 = 0x75
    static let end: UInt16 = 0x77
    static let pageDown: UInt16 = 0x79
    static let function: UInt16 = 0x3F
    static let f1: UInt16 = 0x7A
    static let f2: UInt16 = 0x78
    static let f4: UInt16 = 0x76
    static let f5: UInt16 = 0x60
    static let f6: UInt16 = 0x61
    static let f7: UInt16 = 0x62
    static let f3: UInt16 = 0x63
    static let f8: UInt16 = 0x64
    static let f9: UInt16 = 0x65
    static let f10: UInt16 = 0x6D
    static let f11: UInt16 = 0x67
    static let f12: UInt16 = 0x6F
    static let f13: UInt16 = 0x69
    static let f14: UInt16 = 0x6B
    static let f15: UInt16 = 0x71
    static let f16: UInt16 = 0x6A
    static let f17: UInt16 = 0x40
    static let f18: UInt16 = 0x4F
    static let f19: UInt16 = 0x50
    static let f20: UInt16 = 0x5A

    // US-ANSI Keyboard Positions
    // eg. These key codes are for the physical key (in any keyboard layout)
    // at the location of the named key in the US-ANSI layout.
    static let a: UInt16 = 0x00
    static let b: UInt16 = 0x0B
    static let c: UInt16 = 0x08
    static let d: UInt16 = 0x02
    static let e: UInt16 = 0x0E
    static let f: UInt16 = 0x03
    static let g: UInt16 = 0x05
    static let h: UInt16 = 0x04
    static let i: UInt16 = 0x22
    static let j: UInt16 = 0x26
    static let k: UInt16 = 0x28
    static let l: UInt16 = 0x25
    static let m: UInt16 = 0x2E
    static let n: UInt16 = 0x2D
    static let o: UInt16 = 0x1F
    static let p: UInt16 = 0x23
    static let q: UInt16 = 0x0C
    static let r: UInt16 = 0x0F
    static let s: UInt16 = 0x01
    static let t: UInt16 = 0x11
    static let u: UInt16 = 0x20
    static let v: UInt16 = 0x09
    static let w: UInt16 = 0x0D
    static let x: UInt16 = 0x07
    static let y: UInt16 = 0x10
    static let z: UInt16 = 0x06

    static let zero: UInt16 = 0x1D
    static let one: UInt16 = 0x12
    static let two: UInt16 = 0x13
    static let three: UInt16 = 0x14
    static let four: UInt16 = 0x15
    static let five: UInt16 = 0x17
    static let six: UInt16 = 0x16
    static let seven: UInt16 = 0x1A
    static let eight: UInt16 = 0x1C
    static let nine: UInt16 = 0x19

    static let equals: UInt16 = 0x18
    static let minus: UInt16 = 0x1B
    static let semicolon: UInt16 = 0x29
    static let apostrophe: UInt16 = 0x27
    static let comma: UInt16 = 0x2B
    static let period: UInt16 = 0x2F
    static let forwardSlash: UInt16 = 0x2C
    static let backslash: UInt16 = 0x2A
    static let grave: UInt16 = 0x32
    static let leftBracket: UInt16 = 0x21
    static let rightBracket: UInt16 = 0x1E

    static let keypadDecimal: UInt16 = 0x41
    static let keypadMultiply: UInt16 = 0x43
    static let keypadPlus: UInt16 = 0x45
    static let keypadClear: UInt16 = 0x47
    static let keypadDivide: UInt16 = 0x4B
    static let keypadEnter: UInt16 = 0x4C
    static let keypadMinus: UInt16 = 0x4E
    static let keypadEquals: UInt16 = 0x51
    static let keypad0: UInt16 = 0x52
    static let keypad1: UInt16 = 0x53
    static let keypad2: UInt16 = 0x54
    static let keypad3: UInt16 = 0x55
    static let keypad4: UInt16 = 0x56
    static let keypad5: UInt16 = 0x57
    static let keypad6: UInt16 = 0x58
    static let keypad7: UInt16 = 0x59
    static let keypad8: UInt16 = 0x5B
    static let keypad9: UInt16 = 0x5C

    struct KeyCodeWithFlags {
        let keyCode: UInt16
        let flags: [CGEventFlags]

        init(_ keyCode: UInt16, _ flags: [CGEventFlags] = []) {
            self.keyCode = keyCode
            self.flags = flags
        }
    }

    struct KeyWithFlags {
        let key: String
        let flags: [CGEventFlags]

        init(_ key: String, _ flags: [CGEventFlags] = []) {
            self.key = key
            self.flags = flags
        }
    }

    // map
    private static let keyDictionary = [
        "return": KeyCodeWithFlags(returnKey),
        "enter": KeyCodeWithFlags(enter),
        "tab": KeyCodeWithFlags(tab),
        "space": KeyCodeWithFlags(space),
        "delete": KeyCodeWithFlags(delete),
        "escape": KeyCodeWithFlags(escape),
        "esc": KeyCodeWithFlags(escape),
        "⌘": KeyCodeWithFlags(command),
        "cmd": KeyCodeWithFlags(command),
        "command": KeyCodeWithFlags(command),
        "shift": KeyCodeWithFlags(shift),
        "capslock": KeyCodeWithFlags(capsLock),
        "⌥": KeyCodeWithFlags(option),
        "alt": KeyCodeWithFlags(option),
        "option": KeyCodeWithFlags(option),
        "ctrl": KeyCodeWithFlags(control),
        "control": KeyCodeWithFlags(control),
        "rightshift": KeyCodeWithFlags(rightShift),
        "rightoption": KeyCodeWithFlags(rightOption),
        "rightControl": KeyCodeWithFlags(rightControl),
        "left": KeyCodeWithFlags(leftArrow),
        "right": KeyCodeWithFlags(rightArrow),
        "down": KeyCodeWithFlags(downArrow),
        "up": KeyCodeWithFlags(upArrow),
        "volumeup": KeyCodeWithFlags(volumeUp),
        "volumedown": KeyCodeWithFlags(volumeDown),
        "mute": KeyCodeWithFlags(mute),
        "help": KeyCodeWithFlags(help),
        "home": KeyCodeWithFlags(home),
        "pgup": KeyCodeWithFlags(pageUp),
        "forwarddelete": KeyCodeWithFlags(forwardDelete),
        "end": KeyCodeWithFlags(end),
        "pgdown": KeyCodeWithFlags(pageDown),
        "fn": KeyCodeWithFlags(function),
        "function": KeyCodeWithFlags(function),
        "f1": KeyCodeWithFlags(f1),
        "f2": KeyCodeWithFlags(f2),
        "f4": KeyCodeWithFlags(f4),
        "f5": KeyCodeWithFlags(f5),
        "f6": KeyCodeWithFlags(f6),
        "f7": KeyCodeWithFlags(f7),
        "f3": KeyCodeWithFlags(f3),
        "f8": KeyCodeWithFlags(f8),
        "f9": KeyCodeWithFlags(f9),
        "f10": KeyCodeWithFlags(f10),
        "f11": KeyCodeWithFlags(f11),
        "f12": KeyCodeWithFlags(f12),
        "f13": KeyCodeWithFlags(f13),
        "f14": KeyCodeWithFlags(f14),
        "f15": KeyCodeWithFlags(f15),
        "f16": KeyCodeWithFlags(f16),
        "f17": KeyCodeWithFlags(f17),
        "f18": KeyCodeWithFlags(f18),
        "f19": KeyCodeWithFlags(f19),
        "f20": KeyCodeWithFlags(f20),
        "a": KeyCodeWithFlags(a),
        "b": KeyCodeWithFlags(b),
        "c": KeyCodeWithFlags(c),
        "d": KeyCodeWithFlags(d),
        "e": KeyCodeWithFlags(e),
        "f": KeyCodeWithFlags(f),
        "g": KeyCodeWithFlags(g),
        "h": KeyCodeWithFlags(h),
        "i": KeyCodeWithFlags(i),
        "j": KeyCodeWithFlags(j),
        "k": KeyCodeWithFlags(k),
        "l": KeyCodeWithFlags(l),
        "m": KeyCodeWithFlags(m),
        "n": KeyCodeWithFlags(n),
        "o": KeyCodeWithFlags(o),
        "p": KeyCodeWithFlags(p),
        "q": KeyCodeWithFlags(q),
        "r": KeyCodeWithFlags(r),
        "s": KeyCodeWithFlags(s),
        "t": KeyCodeWithFlags(t),
        "u": KeyCodeWithFlags(u),
        "v": KeyCodeWithFlags(v),
        "w": KeyCodeWithFlags(w),
        "x": KeyCodeWithFlags(x),
        "y": KeyCodeWithFlags(y),
        "z": KeyCodeWithFlags(z),
        "0": KeyCodeWithFlags(zero),
        "1": KeyCodeWithFlags(one),
        "2": KeyCodeWithFlags(two),
        "3": KeyCodeWithFlags(three),
        "4": KeyCodeWithFlags(four),
        "5": KeyCodeWithFlags(five),
        "6": KeyCodeWithFlags(six),
        "7": KeyCodeWithFlags(seven),
        "8": KeyCodeWithFlags(eight),
        "9": KeyCodeWithFlags(nine),
        "=": KeyCodeWithFlags(equals),
        "-": KeyCodeWithFlags(minus),
        ";": KeyCodeWithFlags(semicolon),
        "'": KeyCodeWithFlags(apostrophe),
        ",": KeyCodeWithFlags(comma),
        ".": KeyCodeWithFlags(period),
        "/": KeyCodeWithFlags(forwardSlash),
        "\\": KeyCodeWithFlags(backslash),
        "`": KeyCodeWithFlags(grave),
        "[": KeyCodeWithFlags(leftBracket),
        "]": KeyCodeWithFlags(rightBracket),
        "keypaddecimal": KeyCodeWithFlags(keypadDecimal),
        "keypadmultiply": KeyCodeWithFlags(keypadMultiply),
        "keypadplus": KeyCodeWithFlags(keypadPlus),
        "keypadclear": KeyCodeWithFlags(keypadClear),
        "keypaddivide": KeyCodeWithFlags(keypadDivide),
        "keypadenter": KeyCodeWithFlags(keypadEnter),
        "keypadminus": KeyCodeWithFlags(keypadMinus),
        "keypadequals": KeyCodeWithFlags(keypadEquals),
        "keypad0": KeyCodeWithFlags(keypad0),
        "keypad1": KeyCodeWithFlags(keypad1),
        "keypad2": KeyCodeWithFlags(keypad2),
        "keypad3": KeyCodeWithFlags(keypad3),
        "keypad4": KeyCodeWithFlags(keypad4),
        "keypad5": KeyCodeWithFlags(keypad5),
        "keypad6": KeyCodeWithFlags(keypad6),
        "keypad7": KeyCodeWithFlags(keypad7),
        "keypad8": KeyCodeWithFlags(keypad8),
        "keypad9": KeyCodeWithFlags(keypad9),
    ]

    private static let modifierKeyDictionary = [
        "A": KeyWithFlags("a", [.maskShift]),
        "B": KeyWithFlags("b", [.maskShift]),
        "C": KeyWithFlags("c", [.maskShift]),
        "D": KeyWithFlags("d", [.maskShift]),
        "E": KeyWithFlags("e", [.maskShift]),
        "F": KeyWithFlags("f", [.maskShift]),
        "G": KeyWithFlags("g", [.maskShift]),
        "H": KeyWithFlags("h", [.maskShift]),
        "I": KeyWithFlags("i", [.maskShift]),
        "J": KeyWithFlags("j", [.maskShift]),
        "K": KeyWithFlags("k", [.maskShift]),
        "L": KeyWithFlags("l", [.maskShift]),
        "M": KeyWithFlags("m", [.maskShift]),
        "N": KeyWithFlags("n", [.maskShift]),
        "O": KeyWithFlags("o", [.maskShift]),
        "P": KeyWithFlags("p", [.maskShift]),
        "Q": KeyWithFlags("q", [.maskShift]),
        "R": KeyWithFlags("r", [.maskShift]),
        "S": KeyWithFlags("s", [.maskShift]),
        "T": KeyWithFlags("t", [.maskShift]),
        "U": KeyWithFlags("u", [.maskShift]),
        "V": KeyWithFlags("v", [.maskShift]),
        "W": KeyWithFlags("w", [.maskShift]),
        "X": KeyWithFlags("x", [.maskShift]),
        "Y": KeyWithFlags("y", [.maskShift]),
        "Z": KeyWithFlags("z", [.maskShift]),
        ")": KeyWithFlags("0", [.maskShift]),
        "!": KeyWithFlags("1", [.maskShift]),
        "@": KeyWithFlags("2", [.maskShift]),
        "#": KeyWithFlags("3", [.maskShift]),
        "$": KeyWithFlags("4", [.maskShift]),
        "%": KeyWithFlags("5", [.maskShift]),
        "^": KeyWithFlags("6", [.maskShift]),
        "&": KeyWithFlags("7", [.maskShift]),
        "*": KeyWithFlags("8", [.maskShift]),
        "(": KeyWithFlags("9", [.maskShift]),
        "+": KeyWithFlags("=", [.maskShift]),
        "_": KeyWithFlags("-", [.maskShift]),
        ":": KeyWithFlags(";", [.maskShift]),
        "\"": KeyWithFlags("'", [.maskShift]),
        "<": KeyWithFlags(",", [.maskShift]),
        ">": KeyWithFlags(".", [.maskShift]),
        "?": KeyWithFlags("/", [.maskShift]),
        "|": KeyWithFlags("\\", [.maskShift]),
        "~": KeyWithFlags("`", [.maskShift]),
        "{": KeyWithFlags("[", [.maskShift]),
        "}": KeyWithFlags("]", [.maskShift]),
    ]

    private static var remappingDictionary: [String: String] = [:]

    static func updateMapping(_ newOldMapping: [String: String]) {
        // Merge the new mapping with the existing one
        remappingDictionary.merge(newOldMapping) { current, new in new }
    }

    private static func getRemappedKey(_ key: String) -> String {
        return remappingDictionary[key] ?? key
    }

    static func getKeyInfo(_ name: String) -> KeyCodeWithFlags? {
        let keys = keyDictionary[getRemappedKey(name)]

        if keys != nil {
            return keys
        }

        let modifierKeys = modifierKeyDictionary[name]

        if modifierKeys != nil {
            let key = keyDictionary[getRemappedKey(modifierKeys!.key)]

            if key != nil {
                return KeyCodeWithFlags(key!.keyCode, modifierKeys!.flags)
            }
        }

        return nil
    }
}


================================================
FILE: Sources/SendKeysLib/KeyMappings.swift
================================================
import ArgumentParser

struct KeyMappings {
    enum Layouts: String, Codable, ExpressibleByArgument {
        case qwerty
        case colemak
        case dvorak
    }

    static let Mappings: [Layouts: [String: String]] = [
        .qwerty: Qwerty,
        .colemak: Colemak,
        .dvorak: Dvorak,
    ]

    static let Qwerty: [String: String] = [:]

    static let Colemak: [String: String] = [
        "q": "q",
        "w": "w",
        "f": "e",
        "p": "r",
        "g": "t",
        "j": "y",
        "l": "u",
        "u": "i",
        "y": "o",
        ";": "p",
        "a": "a",
        "r": "s",
        "s": "d",
        "t": "f",
        "d": "g",
        "h": "h",
        "n": "j",
        "e": "k",
        "i": "l",
        "o": ";",
        "z": "z",
        "x": "x",
        "c": "c",
        "b": "b",
        "k": "n",
        "m": "m",
    ]

    static let Dvorak: [String: String] = [
        "[": "-",
        "]": "=",
        "'": "q",
        ",": "w",
        ".": "e",
        "p": "r",
        "y": "t",
        "f": "y",
        "g": "u",
        "c": "i",
        "r": "o",
        "l": "p",
        "/": "[",
        "=": "]",
        "a": "a",
        "o": "s",
        "e": "d",
        "u": "f",
        "i": "g",
        "d": "h",
        "h": "j",
        "t": "k",
        "n": "l",
        "s": ":",
        "-": "'",
        ";": "z",
        "q": "x",
        "j": "c",
        "k": "v",
        "x": "b",
        "b": "n",
        "m": "m",
        "w": ",",
        "v": ".",
        "z": "/",
    ]
}


================================================
FILE: Sources/SendKeysLib/KeyPresser.swift
================================================
import Carbon
import Cocoa
import Foundation

public class KeyPresser {
    private var application: NSRunningApplication?

    init(app: NSRunningApplication?) {
        self.application = app
    }

    func keyPress(key: String, modifiers: [String]) throws -> CGEvent? {
        if let keyDownEvent = try! keyDown(key: key, modifiers: modifiers) {
            return keyUp(key: key, modifiers: modifiers, event: keyDownEvent)
        }

        return nil
    }

    func keyDown(key: String, modifiers: [String]) throws -> CGEvent? {
        let keyDownEvent = try! createKeyEvent(key: key, modifiers: modifiers, keyDown: true)

        if self.application == nil {
            keyDownEvent?.post(tap: CGEventTapLocation.cghidEventTap)
        } else {
            if #available(OSX 10.11, *) {
                keyDownEvent?.postToPid(self.application!.processIdentifier)
            } else {
                keyDownEvent?.post(tap: CGEventTapLocation.cghidEventTap)
            }
        }

        return keyDownEvent
    }

    func keyUp(key: String, modifiers: [String]) throws -> CGEvent? {
        let keyUpEvent = try! createKeyEvent(key: key, modifiers: modifiers, keyDown: false)

        if self.application == nil {
            keyUpEvent?.post(tap: CGEventTapLocation.cghidEventTap)
        } else {
            if #available(OSX 10.11, *) {
                keyUpEvent?.postToPid(self.application!.processIdentifier)
            } else {
                keyUpEvent?.post(tap: CGEventTapLocation.cghidEventTap)
            }
        }

        return keyUpEvent
    }

    func keyUp(key: String, modifiers: [String], event: CGEvent) -> CGEvent? {
        let keyUpEvent = try! createKeyEvent(
            key: key, modifiers: modifiers, keyDown: false, parentEventSource: CGEventSource(event: event))

        if self.application == nil {
            keyUpEvent?.post(tap: CGEventTapLocation.cghidEventTap)
        } else {
            if #available(OSX 10.11, *) {
                keyUpEvent?.postToPid(self.application!.processIdentifier)
            } else {
                keyUpEvent?.post(tap: CGEventTapLocation.cghidEventTap)
            }
        }

        return keyUpEvent
    }

    private func createKeyEvent(
        key: String, modifiers: [String], keyDown: Bool, parentEventSource: CGEventSource? = nil
    ) throws -> CGEvent? {
        let info = KeyCodes.getKeyInfo(key)
        let flags = try! KeyPresser.getModifierFlags(modifiers)
        let mergedFlags = flags.union(CGEventFlags(info?.flags ?? []))
        let eventSource = parentEventSource ?? CGEventSource(stateID: .hidSystemState)
        let keyEvent = CGEvent(keyboardEventSource: eventSource, virtualKey: info?.keyCode ?? 0, keyDown: keyDown)

        if info == nil {
            if key.count == 1 {
                let utf16Chars = Array(key.utf16)
                keyEvent!.keyboardSetUnicodeString(stringLength: utf16Chars.count, unicodeString: utf16Chars)
            } else {
                throw RuntimeError("Unrecognized key: \(key)")
            }
        }

        if !mergedFlags.isEmpty {
            keyEvent?.flags = mergedFlags
        } else {
            keyEvent?.flags = []
        }

        return keyEvent
    }

    static func setKeyboardLayout(_ layout: KeyMappings.Layouts) {
        KeyCodes.updateMapping(KeyMappings.Mappings[layout]!)
    }

    static func getModifierFlags(_ modifiers: [String]) throws -> CGEventFlags {
        var flags: CGEventFlags = []

        for modifier in modifiers.filter({ !$0.isEmpty }) {
            let flag = try getModifierFlag(modifier)
            flags.insert(flag)
        }

        return flags
    }

    static func getModifierFlag(_ modifier: String) throws -> CGEventFlags {
        switch modifier {
        case "⌘",
            "cmd",
            "command":
            return CGEventFlags.maskCommand
        case "^",
            "ctrl",
            "control":
            return CGEventFlags.maskControl
        case "⌥",
            "alt",
            "option":
            return CGEventFlags.maskAlternate
        case "⇧",
            "shift":
            return CGEventFlags.maskShift
        case "fn",
            "function":
            return CGEventFlags.maskSecondaryFn
        default:
            throw RuntimeError("Unrecognized modifier: \(modifier)")
        }
    }
}


================================================
FILE: Sources/SendKeysLib/MouseController.swift
================================================
import Cocoa
import Foundation

class MouseController {
    enum ScrollAxis {
        case horizontal
        case vertical
    }

    enum mouseEventType {
        case up
        case down
        case move
        case drag
    }

    let animationRefreshInterval: TimeInterval
    let keyPresser: KeyPresser
    var downButtons = Set<CGMouseButton>()

    init(animationRefreshInterval: TimeInterval, keyPresser: KeyPresser) {
        self.animationRefreshInterval = animationRefreshInterval
        self.keyPresser = keyPresser
    }

    func move(start: CGPoint?, end: CGPoint, duration: TimeInterval, flags: CGEventFlags) {
        let resolvedStart = start ?? getLocation()!
        let eventSource = CGEventSource(event: nil)
        let button = downButtons.first
        let moveType = getEventType(.move, button)

        let animator = Animator(
            duration, animationRefreshInterval,
            { progress in
                let location = CGFloat(progress) * (end - resolvedStart) + resolvedStart
                self.setLocation(location, eventSource: eventSource, moveType: moveType, button: button, flags: flags)
            })

        animator.animate()
    }

    func move(
        start: CGPoint?, path: String, offset: CGPoint, scale: CGPoint, duration: TimeInterval, flags: CGEventFlags
    ) {
        let resolvedStart = start ?? getLocation()!
        let eventSource = CGEventSource(event: nil)
        let button = downButtons.first
        let moveType = getEventType(.move, button)
        let pathData = PathData(path, resolvedStart)

        let animator = Animator(
            duration, animationRefreshInterval,
            { progress in
                let location = offset + (pathData.getPointAtInterval(progress) * scale)
                self.setLocation(location, eventSource: eventSource, moveType: moveType, button: button, flags: flags)
            })

        animator.animate()
    }

    func click(_ location: CGPoint?, button: CGMouseButton, flags: CGEventFlags, clickCount: Int) {
        let resolvedLocation = location ?? getLocation()!
        let downMouseType = getEventType(.down, button)
        let upMouseType = getEventType(.up, button)

        let downEvent = CGEvent(
            mouseEventSource: nil, mouseType: downMouseType, mouseCursorPosition: resolvedLocation, mouseButton: button)
        downEvent?.setIntegerValueField(.mouseEventClickState, value: Int64(clickCount))
        downEvent?.flags = flags
        downEvent?.post(tap: CGEventTapLocation.cghidEventTap)
        let eventSource = CGEventSource(event: downEvent)

        let upEvent = CGEvent(
            mouseEventSource: eventSource, mouseType: upMouseType, mouseCursorPosition: resolvedLocation,
            mouseButton: button)
        upEvent?.post(tap: CGEventTapLocation.cghidEventTap)
    }

    func down(_ location: CGPoint?, button: CGMouseButton, flags: CGEventFlags) {
        let resolvedLocation = location ?? getLocation()!
        let downMouseType = getEventType(.down, button)

        let downEvent = CGEvent(
            mouseEventSource: nil, mouseType: downMouseType, mouseCursorPosition: resolvedLocation, mouseButton: button)
        downEvent?.flags = flags
        downEvent?.post(tap: CGEventTapLocation.cghidEventTap)

        downButtons.insert(button)
    }

    func up(_ location: CGPoint?, button: CGMouseButton, flags: CGEventFlags) {
        let resolvedLocation = location ?? getLocation()!
        let upMouseType = getEventType(.up, button)

        let upEvent = CGEvent(
            mouseEventSource: nil, mouseType: upMouseType, mouseCursorPosition: resolvedLocation,
            mouseButton: button)
        upEvent?.post(tap: CGEventTapLocation.cghidEventTap)
        downButtons.remove(button)
    }

    func drag(start: CGPoint?, end: CGPoint, duration: TimeInterval, button: CGMouseButton, flags: CGEventFlags) {
        let resolvedStart = start ?? getLocation()!
        let downMouseType = getEventType(.down, button)
        let upMouseType = getEventType(.up, button)
        let moveType = getEventType(.drag, button)
        var eventSource: CGEventSource?

        let animator = Animator(
            duration, animationRefreshInterval,
            { progress in
                let location = CGFloat(progress) * (end - resolvedStart) + resolvedStart
                self.setLocation(location, eventSource: eventSource, moveType: moveType, button: button, flags: flags)
            })

        if !downButtons.contains(button) {
            let downEvent = CGEvent(
                mouseEventSource: nil, mouseType: downMouseType, mouseCursorPosition: resolvedStart, mouseButton: button
            )
            downEvent?.flags = flags
            downEvent?.post(tap: CGEventTapLocation.cghidEventTap)
            eventSource = CGEventSource(event: downEvent)
        }

        animator.animate()

        if !downButtons.contains(button) {
            let upEvent = CGEvent(
                mouseEventSource: eventSource, mouseType: upMouseType, mouseCursorPosition: end, mouseButton: button)
            upEvent?.post(tap: CGEventTapLocation.cghidEventTap)
        }
    }

    func scroll(_ delta: CGPoint, _ duration: TimeInterval, flags: CGEventFlags) {
        var scrolledX: Int = 0
        var scrolledY: Int = 0
        let eventSource = CGEventSource(event: nil)

        let animator = Animator(
            duration, animationRefreshInterval,
            { progress in
                if delta.x != 0 {
                    let amount = Int((Double(delta.x) * progress) - Double(scrolledX))
                    scrolledX += amount

                    self.scrollBy(amount, .horizontal, eventSource: eventSource, flags: flags)
                }
                if delta.y != 0 {
                    let amount = Int((Double(delta.y) * progress) - Double(scrolledY))
                    scrolledY += amount

                    self.scrollBy(amount, .vertical, eventSource: eventSource, flags: flags)
                }
            })

        animator.animate()
    }

    func circle(_ center: CGPoint, _ radius: CGPoint, _ fromAngle: Double, _ toAngle: Double, _ duration: TimeInterval)
    {
        let eventSource = CGEventSource(event: nil)
        let ANGLE_OFFSET: Double = -90
        let button = downButtons.first
        let moveType = getEventType(.move, button)

        let animator = Animator(
            duration, animationRefreshInterval,
            { progress in
                let angle = (toAngle - fromAngle) * progress + fromAngle + ANGLE_OFFSET
                let location = CGPoint(
                    x: cos(angle * Double.pi / 180) * Double(radius.x) + Double(center.x),
                    y: sin(angle * Double.pi / 180) * Double(radius.y) + Double(center.y)
                )
                self.setLocation(location, eventSource: eventSource, moveType: moveType, button: .left, flags: [])
            })

        animator.animate()
    }

    func scrollBy(_ amount: Int, _ axis: ScrollAxis, eventSource: CGEventSource?, flags: CGEventFlags) {
        if #available(OSX 10.13, *) {
            let event = CGEvent(
                scrollWheelEvent2Source: eventSource, units: .pixel, wheelCount: 1, wheel1: 0, wheel2: 0, wheel3: 0)
            let field =
                axis == .vertical
                ? CGEventField.scrollWheelEventPointDeltaAxis1 : CGEventField.scrollWheelEventPointDeltaAxis2

            event?.setIntegerValueField(field, value: Int64(amount * -1))
            event?.flags = flags

            event?.post(tap: CGEventTapLocation.cghidEventTap)
        } else {
            fatalError("Scrolling is only available on 10.13 or later\n")
        }
    }

    func getLocation() -> CGPoint? {
        let event = CGEvent(source: nil)
        return event?.location
    }

    private func setLocation(
        _ location: CGPoint, eventSource: CGEventSource?, moveType: CGEventType = CGEventType.mouseMoved,
        button: CGMouseButton? = nil, flags: CGEventFlags = []
    ) {
        let moveEvent = CGEvent(
            mouseEventSource: eventSource, mouseType: moveType, mouseCursorPosition: location,
            mouseButton: button ?? CGMouseButton.left)
        moveEvent?.flags = flags
        moveEvent?.post(tap: CGEventTapLocation.cghidEventTap)
    }

    private func getEventType(_ mouseType: mouseEventType, _ button: CGMouseButton? = nil) -> CGEventType {
        switch mouseType {
        case .up:
            if button == CGMouseButton.left {
                return CGEventType.leftMouseUp
            } else if button == CGMouseButton.right {
                return CGEventType.rightMouseUp
            } else {
                return CGEventType.otherMouseUp
            }
        case .down:
            if button == CGMouseButton.left {
                return CGEventType.leftMouseDown
            } else if button == CGMouseButton.right {
                return CGEventType.rightMouseDown
            } else {
                return CGEventType.otherMouseDown
            }
        case .move:
            if button == nil {
                return CGEventType.mouseMoved
            } else if button == CGMouseButton.left {
                return CGEventType.leftMouseDragged
            } else if button == CGMouseButton.right {
                return CGEventType.rightMouseDragged
            } else {
                return CGEventType.otherMouseDragged
            }
        case .drag:
            if button == CGMouseButton.left {
                return CGEventType.leftMouseDragged
            } else if button == CGMouseButton.right {
                return CGEventType.rightMouseDragged
            } else {
                return CGEventType.otherMouseDragged
            }
        }
    }

    private func resolveLocation(_ location: CGPoint) -> CGPoint {
        let currentLocation = getLocation()
        return CGPoint(
            x: location.x < 0 ? (currentLocation?.x ?? 0) : location.x,
            y: location.y < 0 ? (currentLocation?.y ?? 0) : location.y
        )
    }
}


================================================
FILE: Sources/SendKeysLib/MouseEventProcessor.swift
================================================
import Cocoa
import Foundation

enum MouseEventType {
    case click
    case drag
}

enum MouseButton: String, CustomStringConvertible {
    case left
    case right
    case center
    case other

    var description: String {
        return self.rawValue
    }
}

struct RawMouseEvent {
    let eventType: CGEventType
    let button: MouseButton
    let point: CGPoint

    init(eventType: CGEventType, button: MouseButton, point: CGPoint) {
        self.eventType = eventType
        self.button = button
        self.point = point
    }
}

class MouseEvent: CustomStringConvertible {
    let eventType: MouseEventType
    let button: MouseButton
    let startPoint: CGPoint
    let endPoint: CGPoint
    var duration: TimeInterval

    static let numberFormatter = createNumberFormatter()

    init(eventType: MouseEventType, button: MouseButton, startPoint: CGPoint, endPoint: CGPoint, duration: TimeInterval)
    {
        self.eventType = eventType
        self.button = button
        self.startPoint = startPoint
        self.endPoint = endPoint
        self.duration = duration
    }

    var description: String {
        switch eventType {
        case .click:
            var moveParts: [String] = []
            var clickParts: [String] = []

            moveParts.append(
                "\(Self.numberFormatter.string(for: endPoint.x)!),\(Self.numberFormatter.string(for: endPoint.y)!)")

            if duration > 0 {
                moveParts.append(Self.numberFormatter.string(for: duration)!)
            }

            clickParts.append(button.description)

            return "<m:\(moveParts.joined(separator: ":"))><m:\(clickParts.joined(separator: ":"))><\\>"
        case .drag:
            var parts: [String] = []

            parts.append(
                "\(Self.numberFormatter.string(for: startPoint.x)!),\(Self.numberFormatter.string(for: startPoint.y)!),\(Self.numberFormatter.string(for: endPoint.x)!),\(Self.numberFormatter.string(for: endPoint.y)!)"
            )

            if duration > 0 {
                parts.append(Self.numberFormatter.string(for: duration)!)
            }

            parts.append(button.description)

            return "<d:\(parts.joined(separator: ":"))><\\>"
        }
    }

    static func createNumberFormatter() -> NumberFormatter {
        let numberFormatter = NumberFormatter()

        numberFormatter.maximumFractionDigits = 2

        return numberFormatter
    }
}

class MouseEventProcessor {
    var events: [RawMouseEvent] = []
    var lastDate: Date = Date()

    func start() {
        lastDate = Date()
    }

    func consumeEvent(type: CGEventType, event: CGEvent) -> MouseEvent? {
        let button = getMouseButton(type: type, event: event)
        let rawEvent = RawMouseEvent(eventType: type, button: button, point: event.location)
        var mouseEvent: MouseEvent? = nil

        switch type {
        case .leftMouseUp, .rightMouseUp, .otherMouseUp:
            switch events.last?.eventType {
            case .leftMouseDown, .rightMouseDown, .otherMouseDown:
                mouseEvent = MouseEvent(
                    eventType: .click, button: button, startPoint: events.first!.point, endPoint: event.location,
                    duration: -lastDate.timeIntervalSinceNow)
            case .leftMouseDragged, .rightMouseDragged, .otherMouseDragged:
                mouseEvent = MouseEvent(
                    eventType: .drag, button: button, startPoint: events.first!.point, endPoint: event.location,
                    duration: -lastDate.timeIntervalSinceNow)
            default:
                events.append(rawEvent)
            }

            lastDate = Date()
            events = []
        default:
            events.append(rawEvent)
        }

        return mouseEvent
    }

    private func getMouseButton(type: CGEventType, event: CGEvent) -> MouseButton {
        var button: MouseButton = .other

        switch type {
        case .leftMouseDown, .leftMouseUp, .leftMouseDragged:
            button = .left
        case .rightMouseDown, .rightMouseUp, .rightMouseDragged:
            button = .right
        case .otherMouseDown, .otherMouseUp, .otherMouseDragged:
            let buttonNumber = event.getIntegerValueField(.mouseEventButtonNumber)
            switch UInt32(buttonNumber) {
            case CGMouseButton.left.rawValue:
                button = .left
            case CGMouseButton.right.rawValue:
                button = .right
            case CGMouseButton.center.rawValue:
                button = .center
            default:
                button = .other
            }
        default:
            button = .other
        }

        return button
    }
}


================================================
FILE: Sources/SendKeysLib/MousePosition.swift
================================================
import ArgumentParser
import Cocoa
import Foundation

enum OutputMode: String, Codable, ExpressibleByArgument {
    case coordinates
    case commands
}

class MousePosition: ParsableCommand {
    public static let configuration = CommandConfiguration(
        abstract: "Prints the current mouse position."
    )

    @Flag(
        name: .shortAndLong, inversion: FlagInversion.prefixedNo,
        help: "Watch and display the mouse positions as the mouse is clicked.")
    var watch: Bool?

    @Option(
        name: NameSpecification([.customShort("o"), .customLong("output", withSingleDash: false)]),
        help: "Displays results as either a series of coordinates or commands.")
    var mode: OutputMode?

    @Option(
        name: .shortAndLong,
        help:
            "Duration (in seconds) to output for mouse events. A negative value uses elapsed time between mouse events."
    )
    var duration: Double?

    var config: MousePositionConfig

    static let eventProcessor = MouseEventProcessor()

    private static func createNumberFormatter() -> NumberFormatter {
        let numberFormatter = NumberFormatter()

        numberFormatter.maximumFractionDigits = 2

        return numberFormatter
    }

    required init() {
        self.config = MousePositionConfig(watch: false, output: .commands, duration: -1)
    }

    func run() {
        self.config = self.config
            .merge(with: ConfigLoader.loadConfig().mousePosition)
            .merge(with: MousePositionConfig(watch: watch, output: mode, duration: duration))

        if self.config.watch! {
            watchMouseInput()
        } else {
            printMousePosition(nil)
        }
    }

    func printMousePosition(_ position: CGPoint?) {
        let numberFormatter = Self.createNumberFormatter()
        let location =
            position ?? MouseController(animationRefreshInterval: 0.01, keyPresser: KeyPresser(app: nil)).getLocation()!

        printAndFlush("\(numberFormatter.string(for: location.x)!),\(numberFormatter.string(for: location.y)!)")
    }

    func listenForInput() {
        fputs(
            "Waiting for user input... Escape or ctrl + d to stop, or any other key to capture mouse position.\n",
            stderr)

        waitForCharInput { _ in
            printMousePosition(nil)
        }
    }

    func waitForCharInput(callback: (_ char: UInt8) -> Void) {
        let stdIn = FileHandle.standardInput
        let originalTerm = enableRawMode(fileHandle: stdIn)

        var char: UInt8 = 0
        while read(stdIn.fileDescriptor, &char, 1) == 1 {
            if char == 4 /* EOF (Ctrl+D) */ || char == 27 /* escape */ {
                break
            }

            callback(char)
        }

        restoreRawMode(fileHandle: stdIn, originalTerm: originalTerm)
    }

    func watchMouseInput() {
        fputs("Waiting for mouse input... ctrl + c to stop.\n", stderr)

        MousePosition.eventProcessor.start()

        var eventMask =
            (1 << CGEventType.leftMouseDown.rawValue)
            | (1 << CGEventType.leftMouseUp.rawValue)
            | (1 << CGEventType.leftMouseDragged.rawValue)
        eventMask =
            eventMask | (1 << CGEventType.rightMouseDown.rawValue)
            | (1 << CGEventType.rightMouseUp.rawValue)
            | (1 << CGEventType.rightMouseDragged.rawValue)
        eventMask =
            eventMask | (1 << CGEventType.otherMouseDown.rawValue)
            | (1 << CGEventType.otherMouseUp.rawValue)
            | (1 << CGEventType.otherMouseDragged.rawValue)

        let info = UnsafeMutableRawPointer(mutating: bridge(obj: self))

        guard
            let eventTap = CGEvent.tapCreate(
                tap: .cghidEventTap, place: .tailAppendEventTap, options: .defaultTap,
                eventsOfInterest: CGEventMask(eventMask),
                callback: {
                    (proxy: CGEventTapProxy, eventType: CGEventType, event: CGEvent, refcon: UnsafeMutableRawPointer?)
                        -> Unmanaged<CGEvent>? in
                    let command: MousePosition = bridge(ptr: UnsafeRawPointer(refcon)!)

                    if let mouseEvent = MousePosition.eventProcessor.consumeEvent(type: eventType, event: event) {
                        // if duration is set, override all mouse event durations
                        if command.config.duration! >= 0 {
                            mouseEvent.duration = command.config.duration!
                        }

                        switch command.config.output! {
                        case .coordinates:
                            if mouseEvent.eventType == .click {
                                command.printMousePosition(mouseEvent.endPoint)
                            }
                        case .commands:
                            command.printAndFlush(mouseEvent.description)
                        }
                    }

                    return Unmanaged.passRetained(event)
                }, userInfo: info)
        else {
            MousePosition.exit(withError: RuntimeError("Failed to create event tap."))
        }

        let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
        CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
        CGEvent.tapEnable(tap: eventTap, enable: true)
        CFRunLoopRun()
    }

    func printAndFlush(_ message: String) {
        print(message)
        fflush(stdout)
    }

    func eventCallback(proxy: CGEventTapProxy, type: CGEventType, event: CGEvent, refcon: UnsafeMutableRawPointer?)
        -> Unmanaged<CGEvent>?
    {
        switch self.config.output! {
        case .coordinates:
            printMousePosition(nil)
        case .commands:
            printMousePosition(nil)
        }
        printAndFlush("Event \(type) \(type.rawValue)")

        return Unmanaged.passRetained(event)
    }

    // see https://stackoverflow.com/a/24335355/669586
    func initStruct<S>() -> S {
        let struct_pointer = UnsafeMutablePointer<S>.allocate(capacity: 1)
        let struct_memory = struct_pointer.pointee
        struct_pointer.deallocate()
        return struct_memory
    }

    func enableRawMode(fileHandle: FileHandle) -> termios {
        var raw: termios = initStruct()
        tcgetattr(fileHandle.fileDescriptor, &raw)

        let original = raw

        raw.c_lflag &= ~(UInt(ECHO | ICANON))
        tcsetattr(fileHandle.fileDescriptor, TCSAFLUSH, &raw)

        return original
    }

    func restoreRawMode(fileHandle: FileHandle, originalTerm: termios) {
        var term = originalTerm
        tcsetattr(fileHandle.fileDescriptor, TCSAFLUSH, &term)
    }
}


================================================
FILE: Sources/SendKeysLib/Path/Extensions.swift
================================================
import AppKit
import Foundation

extension CGPoint {
    // Vector math
    public static func + (left: CGPoint, right: CGPoint) -> CGPoint {
        return CGPoint(x: left.x + right.x, y: left.y + right.y)
    }

    public static func - (left: CGPoint, right: CGPoint) -> CGPoint {
        return CGPoint(x: left.x - right.x, y: left.y - right.y)
    }

    public static func * (left: CGFloat, right: CGPoint) -> CGPoint {
        return CGPoint(x: left * right.x, y: left * right.y)
    }

    public static func * (left: CGPoint, right: CGPoint) -> CGPoint {
        return CGPoint(x: left.x * right.x, y: left.y * right.y)
    }

    public func distance(from: CGPoint) -> Double {
        let delta = self - from
        let sqr = delta * delta
        return sqrt(Double(sqr.x + sqr.y))
    }
}


================================================
FILE: Sources/SendKeysLib/Path/PathCommands.swift
================================================
import Foundation

public class PointValue: CustomStringConvertible {
    var point: CGPoint

    init(_ point: CGPoint) {
        self.point = point
    }

    public var description: String {
        return "\(point.x) \(point.y)"
    }
}

public class ControlPointValue: PointValue {
    var controlPoint1: CGPoint?

    init(_ controlPoint: CGPoint?, _ point: CGPoint) {
        super.init(point)
        self.controlPoint1 = controlPoint
    }

    override public var description: String {
        return "\(controlPoint1 != nil ? "\(controlPoint1!.x) \(controlPoint1!.y)" : "") \(point.x) \(point.y)"
            .trimmingCharacters(in: [" "])
    }
}

public class ControlPointsValue: ControlPointValue {
    var controlPoint2: CGPoint

    init(_ controlPoint1: CGPoint?, _ controlPoint2: CGPoint, _ point: CGPoint) {
        self.controlPoint2 = controlPoint2
        super.init(controlPoint1, point)
    }

    override public var description: String {
        return
            "\(controlPoint1 != nil ? "\(controlPoint1!.x) \(controlPoint1!.y)" : "") \(controlPoint2.x) \(controlPoint2.y) \(point.x) \(point.y)"
            .trimmingCharacters(in: [" "])
    }
}

public class ArchCommandValue: PointValue {
    var radius: CGPoint
    var angle: Double
    var largeArc: Bool
    var sweep: Bool

    init(_ radius: CGPoint, _ angle: Double, _ largeArc: Bool, _ sweep: Bool, _ point: CGPoint) {
        self.radius = radius
        self.angle = angle
        self.largeArc = largeArc
        self.sweep = sweep
        super.init(point)
    }

    override public var description: String {
        return "\(radius.x) \(radius.y) \(angle) \(largeArc ? "1" : "0") \(sweep ? "1" : "0") \(point.x) \(point.y)"
    }
}

public class PathCommandBase: Equatable, CustomStringConvertible {
    var type: Character

    init(_ type: Character) {
        self.type = type
    }

    func decompose(from: CGPoint) -> [PathCommandBase] {
        return [self]
    }

    public func equals(_ comparison: PathCommandBase) -> Bool {
        return self.description == comparison.description
    }

    public static func == (lhs: PathCommandBase, rhs: PathCommandBase) -> Bool {
        return lhs.equals(rhs) && rhs.equals(lhs)
    }

    public var description: String {
        return "\(type)"
    }

    public var currentPoint: CGPoint? {
        return nil
    }

    public var isRelative: Bool {
        return type.isLowercase
    }

    public func makeAbsolute(_ point: CGPoint) {
        if isRelative {
            type = Character(type.uppercased())
        }
    }

    public func distanceFrom(point: CGPoint) -> Double {
        return 0
    }

    public func pointAlongPath(interval: Double, from: CGPoint) -> CGPoint {
        if currentPoint == nil {
            return from
        }
        return from + CGFloat(interval) * (currentPoint! - from)
    }
}

public class PathCommand<T>: PathCommandBase {
    var value: T

    init(_ type: Character, _ value: T) {
        self.value = value
        super.init(type)
    }

    override public var description: String {
        return "\(type) \(value)"
    }
}

public class NumericPathCommand: PathCommand<Double> {
    override func decompose(from: CGPoint) -> [PathCommandBase] {
        if type == "H" {
            return [PointPathCommand("L", PointValue(CGPoint(x: CGFloat(value), y: from.y)))]
        }
        if type == "V" {
            return [PointPathCommand("L", PointValue(CGPoint(x: from.x, y: CGFloat(value))))]
        }
        return []
    }

    override public func makeAbsolute(_ point: CGPoint) {
        if isRelative {
            super.makeAbsolute(point)

            if type == "H" {
                value = Double(point.x) + value
            }
            if type == "V" {
                value = Double(point.y) + value
            }
        }
    }

    override public func distanceFrom(point: CGPoint) -> Double {
        fatalError("Not implemented")
    }
}

public class PointPathCommand: PathCommand<PointValue> {
    override public func makeAbsolute(_ point: CGPoint) {
        if isRelative {
            super.makeAbsolute(point)

            value.point = point + value.point
        }
    }

    override public func distanceFrom(point: CGPoint) -> Double {
        if type == "L" {
            return value.point.distance(from: point)
        } else if type == "M" {
            return 0
        }
        fatalError("Not implemented")
    }

    override public var currentPoint: CGPoint? {
        return value.point
    }
}

public class ArcPathCommand: PathCommand<ArchCommandValue> {
    override func decompose(from: CGPoint) -> [PathCommandBase] {
        if from == value.point {
            return []
        }

        if value.radius.x == 0 || value.radius.y == 0 {
            return [PointPathCommand("L", PointValue(value.point))]
        }

        let midpointDistance = CGFloat(0.5) * (from - value.point)
        var matrix = AffineTransform()
        matrix.rotate(byDegrees: CGFloat(value.angle))
        let tranformedMidpoint = matrix.transform(midpointDistance)
        var rx = value.radius.x
        var ry = value.radius.y
        let squareRx = rx * rx
        let squareRy = ry * ry
        let squareX = tranformedMidpoint.x * tranformedMidpoint.x
        let squareY = tranformedMidpoint.y * tranformedMidpoint.y

        let radiiScale = squareX / squareRx + squareY / squareRy
        if radiiScale > 1 {
            rx *= sqrt(radiiScale)
            ry *= sqrt(radiiScale)
        }

        matrix = AffineTransform()
        matrix.scale(x: 1 / rx, y: 1 / ry)
        matrix.rotate(byDegrees: CGFloat(-value.angle))
        var point1 = matrix.transform(from)
        var point2 = matrix.transform(value.point)
        var delta = point2 - point1
        let d = delta.x * delta.x + delta.y * delta.y

        var scaleFactor = sqrt(max(1 / d - 0.25, 0))
        if value.sweep == value.largeArc {
            scaleFactor = -scaleFactor
        }

        delta = scaleFactor * delta
        let center = 0.5 * (point1 + point2) + CGPoint(x: -delta.y, y: delta.x)

        let theta1 = Double(atan2(point1.y - center.y, point1.x - center.x))
        let theta2 = Double(atan2(point2.y - center.y, point2.x - center.x))
        var thetaArc = Double(theta2 - theta1)
        if thetaArc < 0 && value.sweep {
            thetaArc += Double.pi * 2.0
        } else if thetaArc > 0 && !value.sweep {
            thetaArc -= Double.pi * 2.0
        }

        matrix = AffineTransform()
        matrix.rotate(byDegrees: CGFloat(value.angle))
        matrix.scale(x: rx, y: ry)
        let segments = Int(ceil(fabs(thetaArc / Double.pi / 2)))

        var commands: [PathCommandBase] = []

        for i in 0...segments - 1 {
            let startTheta = theta1 + (Double(i) * thetaArc) / Double(segments)
            let endTheta = theta1 + ((Double(i) + 1.0) * thetaArc) / Double(segments)
            let t = CGFloat((8.0 / 6.0) * tan(0.25 * (endTheta - startTheta)))
            // if (!std::isfinite(t))
            //     return false;
            let sinStartTheta = CGFloat(sin(startTheta))
            let cosStartTheta = CGFloat(cos(startTheta))
            let sinEndTheta = CGFloat(sin(endTheta))
            let cosEndTheta = CGFloat(cos(endTheta))
            point1 = CGPoint(
                x: cosStartTheta - t * sinStartTheta + center.x,
                y: sinStartTheta + t * cosStartTheta + center.y
            )
            var targetPoint = CGPoint(
                x: cosEndTheta + center.x,
                y: sinEndTheta + center.y
            )
            point2 = CGPoint(x: targetPoint.x + t * sinEndTheta, y: targetPoint.y - t * cosEndTheta)

            point1 = matrix.transform(point1)
            point2 = matrix.transform(point2)
            targetPoint = matrix.transform(targetPoint)

            commands.append(CubicBezierPathCommand("C", ControlPointsValue(point1, point2, targetPoint)))
        }

        return commands
    }

    override public var currentPoint: CGPoint? {
        return value.point
    }

    override public func makeAbsolute(_ point: CGPoint) {
        if isRelative {
            super.makeAbsolute(point)

            value.point = point + value.point
        }
    }

    override public func distanceFrom(point: CGPoint) -> Double {
        fatalError("Not implemented")
    }
}

public class QuadraticBezierPathCommand: PathCommand<ControlPointValue> {
    override func decompose(from: CGPoint) -> [PathCommandBase] {
        let controlPoint = value.controlPoint1 ?? from
        return [
            CubicBezierPathCommand(
                "C",
                ControlPointsValue(
                    from + (2.0 / 3.0) * (controlPoint - from),
                    value.point + (2.0 / 3.0) * (controlPoint - value.point),
                    value.point
                ))
        ]
    }

    override public var currentPoint: CGPoint? {
        return value.point
    }

    override public func makeAbsolute(_ point: CGPoint) {
        if isRelative {
            super.makeAbsolute(point)

            if value.controlPoint1 != nil {
                value.controlPoint1 = point + value.controlPoint1!
            }
            value.point = point + value.point
        }
    }

    override public func distanceFrom(point: CGPoint) -> Double {
        fatalError("Not implemented")
    }
}

public class CubicBezierPathCommand: PathCommand<ControlPointsValue> {
    override public var currentPoint: CGPoint? {
        return value.point
    }

    override public func makeAbsolute(_ point: CGPoint) {
        if isRelative {
            super.makeAbsolute(point)

            if value.controlPoint1 != nil {
                value.controlPoint1 = point + value.controlPoint1!
            }
            value.controlPoint2 = point + value.controlPoint2
            value.point = point + value.point
        }
    }

    private func lengthValueAt(t: CGFloat, p0: CGFloat, c1: CGFloat, c2: CGFloat, p1: CGFloat) -> CGFloat {
        var value: CGFloat = 0.0

        // (1-t)^3 * p0 + 3 * (1-t)^2 * t * c1 + 3 * (1-t) * t^2 * c2 + t^3 * p1
        value += pow(1 - t, 3) * p0
        value += 3 * pow(1 - t, 2) * t * c1
        value += 3 * (1 - t) * pow(t, 2) * c2
        value += pow(t, 3) * p1

        return value
    }

    private func poinAt(t: CGFloat, start: CGPoint) -> CGPoint {
        let x = lengthValueAt(
            t: t, p0: start.x, c1: value.controlPoint1?.x ?? start.x, c2: value.controlPoint2.x, p1: value.point.x)
        let y = lengthValueAt(
            t: t, p0: start.y, c1: value.controlPoint1?.y ?? start.y, c2: value.controlPoint2.y, p1: value.point.y)

        return CGPoint(x: x, y: y)
    }

    override public func distanceFrom(point: CGPoint) -> Double {
        var total = 0.0
        var previousPoint = point
        let segments = 1_000

        for i in 1...segments {
            let intervalPoint = poinAt(t: CGFloat(i) / CGFloat(segments), start: point)
            total += intervalPoint.distance(from: previousPoint)  // Command.distanceBetweenPoints(previousPoint, intervalPoint);

            previousPoint = intervalPoint
        }

        return Double(total)
    }

    override public func pointAlongPath(interval: Double, from: CGPoint) -> CGPoint {
        return poinAt(t: CGFloat(interval), start: from)
    }
}


================================================
FILE: Sources/SendKeysLib/Path/PathData.swift
================================================
import Foundation

struct SegmentInfo {
    var startLength: Double
    var length: Double
    var startPoint: CGPoint
    var command: PathCommandBase

    init(_ startLength: Double, _ length: Double, _ startPoint: CGPoint, _ command: PathCommandBase) {
        self.startLength = startLength
        self.length = length
        self.startPoint = startPoint
        self.command = command
    }
}

public class PathData {
    public let commands: [PathCommandBase]
    private let segments: [SegmentInfo]

    convenience init(_ data: String) {
        self.init(data, CGPoint.zero)
    }

    init(_ data: String, _ startPoint: CGPoint) {
        commands = PathData.normalize(PathParser(data).parse(), startPoint)
        segments = PathData.getSegments(commands, startPoint)
    }

    private static func normalize(_ commands: [PathCommandBase], _ startPoint: CGPoint) -> [PathCommandBase] {
        var currentPoint = startPoint
        var pathStart = currentPoint
        var previousCubic: CubicBezierPathCommand?
        var previousQuadratic: QuadraticBezierPathCommand?

        return commands.flatMap { command -> [PathCommandBase] in
            command.makeAbsolute(currentPoint)

            if command.type == "Z" {
                return [PointPathCommand("L", PointValue(pathStart))]
            }
            if command.type == "M", let pointCommand = command as? PointPathCommand {
                pathStart = pointCommand.value.point
            }
            if let cubicCommand = command as? CubicBezierPathCommand {
                if command.type == "S" {
                    if previousCubic != nil {
                        // reflect
                        cubicCommand.value.controlPoint1 = 2 * currentPoint - previousCubic!.value.controlPoint2
                    } else {
                        cubicCommand.value.controlPoint1 = currentPoint
                    }
                }
                previousCubic = cubicCommand
            }
            if let quadtraticCommand = command as? QuadraticBezierPathCommand {
                if command.type == "T" {
                    if previousQuadratic != nil {
                        // reflect
                        quadtraticCommand.value.controlPoint1 =
                            2 * currentPoint - (previousQuadratic!.value.controlPoint1 ?? CGPoint.zero)
                    } else {
                        quadtraticCommand.value.controlPoint1 = currentPoint
                    }
                }
                previousQuadratic = quadtraticCommand
            }

            let newCommands: [PathCommandBase] = command.decompose(from: currentPoint)

            if command.currentPoint != nil {
                currentPoint = command.currentPoint!
            } else if newCommands.last?.currentPoint != nil {
                currentPoint = (newCommands.last?.currentPoint)!
            }

            return newCommands
        }
    }

    static func getSegments(_ commands: [PathCommandBase], _ startPoint: CGPoint) -> [SegmentInfo] {
        var total = 0.0
        var previousPoint = startPoint

        var segments: [SegmentInfo] = []

        for command in commands {
            let length = command.distanceFrom(point: previousPoint)

            if length >= 0 {
                segments.append(SegmentInfo(total, length, previousPoint, command))
            }

            if command.currentPoint != nil {
                previousPoint = command.currentPoint!
            }

            total += length
        }

        return segments
    }

    func getTotalDistance() -> Double {
        let last = segments.last

        if last == nil {
            return 0.0
        }

        return last!.startLength + last!.length
    }

    func getPointAtInterval(_ interval: Double) -> CGPoint {
        let targetLength = getTotalDistance() * max(min(interval, 1), 0)
        let index = segments.firstIndex { segment in
            return segment.startLength > targetLength
        }

        var segment: SegmentInfo?
        if index ?? 0 <= 0 {
            segment = segments.last
        } else {
            segment = segments[index! - 1]
        }

        if segment == nil {
            return CGPoint.zero
        }

        return segment!.command.pointAlongPath(
            interval: (targetLength - segment!.startLength) / segment!.length, from: segment!.startPoint)
    }
}


================================================
FILE: Sources/SendKeysLib/Path/PathParser.swift
================================================
import Foundation

public class PathParser {
    private var index: Int
    private let data: [Character]
    private var previousType: Character?

    init(_ data: String) {
        self.index = 0
        self.data = Array(data)
    }

    public func parse() -> [PathCommandBase] {
        readIgnored()
        var commands: [PathCommandBase] = []

        while case let current = readCommand(false), current != nil {
            previousType = current!.type
            commands.append(current!)
        }

        return commands
    }

    private func readCommand(_ usePrevious: Bool) -> PathCommandBase? {
        let type = usePrevious ? previousType : index < data.count ? data[index] : nil
        var command: PathCommandBase

        if type == nil {
            return nil
        }

        if !usePrevious {
            index += 1
        }

        switch type {
        case "a", "A":
            let radius = readPoint()
            let rotation = readDouble()
            let largeArc = readInt() != 0
            let sweep = readInt() != 0
            let point = readPoint()

            command = ArcPathCommand(type!, ArchCommandValue(radius, rotation, largeArc, sweep, point))
        case "c", "C":
            let point1 = readPoint()
            let point2 = readPoint()
            let point = readPoint()

            command = CubicBezierPathCommand(type!, ControlPointsValue(point1, point2, point))
        case "h", "H", "v", "V":
            command = NumericPathCommand(type!, readDouble())
        case "l", "L", "m", "M":
            command = PointPathCommand(type!, PointValue(readPoint()))
        case "q", "Q":
            let point1 = readPoint()
            let point = readPoint()

            command = QuadraticBezierPathCommand(type!, ControlPointValue(point1, point))
        case "s", "S":
            let point2 = readPoint()
            let point = readPoint()

            command = CubicBezierPathCommand(type!, ControlPointsValue(nil, point2, point))
        case "t", "T":
            let point = readPoint()

            command = QuadraticBezierPathCommand(type!, ControlPointValue(nil, point))
        case "z", "Z":
            command = PathCommandBase(type!)
        default:
            index -= 1
            if previousType != nil {
                return readCommand(true)
            } else {
                return nil
            }
        }

        readIgnored()

        return command
    }

    private func readIgnored() {
        var current: Character
        while index < data.count {
            current = data[index]
            if " \r\n\t,".contains(current) {
                index += 1
            } else {
                break
            }
        }
    }

    private func readDouble() -> Double {
        var current: Character

        readIgnored()

        let start = index
        while index < data.count {
            current = data[index]
            if "+-0123456789eE.".contains(current) {
                index += 1
            } else {
                break
            }
        }

        let end = index

        readIgnored()

        if start == index {
            fatalError("Unable to read Double")
        }

        return Double(String(data[start..<end]))!
    }

    private func readInt() -> Int {
        var current: Character

        readIgnored()

        let start = index
        while index < data.count {
            current = data[index]
            if "+-0123456789eE".contains(current) {
                index += 1
            } else {
                break
            }
        }

        let end = index

        readIgnored()

        if start == index {
            fatalError("Unable to read Int")
        }

        return Int(String(data[start..<end]))!
    }

    private func readPoint() -> CGPoint {
        return CGPoint(x: readDouble(), y: readDouble())
    }
}


================================================
FILE: Sources/SendKeysLib/RuntimeError.swift
================================================
struct RuntimeError: Error {
    let message: String

    init(_ message: String) {
        self.message = message
    }

    public var localizedDescription: String {
        return message
    }
}


================================================
FILE: Sources/SendKeysLib/SendKeysCli.swift
================================================
import ArgumentParser
import Foundation

@available(OSX 10.11, *)
public struct SendKeysCli: ParsableCommand {
    public static let configuration = CommandConfiguration(
        commandName: "sendkeys",
        abstract:
            "Command line tool for automating keystrokes and mouse events. More information: https://github.com/socsieng/sendkeys/blob/main/README.md",
        version: "0.0.0", /* auto-updated */
        subcommands: [Sender.self, AppLister.self, MousePosition.self, Transformer.self],
        defaultSubcommand: Sender.self
    )

    public init() {}
}


================================================
FILE: Sources/SendKeysLib/Sender.swift
================================================
import ArgumentParser
import Cocoa
import Foundation

@available(OSX 10.11, *)
public struct Sender: ParsableCommand {
    public static let configuration = CommandConfiguration(
        commandName: "send",
        abstract: "Sends keystroke and mouse event commands."
    )

    @Option(name: .shortAndLong, help: "Name of a running application to send keys to.")
    var applicationName: String?

    @Option(
        name: NameSpecification([.short, .customLong("pid")]),
        help: "Process id of a running application to send keys to.")
    var processId: Int?

    @Flag(
        name: .long, inversion: FlagInversion.prefixedNo,
        help: "Activate the specified app or process before sending commands.")
    var activate: Bool?

    @Flag(
        name: .long, inversion: FlagInversion.prefixedNo, help: "Only send keystrokes to the targeted app or process.")
    var targeted: Bool?

    @Option(name: .shortAndLong, help: "Default delay between keystrokes in seconds.")
    var delay: Double?

    @Option(name: .shortAndLong, help: "Initial delay before sending commands in seconds.")
    var initialDelay: Double?

    @Option(name: NameSpecification([.customShort("f"), .long]), help: "File containing keystroke instructions.")
    var inputFile: String?

    @Option(name: .shortAndLong, help: "String of characters to send.")
    var characters: String?

    @Option(help: "Number of seconds between animation updates.")
    var animationInterval: Double?

    @Option(name: .shortAndLong, help: "Character sequence to use to terminate execution (e.g. f12:command).")
    var terminateCommand: String?

    @Option(
        name: NameSpecification([.customLong("config")]),
        help: "Configuration file to load settings from (yaml format).")
    var configurationFile: String?

    @Option(name: .long, help: "Keyboard layout to use for sending keystrokes.")
    var keyboardLayout: KeyMappings.Layouts?

    var config: SendConfig

    public init() {
        self.config = SendConfig(
            activate: true, animationInterval: 0.01, delay: 0.1, initialDelay: 1,
            targeted: false, terminateCommand: nil)
    }

    public mutating func run() throws {
        let accessEnabled = AXIsProcessTrustedWithOptions(
            [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: true] as CFDictionary)

        if !accessEnabled {
            fputs(
                "WARNING: Accessibility preferences must be enabled to use this tool. If running from the terminal, make sure that your terminal app has accessibility permissiions enabled.\n\n",
                stderr)
        }

        let activator = AppActivator(appName: applicationName, processId: processId)
        let app: NSRunningApplication? = try activator.find()
        let keyPresser: KeyPresser

        // load from home directory by default
        self.config = self.config.merge(with: ConfigLoader.loadConfig().send)

        if !(configurationFile ?? "").isEmpty {
            self.config = self.config.merge(with: ConfigLoader.loadConfig(configurationFile!).send)
        }

        self.config = self.config.merge(
            with: SendConfig(
                activate: activate, animationInterval: animationInterval, delay: delay, initialDelay: initialDelay,
                keyboardLayout: keyboardLayout, targeted: targeted, terminateCommand: terminateCommand))

        let activate = activate ?? self.config.activate!
        let targeted = targeted ?? self.config.targeted!
        let delay = delay ?? self.config.delay!
        let initialDelay = initialDelay ?? self.config.initialDelay!
        let animationInterval = animationInterval ?? self.config.animationInterval!
        let terminateCommand = terminateCommand ?? self.config.terminateCommand
        let keyboardLayout = keyboardLayout ?? self.config.keyboardLayout

        if keyboardLayout != nil {
            KeyPresser.setKeyboardLayout(keyboardLayout!)
        }

        if self.config.remap != nil {
            KeyCodes.updateMapping(self.config.remap!)
        }

        if targeted {
            if app == nil {
                throw RuntimeError("Application could not be found.")
            }
            keyPresser = KeyPresser(app: app)
        } else {
            keyPresser = KeyPresser(app: nil)
        }

        let mouseController = MouseController(animationRefreshInterval: animationInterval, keyPresser: keyPresser)
        let commandProcessor = CommandsProcessor(
            defaultPause: delay, keyPresser: keyPresser, mouseController: mouseController)
        var commandString: String?

        if !(inputFile ?? "").isEmpty {
            if let data = FileManager.default.contents(atPath: inputFile!) {
                commandString = String(data: data, encoding: .utf8)
            } else {
                fatalError("Could not read file \(inputFile!)\n")
            }
        } else if !(characters ?? "").isEmpty {
            commandString = characters
        }

        var listener: TerminationListener?
        if terminateCommand != nil && !terminateCommand!.isEmpty {
            listener = TerminationListener(sequence: terminateCommand!) {
                Sender.exit()
            }
            listener!.listen()
        }

        if activate {
            try activator.activate()
        }

        if initialDelay > 0 {
            Sleeper.sleep(seconds: initialDelay)
        }

        if !(commandString ?? "").isEmpty {
            commandProcessor.process(commandString!)
            Sleeper.sleep(seconds: 0.01)
        } else if !isTty() {
            var data: Data

            repeat {
                data = FileHandle.standardInput.availableData

                if data.count > 0 {
                    commandString = String(data: data, encoding: .utf8)
                    commandProcessor.process(commandString!)
                }
            } while data.count > 0
        } else {
            print(SendKeysCli.helpMessage(for: Self.self))
        }

        if listener != nil {
            listener!.stop()
        }
    }
}


================================================
FILE: Sources/SendKeysLib/Sleeper.swift
================================================
import Foundation

struct Sleeper {
    static func sleep(seconds: Double) {
        usleep(useconds_t(seconds * 1_000_000))
    }
}


================================================
FILE: Sources/SendKeysLib/TerminationListener.swift
================================================
import Cocoa
import Foundation

class TerminationListener {
    private var keycode: UInt16
    private var modifiers: [CGEventFlags]
    private var callback: () -> Void
    private let flags: [CGEventFlags] = [.maskCommand, .maskControl, .maskShift, .maskAlternate]
    private var runLoopSource: CFRunLoopSource?
    private var runLoop: CFRunLoop?
    private let expression = try! NSRegularExpression(pattern: "^(.|[\\w]+)(:([,\\w⌘^⌥⇧]+))?$")

    init(sequence: String, callback: @escaping () -> Void) {
        guard let groups = getRegexGroups(expression, sequence) else {
            fatalError("Invalid sequence: \(sequence)")
        }
        let modifiers = groups[3]?.split(separator: ",") ?? []

        self.keycode = KeyCodes.getKeyInfo(groups[1]!)!.keyCode
        do {
            self.modifiers = try modifiers.map {
                (modifier) -> CGEventFlags in
                try KeyPresser.getModifierFlag(String(modifier))
            }
        } catch {
            fatalError("Failed to get modifier flags: \(error)")
        }
        self.callback = callback
    }

    func listen() {
        DispatchQueue.global(qos: .background).async {
            self.listenSync()
        }
    }

    private func listenSync() {
        let eventMask = 1 << CGEventType.keyDown.rawValue

        self.runLoop = CFRunLoopGetCurrent()
        let info = UnsafeMutableRawPointer(mutating: bridge(obj: self))

        guard
            let eventTap = CGEvent.tapCreate(
                tap: .cghidEventTap, place: .tailAppendEventTap, options: .defaultTap,
                eventsOfInterest: CGEventMask(eventMask),
                callback: {
                    (proxy: CGEventTapProxy, eventType: CGEventType, event: CGEvent, refcon: UnsafeMutableRawPointer?)
                        -> Unmanaged<CGEvent>? in

                    let listener: TerminationListener = bridge(ptr: UnsafeRawPointer(refcon)!)
                    let keycode = event.getIntegerValueField(.keyboardEventKeycode)

                    if keycode == listener.keycode {
                        var flagsMatch = true
                        for flag in listener.flags {
                            if event.flags.contains(flag) != listener.modifiers.contains(flag) {
                                flagsMatch = false
                                break
                            }
                        }

                        if flagsMatch {
                            listener.callback()
                        }
                    }

                    return Unmanaged.passRetained(event)
                }, userInfo: info)
        else {
            fatalError("Failed to create event tap.")
        }

        self.runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
        CFRunLoopAddSource(self.runLoop, runLoopSource, .commonModes)
        CGEvent.tapEnable(tap: eventTap, enable: true)
        CFRunLoopRun()
    }

    func stop() {
        guard let runLoopSource = self.runLoopSource else {
            return
        }

        guard let runLoop = self.runLoop else {
            return
        }

        CFRunLoopRemoveSource(runLoop, runLoopSource, .commonModes)
        CFRunLoopStop(runLoop)
    }
}


================================================
FILE: Sources/SendKeysLib/Transformer.swift
================================================
import ArgumentParser
import Foundation

@available(OSX 10.11, *)
class Transformer: ParsableCommand {
    public static let configuration = CommandConfiguration(
        commandName: "transform",
        abstract:
            "Transforms raw text input into application friendly character sequences. Examples include accounting for applications that automatically indent source code and insert closing brackets."
    )

    @Flag(
        name: .shortAndLong, inversion: FlagInversion.prefixedNo,
        help: "Determines if the application automatically inserts indentation.")
    var indent: Bool?

    @Option(
        name: .shortAndLong,
        help:
            "Specifies which brackets are automatically closed by the application and don't need to be explicitly closed."
    )
    var autoClose: String?

    @Option(
        name: NameSpecification([.customShort("f"), .long]),
        help: "File containing keystroke instructions to transform.")
    var inputFile: String?

    @Option(name: .shortAndLong, help: "String of characters to transform.")
    var characters: String?

    var config: TransformerConfig

    public init(indent: Bool, autoClose: String = "}])") {
        self.config = TransformerConfig(indent: indent, autoClose: autoClose)
    }

    required init() {
        self.config = TransformerConfig(indent: true, autoClose: "}])")
    }

    func run() {
        var commandString: String?
        self.config = self.config
            .merge(with: ConfigLoader.loadConfig().transformer)
            .merge(with: TransformerConfig(indent: indent, autoClose: autoClose))

        if !(inputFile ?? "").isEmpty {
            if let data = FileManager.default.contents(atPath: inputFile!) {
                commandString = String(data: data, encoding: .utf8)
            } else {
                fatalError("Could not read file \(inputFile!)\n")
            }
        } else if !(characters ?? "").isEmpty {
            commandString = characters
        }

        if !(commandString ?? "").isEmpty {
            fputs(transform(commandString!), stdout)
        } else if !isTty() {
            var data: Data

            repeat {
                data = FileHandle.standardInput.availableData

                if data.count > 0 {
                    commandString = String(data: data, encoding: .utf8)
                    fputs(transform(commandString!), stdout)
                }
            } while data.count > 0
        } else {
            print(SendKeysCli.helpMessage(for: Self.self))
        }
    }

    func transform(_ input: String) -> String {
        var output = input

        if self.config.indent! {
            let removeIndentExpression = try! NSRegularExpression(pattern: "^[\\t ]+", options: .anchorsMatchLines)
            let range = NSRange(location: 0, length: output.count)
            output = removeIndentExpression.stringByReplacingMatches(
                in: output, options: [], range: range, withTemplate: "")
        }

        if !self.config.autoClose!.isEmpty {
            let removeBracketExpression = try! NSRegularExpression(
                pattern:
                    "\\n[\\t ]*[\(NSRegularExpression.escapedPattern(for: self.config.autoClose!).replacingOccurrences(of: "]", with: "\\]"))]+"
            )
            let range = NSRange(location: 0, length: output.count)
            output = removeBracketExpression.stringByReplacingMatches(
                in: output, options: .withoutAnchoringBounds, range: range,
                withTemplate: "<\\\\>\n<c:down><p:0><c:right:command>")
        }

        return output
    }
}


================================================
FILE: Sources/SendKeysLib/Utilities.swift
================================================
import Foundation

func isTty() -> Bool {
    return isatty(FileHandle.standardInput.fileDescriptor) == 1
}

func getRegexGroups(_ expression: NSRegularExpression, _ input: String) -> [String?]? {
    var groups: [String?] = []
    let matchResult = expression.firstMatch(
        in: input, options: .anchored, range: NSRange(location: 0, length: input.utf8.count))

    if matchResult == nil {
        return nil
    }

    let numberOfRanges = matchResult!.numberOfRanges

    for i in 0..<numberOfRanges {
        let range = Range(matchResult!.range(at: i), in: input)
        let arg = range == nil ? nil : String(input[range!])
        groups.append(arg)
    }

    return groups
}


================================================
FILE: Sources/sendkeys/main.swift
================================================
import Foundation
import SendKeysLib

if #available(OSX 10.11, *) {
    SendKeysCli.main()
} else {
    print("OS version 10.11 or higher is required.")
}


================================================
FILE: Tests/LinuxMain.swift
================================================
import XCTest
import sendkeysTests

var tests = [XCTestCaseEntry]()
tests += sendkeysTests.allTests()
XCTMain(tests)


================================================
FILE: Tests/SendKeysTests/CommandIteratorTests.swift
================================================
import XCTest

@testable import SendKeysLib

final class CommandIteratorTests: XCTestCase {
    var commandFactory: CommandFactory!

    override func setUp() {
        let keyPresser = KeyPresser(app: nil)
        commandFactory = CommandFactory(
            keyPresser: keyPresser,
            mouseController: MouseController(animationRefreshInterval: 0.01, keyPresser: keyPresser))
    }

    func testParsesCharacters() throws {
        let commands = getCommands(CommandsIterator("abc", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                DefaultCommand(key: "a"),
                DefaultCommand(key: "b"),
                DefaultCommand(key: "c"),
            ])
    }

    func testParsesKeyPress() throws {
        let commands = getCommands(CommandsIterator("<c:a>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                KeyPressCommand(key: "a", modifiers: [])
            ])
    }

    func testParsesKeyPressDelete() throws {
        let commands = getCommands(CommandsIterator("<c:delete>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                KeyPressCommand(key: "delete", modifiers: [])
            ])
    }

    func testParsesKeyPressesWithModifierKey() throws {
        let commands = getCommands(CommandsIterator("<c:a:command>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                KeyPressCommand(key: "a", modifiers: ["command"])
            ])
    }

    func testParsesKeyPressesWithModifierKeys() throws {
        let commands = getCommands(CommandsIterator("<c:a:command,shift>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                KeyPressCommand(key: "a", modifiers: ["command", "shift"])
            ])
    }

    func testParsesKeyPressAlias() throws {
        let commands = getCommands(CommandsIterator("<k:a>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                KeyPressCommand(key: "a", modifiers: [])
            ])
    }

    func testParsesKeyDown() throws {
        let commands = getCommands(CommandsIterator("<kd:a>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                KeyDownCommand(key: "a", modifiers: [])
            ])
    }

    func testParsesKeyDownWithModifierKey() throws {
        let commands = getCommands(CommandsIterator("<kd:a:shift>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                KeyDownCommand(key: "a", modifiers: ["shift"])
            ])
    }

    func testParsesKeyDownAsModifierKey() throws {
        let commands = getCommands(CommandsIterator("<kd:shift>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                KeyDownCommand(key: "shift", modifiers: [])
            ])
    }

    func testParsesKeyUp() throws {
        let commands = getCommands(CommandsIterator("<ku:a>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                KeyUpCommand(key: "a", modifiers: [])
            ])
    }

    func testParsesKeyUpWithModifierKey() throws {
        let commands = getCommands(CommandsIterator("<ku:a:shift>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                KeyUpCommand(key: "a", modifiers: ["shift"])
            ])
    }

    func testParsesKeyUpAsModifierKey() throws {
        let commands = getCommands(CommandsIterator("<ku:shift>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                KeyUpCommand(key: "shift", modifiers: [])
            ])
    }

    func testParsesNewLines() throws {
        let commands = getCommands(CommandsIterator("\n\n\n", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                NewlineCommand(),
                NewlineCommand(),
                NewlineCommand(),
            ])
    }

    func testParsesNewLinesWithCarriageReturns() throws {
        let commands = getCommands(CommandsIterator("\r\n\r\n\n", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                NewlineCommand(),
                NewlineCommand(),
                NewlineCommand(),
            ])
    }

    func testParsesMultipleKeyPresses() throws {
        let commands = getCommands(CommandsIterator("<c:a:command><c:c:command>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                KeyPressCommand(key: "a", modifiers: ["command"]),
                KeyPressCommand(key: "c", modifiers: ["command"]),
            ])
    }

    func testParsesContinuation() throws {
        let commands = getCommands(CommandsIterator("<\\>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                ContinuationCommand()
            ])
    }

    func testParsesPause() throws {
        let commands = getCommands(CommandsIterator("<p:0.2>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                PauseCommand(duration: 0.2)
            ])
    }

    func testParsesStickyPause() throws {
        let commands = getCommands(CommandsIterator("<P:0.2>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                StickyPauseCommand(duration: 0.2)
            ])
    }

    func testParsesMouseMove() throws {
        let commands = getCommands(CommandsIterator("<m:1.5,2.5,3.5,4.5>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MouseMoveCommand(x1: 1.5, y1: 2.5, x2: 3.5, y2: 4.5, duration: 0, modifiers: [])
            ])
    }

    func testParsesMouseMoveWithModifier() throws {
        let commands = getCommands(CommandsIterator("<m:1,2,3,4:command>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MouseMoveCommand(x1: 1, y1: 2, x2: 3, y2: 4, duration: 0, modifiers: ["command"])
            ])
    }

    func testParsesMouseMoveWithDuration() throws {
        let commands = getCommands(CommandsIterator("<m:1,2,3,4:0.1>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MouseMoveCommand(x1: 1, y1: 2, x2: 3, y2: 4, duration: 0.1, modifiers: [])
            ])
    }

    func testParsesMouseMoveWithNegativeCoordinates() throws {
        let commands = getCommands(CommandsIterator("<m:-1,-2,-3,-4:0.1>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MouseMoveCommand(x1: -1, y1: -2, x2: -3, y2: -4, duration: 0.1, modifiers: [])
            ])
    }

    func testParsesMouseMoveWithDurationAndModifiers() throws {
        let commands = getCommands(CommandsIterator("<m:1,2,3,4:0.1:shift,command>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MouseMoveCommand(x1: 1, y1: 2, x2: 3, y2: 4, duration: 0.1, modifiers: ["shift", "command"])
            ])
    }

    func testParsesPartialMouseMove() throws {
        let commands = getCommands(CommandsIterator("<m:3,4>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MouseMoveCommand(x1: nil, y1: nil, x2: 3, y2: 4, duration: 0, modifiers: [])
            ])
    }

    func testParsesPartialMouseMoveWithDuration() throws {
        let commands = getCommands(CommandsIterator("<m:3,4:2>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MouseMoveCommand(x1: nil, y1: nil, x2: 3, y2: 4, duration: 2, modifiers: [])
            ])
    }

    func testParsesMouseClick() throws {
        let commands = getCommands(CommandsIterator("<m:left>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MouseClickCommand(button: "left", modifiers: [], clicks: 1)
            ])
    }

    func testParsesMouseClickWithModifiers() throws {
        let commands = getCommands(CommandsIterator("<m:left:shift,command>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MouseClickCommand(button: "left", modifiers: ["shift", "command"], clicks: 1)
            ])
    }

    func testParsesMouseClickWithClickCount() throws {
        let commands = getCommands(CommandsIterator("<m:right:2>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MouseClickCommand(button: "right", modifiers: [], clicks: 2)
            ])
    }

    func testParsesMouseClickWithModifiersAndClickCount() throws {
        let commands = getCommands(CommandsIterator("<m:right:command:2>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MouseClickCommand(button: "right", modifiers: ["command"], clicks: 2)
            ])
    }

    func testParsesMousePath() throws {
        let commands = getCommands(CommandsIterator("<mpath:L 200 400:2>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MousePathCommand(
                    path: "L 200 400", offsetX: 0, offsetY: 0, scaleX: 1, scaleY: 1, duration: 2, modifiers: [])
            ])
    }

    func testParsesMousePathWithOffset() throws {
        let commands = getCommands(CommandsIterator("<mpath:L 200 400:100,200:2>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MousePathCommand(
                    path: "L 200 400", offsetX: 100, offsetY: 200, scaleX: 1, scaleY: 1, duration: 2, modifiers: [])
            ])
    }

    func testParsesMousePathWithOffsetAndScale() throws {
        let commands = getCommands(
            CommandsIterator("<mpath:L 200 400:100,200,0.5,2.5:2>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MousePathCommand(
                    path: "L 200 400", offsetX: 100, offsetY: 200, scaleX: 0.5, scaleY: 2.5, duration: 2, modifiers: [])
            ])
    }

    func testParsesMousePathWithOffsetAndPartialScale() throws {
        let commands = getCommands(CommandsIterator("<mpath:L 200 400:100,200,0.4:2>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MousePathCommand(
                    path: "L 200 400", offsetX: 100, offsetY: 200, scaleX: 0.4, scaleY: 0.4, duration: 2, modifiers: [])
            ])
    }

    func testParsesMouseDrag() throws {
        let commands = getCommands(CommandsIterator("<d:1.5,2.5,3.5,4.5>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MouseDragCommand(x1: 1.5, y1: 2.5, x2: 3.5, y2: 4.5, duration: 0, button: "left", modifiers: [])
            ])
    }

    func testParsesMouseDragWithButton() throws {
        let commands = getCommands(CommandsIterator("<d:1,2,3,4:right>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MouseDragCommand(x1: 1, y1: 2, x2: 3, y2: 4, duration: 0, button: "right", modifiers: [])
            ])
    }

    func testParsesMouseDragWithButtonAndModifiers() throws {
        let commands = getCommands(CommandsIterator("<d:1,2,3,4:right:command,shift>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MouseDragCommand(
                    x1: 1, y1: 2, x2: 3, y2: 4, duration: 0, button: "right", modifiers: ["command", "shift"])
            ])
    }

    func testParsesMouseDragWithDuration() throws {
        let commands = getCommands(CommandsIterator("<d:1,2,3,4:0.1>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MouseDragCommand(x1: 1, y1: 2, x2: 3, y2: 4, duration: 0.1, button: "left", modifiers: [])
            ])
    }

    func testParsesMouseDragWithDurationWithNegativeCoordinates() throws {
        let commands = getCommands(CommandsIterator("<d:-1.5,-2,-3,-4:0.1>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MouseDragCommand(x1: -1.5, y1: -2, x2: -3, y2: -4, duration: 0.1, button: "left", modifiers: [])
            ])
    }

    func testParsesMouseDragWithDurationAndButton() throws {
        let commands = getCommands(CommandsIterator("<d:1,2,3,4:0.1:right>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MouseDragCommand(x1: 1, y1: 2, x2: 3, y2: 4, duration: 0.1, button: "right", modifiers: [])
            ])
    }

    func testParsesMouseDragWithDurationAndButtonAndModifier() throws {
        let commands = getCommands(CommandsIterator("<d:1,2,3,4:0.1:right:command>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MouseDragCommand(x1: 1, y1: 2, x2: 3, y2: 4, duration: 0.1, button: "right", modifiers: ["command"])
            ])
    }

    func testParsesPartialMouseDrag() throws {
        let commands = getCommands(CommandsIterator("<d:3,4>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MouseDragCommand(x1: nil, y1: nil, x2: 3, y2: 4, duration: 0, button: "left", modifiers: [])
            ])
    }

    func testParsesPartialMouseDragWithDuration() throws {
        let commands = getCommands(CommandsIterator("<d:3,4:2>", commandFactory: commandFactory))
        XCTAssertEqual(
            commands,
            [
                MouseDragCommand(x1: nil, y1: nil, x2: 3, y2: 4, duration: 2, button: "left", modifiers: [])
            ])
    }

    func testParsesPa
Download .txt
gitextract_cwwrie3z/

├── .editorconfig
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.yaml
│   │   └── feature_request.yaml
│   └── workflows/
│       └── build.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.yml
├── .swift-format
├── .vscode/
│   ├── extensions.json
│   ├── launch.json
│   ├── settings.json
│   └── tasks.json
├── CHANGELOG.md
├── Formula/
│   └── sendkeys_template.rb
├── LICENSE
├── Makefile
├── Package.resolved
├── Package.swift
├── README.md
├── Sources/
│   ├── SendKeysLib/
│   │   ├── Animator.swift
│   │   ├── AppActivator.swift
│   │   ├── AppLister.swift
│   │   ├── Bridge.swift
│   │   ├── Commands/
│   │   │   ├── Command.swift
│   │   │   ├── CommandExecutor.swift
│   │   │   ├── CommandFactory.swift
│   │   │   ├── CommandsIterator.swift
│   │   │   ├── CommandsProcessor.swift
│   │   │   ├── ContinuationCommand.swift
│   │   │   ├── DefaultCommand.swift
│   │   │   ├── KeyDownCommand.swift
│   │   │   ├── KeyPressCommand.swift
│   │   │   ├── KeyUpCommand.swift
│   │   │   ├── MouseClickCommand.swift
│   │   │   ├── MouseDownCommand.swift
│   │   │   ├── MouseDragCommand.swift
│   │   │   ├── MouseFocusCommand.swift
│   │   │   ├── MouseMoveCommand.swift
│   │   │   ├── MousePathCommand.swift
│   │   │   ├── MouseScrollCommand.swift
│   │   │   ├── MouseUpCommand.swift
│   │   │   ├── NewlineCommand.swift
│   │   │   ├── PauseCommand.swift
│   │   │   └── StickyPauseCommand.swift
│   │   ├── Configuration/
│   │   │   ├── AllConfiguration.swift
│   │   │   ├── ConfigLoader.swift
│   │   │   ├── MousePositionConfig.swift
│   │   │   ├── SendConfig.swift
│   │   │   └── TransformerConfig.swift
│   │   ├── KeyCodes.swift
│   │   ├── KeyMappings.swift
│   │   ├── KeyPresser.swift
│   │   ├── MouseController.swift
│   │   ├── MouseEventProcessor.swift
│   │   ├── MousePosition.swift
│   │   ├── Path/
│   │   │   ├── Extensions.swift
│   │   │   ├── PathCommands.swift
│   │   │   ├── PathData.swift
│   │   │   └── PathParser.swift
│   │   ├── RuntimeError.swift
│   │   ├── SendKeysCli.swift
│   │   ├── Sender.swift
│   │   ├── Sleeper.swift
│   │   ├── TerminationListener.swift
│   │   ├── Transformer.swift
│   │   └── Utilities.swift
│   └── sendkeys/
│       └── main.swift
├── Tests/
│   ├── LinuxMain.swift
│   ├── SendKeysTests/
│   │   ├── CommandIteratorTests.swift
│   │   ├── CommandsProcessorTests.swift
│   │   ├── KeyPresserTests.swift
│   │   ├── PathDataTests.swift
│   │   ├── PathParserTests.swift
│   │   ├── TransformerTests.swift
│   │   ├── XCTestManifests.swift
│   │   └── sendkeysTests.swift
│   └── keys.txt
├── examples/
│   ├── .sendkeysrc.yml
│   └── node.js
├── scripts/
│   ├── bottle.sh
│   ├── code-coverage.sh
│   ├── format.sh
│   ├── install-pre-commit.sh
│   ├── pre-commit.sh
│   ├── update-version.sh
│   └── verify-output.sh
└── version.txt
Download .txt
SYMBOL INDEX (2 symbols across 1 files)

FILE: Formula/sendkeys_template.rb
  class Sendkeys (line 3) | class Sendkeys < Formula
    method install (line 12) | def install
Condensed preview — 87 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (234K chars).
[
  {
    "path": ".editorconfig",
    "chars": 142,
    "preview": "root = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.swift]\nindent_style = "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yaml",
    "chars": 990,
    "preview": "name: Bug Report\ndescription: Report a bug you've found\ntitle: \"[bug]: \"\nlabels: [bug]\nbody:\n  - type: markdown\n    attr"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yaml",
    "chars": 842,
    "preview": "name: Feature Request\ndescription: Suggest an idea for this project\ntitle: \"[feature]: \"\nlabels: [enhancement]\nbody:\n  -"
  },
  {
    "path": ".github/workflows/build.yml",
    "chars": 4783,
    "preview": "name: build\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\njobs:\n  build:\n    runs-"
  },
  {
    "path": ".gitignore",
    "chars": 111,
    "preview": ".DS_Store\n/.build\n/Packages\n/*.xcodeproj\nxcuserdata/\n\n.swiftpm/\n\n*.bak\n\nFormula/sendkeys.rb\n*.tar.gz\n\n/.output\n"
  },
  {
    "path": ".prettierignore",
    "chars": 36,
    "preview": "node_modules/\n.build/\n\nCHANGELOG.md\n"
  },
  {
    "path": ".prettierrc.yml",
    "chars": 207,
    "preview": "arrowParens: avoid\nprintWidth: 120\nquoteProps: consistent\nsemi: true\nsingleQuote: true\ntabWidth: 2\ntrailingComma: all\nov"
  },
  {
    "path": ".swift-format",
    "chars": 1867,
    "preview": "{\n  \"fileScopedDeclarationPrivacy\": {\n    \"accessLevel\": \"private\"\n  },\n  \"indentation\": {\n    \"spaces\": 4\n  },\n  \"inden"
  },
  {
    "path": ".vscode/extensions.json",
    "chars": 111,
    "preview": "{\n  \"recommendations\": [\"vknabel.vscode-apple-swift-format\", \"sswg.swift-lang\", \"editorconfig.editorconfig\"]\n}\n"
  },
  {
    "path": ".vscode/launch.json",
    "chars": 665,
    "preview": "{\n  \"configurations\": [\n    {\n      \"type\": \"lldb\",\n      \"request\": \"launch\",\n      \"sourceLanguages\": [\"swift\"],\n     "
  },
  {
    "path": ".vscode/settings.json",
    "chars": 34,
    "preview": "{\n  \"editor.formatOnSave\": true\n}\n"
  },
  {
    "path": ".vscode/tasks.json",
    "chars": 244,
    "preview": "{\n  // See https://go.microsoft.com/fwlink/?LinkId=733558\n  // for the documentation about the tasks.json format\n  \"vers"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 18878,
    "preview": "# Changelog\n\n## [4.3.1](https://github.com/socsieng/sendkeys/compare/v4.3.0...v4.3.1) (2024-12-14)\n\n\n### Bug Fixes\n\n* ad"
  },
  {
    "path": "Formula/sendkeys_template.rb",
    "chars": 462,
    "preview": "# Documentation: https://docs.brew.sh/Formula-Cookbook\n#                https://rubydoc.brew.sh/Formula\nclass Sendkeys <"
  },
  {
    "path": "LICENSE",
    "chars": 11358,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "Makefile",
    "chars": 456,
    "preview": "prefix ?= /usr/local\nbindir ?= $(prefix)/bin\n\n.PHONY: build\nbuild:\n\t@scripts/update-version.sh\n\t@swift build -c release "
  },
  {
    "path": "Package.resolved",
    "chars": 1695,
    "preview": "{\n  \"object\": {\n    \"pins\": [\n      {\n        \"package\": \"swift-argument-parser\",\n        \"repositoryURL\": \"https://gith"
  },
  {
    "path": "Package.swift",
    "chars": 1363,
    "preview": "// swift-tools-version:5.5\n// The swift-tools-version declares the minimum version of Swift required to build this packa"
  },
  {
    "path": "README.md",
    "chars": 16659,
    "preview": "# SendKeys\n\n![Build status](https://github.com/socsieng/sendkeys/workflows/build/badge.svg)\n![Homebrew installs](https:/"
  },
  {
    "path": "Sources/SendKeysLib/Animator.swift",
    "chars": 912,
    "preview": "import Foundation\n\nclass Animator {\n    typealias AnimationCallback = (_ progress: Double) -> Void\n\n    let duration: Ti"
  },
  {
    "path": "Sources/SendKeysLib/AppActivator.swift",
    "chars": 3678,
    "preview": "import Cocoa\n\nclass AppActivator: NSObject {\n    private var application: NSRunningApplication!\n    private let appName:"
  },
  {
    "path": "Sources/SendKeysLib/AppLister.swift",
    "chars": 1555,
    "preview": "import ArgumentParser\nimport Cocoa\nimport Foundation\n\nclass AppLister: ParsableCommand {\n    public static let configura"
  },
  {
    "path": "Sources/SendKeysLib/Bridge.swift",
    "chars": 511,
    "preview": "func bridge<T: AnyObject>(obj: T) -> UnsafeRawPointer {\n    return UnsafeRawPointer(Unmanaged.passUnretained(obj).toOpaq"
  },
  {
    "path": "Sources/SendKeysLib/Commands/Command.swift",
    "chars": 1685,
    "preview": "import Foundation\n\npublic enum CommandType {\n    case undefined\n    case keyPress\n    case keyDown\n    case keyUp\n    ca"
  },
  {
    "path": "Sources/SendKeysLib/Commands/CommandExecutor.swift",
    "chars": 242,
    "preview": "import Foundation\n\npublic protocol CommandExecutorProtocol {\n    func execute(_ command: Command)\n}\n\npublic class Comman"
  },
  {
    "path": "Sources/SendKeysLib/Commands/CommandFactory.swift",
    "chars": 1464,
    "preview": "public class CommandFactory {\n    public static let commands: [Command.Type] = [\n        KeyPressCommand.self,\n        K"
  },
  {
    "path": "Sources/SendKeysLib/Commands/CommandsIterator.swift",
    "chars": 1851,
    "preview": "import Foundation\n\npublic class CommandsIterator: IteratorProtocol {\n    public typealias Element = Command\n\n    let com"
  },
  {
    "path": "Sources/SendKeysLib/Commands/CommandsProcessor.swift",
    "chars": 2453,
    "preview": "import Foundation\n\npublic class CommandsProcessor {\n    var defaultPause: TimeInterval\n\n    let numberFormatter = Number"
  },
  {
    "path": "Sources/SendKeysLib/Commands/ContinuationCommand.swift",
    "chars": 470,
    "preview": "import Foundation\n\npublic class ContinuationCommand: Command {\n    public override class var commandType: CommandType { "
  },
  {
    "path": "Sources/SendKeysLib/Commands/DefaultCommand.swift",
    "chars": 422,
    "preview": "import Foundation\n\npublic class DefaultCommand: KeyPressCommand {\n    private static let _expression = try! NSRegularExp"
  },
  {
    "path": "Sources/SendKeysLib/Commands/KeyDownCommand.swift",
    "chars": 681,
    "preview": "import Foundation\n\npublic class KeyDownCommand: KeyPressCommand {\n    public override class var commandType: CommandType"
  },
  {
    "path": "Sources/SendKeysLib/Commands/KeyPressCommand.swift",
    "chars": 1455,
    "preview": "import Foundation\n\npublic class KeyPressCommand: Command, RequiresKeyPresser {\n    public override class var commandType"
  },
  {
    "path": "Sources/SendKeysLib/Commands/KeyUpCommand.swift",
    "chars": 675,
    "preview": "import Foundation\n\npublic class KeyUpCommand: KeyPressCommand {\n    public override class var commandType: CommandType {"
  },
  {
    "path": "Sources/SendKeysLib/Commands/MouseClickCommand.swift",
    "chars": 2213,
    "preview": "import Cocoa\nimport Foundation\n\npublic class MouseClickCommand: Command, RequiresMouseController {\n    public override c"
  },
  {
    "path": "Sources/SendKeysLib/Commands/MouseDownCommand.swift",
    "chars": 981,
    "preview": "import Foundation\n\npublic class MouseDownCommand: MouseClickCommand {\n    public override class var commandType: Command"
  },
  {
    "path": "Sources/SendKeysLib/Commands/MouseDragCommand.swift",
    "chars": 1835,
    "preview": "import Foundation\n\npublic class MouseDragCommand: MouseMoveCommand {\n    public override class var commandType: CommandT"
  },
  {
    "path": "Sources/SendKeysLib/Commands/MouseFocusCommand.swift",
    "chars": 2253,
    "preview": "import Foundation\n\npublic class MouseFocusCommand: MouseClickCommand {\n    public override class var commandType: Comman"
  },
  {
    "path": "Sources/SendKeysLib/Commands/MouseMoveCommand.swift",
    "chars": 2270,
    "preview": "import Foundation\n\npublic class MouseMoveCommand: MouseClickCommand {\n    public override class var commandType: Command"
  },
  {
    "path": "Sources/SendKeysLib/Commands/MousePathCommand.swift",
    "chars": 2620,
    "preview": "import Foundation\n\npublic class MousePathCommand: MouseClickCommand {\n    public override class var commandType: Command"
  },
  {
    "path": "Sources/SendKeysLib/Commands/MouseScrollCommand.swift",
    "chars": 1740,
    "preview": "import Foundation\n\npublic class MouseScrollCommand: MouseClickCommand {\n    public override class var commandType: Comma"
  },
  {
    "path": "Sources/SendKeysLib/Commands/MouseUpCommand.swift",
    "chars": 975,
    "preview": "import Foundation\n\npublic class MouseUpCommand: MouseClickCommand {\n    public override class var commandType: CommandTy"
  },
  {
    "path": "Sources/SendKeysLib/Commands/NewlineCommand.swift",
    "chars": 408,
    "preview": "import Foundation\n\npublic class NewlineCommand: KeyPressCommand {\n    private static let _expression = try! NSRegularExp"
  },
  {
    "path": "Sources/SendKeysLib/Commands/PauseCommand.swift",
    "chars": 1077,
    "preview": "import Foundation\n\npublic class PauseCommand: Command {\n    public override class var commandType: CommandType { return "
  },
  {
    "path": "Sources/SendKeysLib/Commands/StickyPauseCommand.swift",
    "chars": 326,
    "preview": "import Foundation\n\npublic class StickyPauseCommand: PauseCommand {\n    public override class var commandType: CommandTyp"
  },
  {
    "path": "Sources/SendKeysLib/Configuration/AllConfiguration.swift",
    "chars": 764,
    "preview": "struct AllConfiguration: Codable {\n    var send: SendConfig?\n    var mousePosition: MousePositionConfig?\n    var transfo"
  },
  {
    "path": "Sources/SendKeysLib/Configuration/ConfigLoader.swift",
    "chars": 1060,
    "preview": "import Foundation\nimport Yams\n\nlet defaultConfigFiles = [\n    NSString(\"~/.sendkeysrc.yml\").expandingTildeInPath,\n    NS"
  },
  {
    "path": "Sources/SendKeysLib/Configuration/MousePositionConfig.swift",
    "chars": 573,
    "preview": "struct MousePositionConfig: Codable {\n    var watch: Bool?\n    var output: OutputMode?\n    var duration: Double?\n\n    in"
  },
  {
    "path": "Sources/SendKeysLib/Configuration/SendConfig.swift",
    "chars": 1483,
    "preview": "struct SendConfig: Codable {\n    var activate: Bool?\n    var animationInterval: Double?\n    var delay: Double?\n    var i"
  },
  {
    "path": "Sources/SendKeysLib/Configuration/TransformerConfig.swift",
    "chars": 445,
    "preview": "struct TransformerConfig: Codable {\n    var indent: Bool?\n    var autoClose: String?\n\n    init(indent: Bool? = nil, auto"
  },
  {
    "path": "Sources/SendKeysLib/KeyCodes.swift",
    "chars": 13004,
    "preview": "import Cocoa\n\n// From: https://gist.github.com/swillits/df648e87016772c7f7e5dbed2b345066\nstruct KeyCodes {\n    // Layout"
  },
  {
    "path": "Sources/SendKeysLib/KeyMappings.swift",
    "chars": 1561,
    "preview": "import ArgumentParser\n\nstruct KeyMappings {\n    enum Layouts: String, Codable, ExpressibleByArgument {\n        case qwer"
  },
  {
    "path": "Sources/SendKeysLib/KeyPresser.swift",
    "chars": 4373,
    "preview": "import Carbon\nimport Cocoa\nimport Foundation\n\npublic class KeyPresser {\n    private var application: NSRunningApplicatio"
  },
  {
    "path": "Sources/SendKeysLib/MouseController.swift",
    "chars": 10102,
    "preview": "import Cocoa\nimport Foundation\n\nclass MouseController {\n    enum ScrollAxis {\n        case horizontal\n        case verti"
  },
  {
    "path": "Sources/SendKeysLib/MouseEventProcessor.swift",
    "chars": 4704,
    "preview": "import Cocoa\nimport Foundation\n\nenum MouseEventType {\n    case click\n    case drag\n}\n\nenum MouseButton: String, CustomSt"
  },
  {
    "path": "Sources/SendKeysLib/MousePosition.swift",
    "chars": 6679,
    "preview": "import ArgumentParser\nimport Cocoa\nimport Foundation\n\nenum OutputMode: String, Codable, ExpressibleByArgument {\n    case"
  },
  {
    "path": "Sources/SendKeysLib/Path/Extensions.swift",
    "chars": 803,
    "preview": "import AppKit\nimport Foundation\n\nextension CGPoint {\n    // Vector math\n    public static func + (left: CGPoint, right: "
  },
  {
    "path": "Sources/SendKeysLib/Path/PathCommands.swift",
    "chars": 11409,
    "preview": "import Foundation\n\npublic class PointValue: CustomStringConvertible {\n    var point: CGPoint\n\n    init(_ point: CGPoint)"
  },
  {
    "path": "Sources/SendKeysLib/Path/PathData.swift",
    "chars": 4409,
    "preview": "import Foundation\n\nstruct SegmentInfo {\n    var startLength: Double\n    var length: Double\n    var startPoint: CGPoint\n "
  },
  {
    "path": "Sources/SendKeysLib/Path/PathParser.swift",
    "chars": 3904,
    "preview": "import Foundation\n\npublic class PathParser {\n    private var index: Int\n    private let data: [Character]\n    private va"
  },
  {
    "path": "Sources/SendKeysLib/RuntimeError.swift",
    "chars": 199,
    "preview": "struct RuntimeError: Error {\n    let message: String\n\n    init(_ message: String) {\n        self.message = message\n    }"
  },
  {
    "path": "Sources/SendKeysLib/SendKeysCli.swift",
    "chars": 578,
    "preview": "import ArgumentParser\nimport Foundation\n\n@available(OSX 10.11, *)\npublic struct SendKeysCli: ParsableCommand {\n    publi"
  },
  {
    "path": "Sources/SendKeysLib/Sender.swift",
    "chars": 6093,
    "preview": "import ArgumentParser\nimport Cocoa\nimport Foundation\n\n@available(OSX 10.11, *)\npublic struct Sender: ParsableCommand {\n "
  },
  {
    "path": "Sources/SendKeysLib/Sleeper.swift",
    "chars": 133,
    "preview": "import Foundation\n\nstruct Sleeper {\n    static func sleep(seconds: Double) {\n        usleep(useconds_t(seconds * 1_000_0"
  },
  {
    "path": "Sources/SendKeysLib/TerminationListener.swift",
    "chars": 3254,
    "preview": "import Cocoa\nimport Foundation\n\nclass TerminationListener {\n    private var keycode: UInt16\n    private var modifiers: ["
  },
  {
    "path": "Sources/SendKeysLib/Transformer.swift",
    "chars": 3614,
    "preview": "import ArgumentParser\nimport Foundation\n\n@available(OSX 10.11, *)\nclass Transformer: ParsableCommand {\n    public static"
  },
  {
    "path": "Sources/SendKeysLib/Utilities.swift",
    "chars": 689,
    "preview": "import Foundation\n\nfunc isTty() -> Bool {\n    return isatty(FileHandle.standardInput.fileDescriptor) == 1\n}\n\nfunc getReg"
  },
  {
    "path": "Sources/sendkeys/main.swift",
    "chars": 155,
    "preview": "import Foundation\nimport SendKeysLib\n\nif #available(OSX 10.11, *) {\n    SendKeysCli.main()\n} else {\n    print(\"OS versio"
  },
  {
    "path": "Tests/LinuxMain.swift",
    "chars": 117,
    "preview": "import XCTest\nimport sendkeysTests\n\nvar tests = [XCTestCaseEntry]()\ntests += sendkeysTests.allTests()\nXCTMain(tests)\n"
  },
  {
    "path": "Tests/SendKeysTests/CommandIteratorTests.swift",
    "chars": 19291,
    "preview": "import XCTest\n\n@testable import SendKeysLib\n\nfinal class CommandIteratorTests: XCTestCase {\n    var commandFactory: Comm"
  },
  {
    "path": "Tests/SendKeysTests/CommandsProcessorTests.swift",
    "chars": 6235,
    "preview": "import XCTest\n\n@testable import SendKeysLib\n\nclass CommandExecutorSpy: CommandExecutorProtocol {\n    var commands: [Comm"
  },
  {
    "path": "Tests/SendKeysTests/KeyPresserTests.swift",
    "chars": 1440,
    "preview": "import XCTest\n\n@testable import SendKeysLib\n\nfinal class KeyPresserTests: XCTestCase {\n    func testEscapeKey() throws {"
  },
  {
    "path": "Tests/SendKeysTests/PathDataTests.swift",
    "chars": 3428,
    "preview": "import XCTest\n\n@testable import SendKeysLib\n\nfinal class PathDataTests: XCTestCase {\n    func testNormalizesPathData() t"
  },
  {
    "path": "Tests/SendKeysTests/PathParserTests.swift",
    "chars": 4019,
    "preview": "import XCTest\n\n@testable import SendKeysLib\n\nfinal class PathParserTests: XCTestCase {\n    func testParsesRelativeComman"
  },
  {
    "path": "Tests/SendKeysTests/TransformerTests.swift",
    "chars": 1278,
    "preview": "import XCTest\n\n@testable import SendKeysLib\n\nfinal class TransformerTests: XCTestCase {\n    func testShouldNoteTransform"
  },
  {
    "path": "Tests/SendKeysTests/XCTestManifests.swift",
    "chars": 177,
    "preview": "import XCTest\n\n#if !canImport(ObjectiveC)\n    public func allTests() -> [XCTestCaseEntry] {\n        return [\n           "
  },
  {
    "path": "Tests/SendKeysTests/sendkeysTests.swift",
    "chars": 1434,
    "preview": "import XCTest\n\nimport class Foundation.Bundle\n\nfinal class sendkeysTests: XCTestCase {\n    func testHelp() throws {\n    "
  },
  {
    "path": "Tests/keys.txt",
    "chars": 106,
    "preview": "0123456789<\\>\nabcdefghijklmnopqrstuvwxyz<\\>\nABCDEFGHIJKLMNOPQRSTUVWXYZ<\\>\n-,;.'[]/\\`=<\\>\n<c:c:control><\\>\n"
  },
  {
    "path": "examples/.sendkeysrc.yml",
    "chars": 828,
    "preview": "# All properties are optional\nsend:\n  activate: true\n  animationInterval: 0.01\n  delay: 0.1\n  initialDelay: 1\n  keyboard"
  },
  {
    "path": "examples/node.js",
    "chars": 338,
    "preview": "const http = require('http');\n\nconst hostname = '127.0.0.1';\nconst port = 3000;\n\nconst server = http.createServer((req, "
  },
  {
    "path": "scripts/bottle.sh",
    "chars": 1497,
    "preview": "#!/usr/bin/env bash\n\nset -e\n\ncwd=`pwd`\nscript_folder=`cd $(dirname $0) && pwd`\nversion=$1\nformula_template=$script_folde"
  },
  {
    "path": "scripts/code-coverage.sh",
    "chars": 970,
    "preview": "#!/usr/bin/env bash\n\nset -e\n\ncwd=`pwd`\nscript_folder=`cd $(dirname $0) && pwd`\nbuild_folder=$script_folder/../.build\nout"
  },
  {
    "path": "scripts/format.sh",
    "chars": 349,
    "preview": "#!/usr/bin/env bash\n\nset -e\n\ncwd=`pwd`\nscript_folder=`cd $(dirname $0) && pwd`\nfile=$1\n\nif [ -z \"$file\" ]\nthen\n  # all f"
  },
  {
    "path": "scripts/install-pre-commit.sh",
    "chars": 183,
    "preview": "#!/usr/bin/env bash\nset -e\n\nscript_folder=`cd $(dirname $0) && pwd`\nrepo_folder=`git rev-parse --show-toplevel`\n\ncp -f $"
  },
  {
    "path": "scripts/pre-commit.sh",
    "chars": 406,
    "preview": "#!/usr/bin/env bash\nset -e\n\n# get the repository root\nrepo_folder=`git rev-parse --show-toplevel`\n\n# use repository root"
  },
  {
    "path": "scripts/update-version.sh",
    "chars": 392,
    "preview": "#!/usr/bin/env bash\n\nset -e\n\ncwd=`pwd`\nscript_folder=`cd $(dirname $0) && pwd`\nversion=${1:-`cat $script_folder/../versi"
  },
  {
    "path": "scripts/verify-output.sh",
    "chars": 685,
    "preview": "#!/usr/bin/env bash\n\nset -e\n\ncwd=`pwd`\nscript_folder=`cd $(dirname $0) && pwd`\noutput_folder=\"$script_folder/../.output\""
  },
  {
    "path": "version.txt",
    "chars": 6,
    "preview": "4.3.1\n"
  }
]

About this extraction

This page contains the full source code of the socsieng/sendkeys GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 87 files (213.7 KB), approximately 57.1k tokens, and a symbol index with 2 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!