Merge branch 'espanso:dev' into wayland-info
This commit is contained in:
commit
f87a38b674
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
|
@ -1,4 +1,3 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: ['federico-terzi']
|
||||
custom: ['https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FHNLR5DRS267E&source=url']
|
||||
|
|
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -7,14 +7,6 @@ assignees: ''
|
|||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
Feature requests have been moved to discussions, please open it there :)
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
https://github.com/espanso/espanso/discussions/categories/feature-requests-and-ideas
|
4
.github/scripts/ubuntu/build_deb.sh
vendored
4
.github/scripts/ubuntu/build_deb.sh
vendored
|
@ -8,10 +8,10 @@ cargo install cargo-deb --version 1.34.0
|
|||
cd espanso
|
||||
|
||||
echo "Building X11 deb package"
|
||||
cargo deb -p espanso
|
||||
cargo deb -p espanso -- --no-default-features --features "modulo vendored-tls"
|
||||
|
||||
echo "Building Wayland deb package"
|
||||
cargo deb -p espanso --variant wayland -- --features wayland
|
||||
cargo deb -p espanso --variant wayland -- --no-default-features --features "modulo wayland vendored-tls"
|
||||
|
||||
cd ..
|
||||
cp espanso/target/debian/espanso_*.deb espanso-debian-x11-amd64.deb
|
||||
|
|
92
.github/workflows/ci.yml
vendored
92
.github/workflows/ci.yml
vendored
|
@ -3,7 +3,12 @@
|
|||
|
||||
name: CI
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- dev
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
@ -11,13 +16,18 @@ env:
|
|||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, macos-latest, ubuntu-latest]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
env:
|
||||
WX_WIDGETS_BUILD_OUT_DIR: "${{github.workspace}}/wx-widgets-build"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Check formatting
|
||||
run: |
|
||||
rustup component add rustfmt
|
||||
|
@ -27,26 +37,60 @@ jobs:
|
|||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libx11-dev libxtst-dev libxkbcommon-dev libdbus-1-dev libwxgtk3.0-gtk3-dev
|
||||
- name: Install rust-script and cargo-make
|
||||
run: |
|
||||
cargo install rust-script --version "0.7.0"
|
||||
cargo install cargo-make --version 0.34.0
|
||||
# wxWidgets builds (internal to espanso-modulo) are by far the largest bottleneck
|
||||
# in the current pipeline, so we cache the results for a faster compilation process
|
||||
- name: "Cache wxWidgets builds"
|
||||
if: ${{ runner.os != 'Linux' }}
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
${{env.WX_WIDGETS_BUILD_OUT_DIR}}
|
||||
key: ${{ github.job }}-${{ runner.os }}-${{ hashFiles('espanso-modulo/build.rs') }}-${{ hashFiles('espanso-modulo/vendor/*') }}
|
||||
- name: Build
|
||||
run: |
|
||||
cargo make build-binary
|
||||
- name: Check clippy
|
||||
run: |
|
||||
rustup component add clippy
|
||||
cargo clippy -- -D warnings
|
||||
env:
|
||||
MACOSX_DEPLOYMENT_TARGET: "10.13"
|
||||
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest, macos-latest, ubuntu-latest]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Check formatting
|
||||
run: |
|
||||
rustup component add rustfmt
|
||||
cargo fmt --all -- --check
|
||||
- name: Install Linux dependencies
|
||||
if: ${{ runner.os == 'Linux' }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libx11-dev libxtst-dev libxkbcommon-dev libdbus-1-dev libwxgtk3.0-gtk3-dev
|
||||
- name: Install rust-script and cargo-make
|
||||
run: |
|
||||
cargo install rust-script --version "0.7.0"
|
||||
cargo install --force cargo-make --version 0.34.0
|
||||
cargo install cargo-make --version 0.34.0
|
||||
- name: Run test suite
|
||||
run: cargo make test-binary
|
||||
- name: Build
|
||||
run: |
|
||||
cargo make build-binary
|
||||
|
||||
build-wayland:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Check formatting
|
||||
run: |
|
||||
rustup component add rustfmt
|
||||
|
@ -55,29 +99,57 @@ jobs:
|
|||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxkbcommon-dev libwxgtk3.0-gtk3-dev libdbus-1-dev
|
||||
- name: Install rust-script and cargo-make
|
||||
run: |
|
||||
cargo install rust-script --version "0.7.0"
|
||||
cargo install cargo-make --version 0.34.0
|
||||
- name: Build
|
||||
run: cargo make build-binary --env NO_X11=true
|
||||
- name: Check clippy
|
||||
run: |
|
||||
rustup component add clippy
|
||||
cargo clippy -p espanso --features wayland -- -D warnings
|
||||
|
||||
test-wayland:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Check formatting
|
||||
run: |
|
||||
rustup component add rustfmt
|
||||
cargo fmt --all -- --check
|
||||
- name: Install Linux dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libxkbcommon-dev libwxgtk3.0-gtk3-dev libdbus-1-dev
|
||||
- name: Install rust-script and cargo-make
|
||||
run: |
|
||||
cargo install rust-script --version "0.7.0"
|
||||
cargo install --force cargo-make --version 0.34.0
|
||||
cargo install cargo-make --version 0.34.0
|
||||
- name: Run test suite
|
||||
run: cargo make test-binary --env NO_X11=true
|
||||
- name: Build
|
||||
run: cargo make build-binary --env NO_X11=true
|
||||
|
||||
build-macos-arm:
|
||||
runs-on: macos-11
|
||||
env:
|
||||
WX_WIDGETS_BUILD_OUT_DIR: "${{github.workspace}}/wx-widgets-build"
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install target
|
||||
run: rustup update && rustup target add aarch64-apple-darwin
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Install rust-script and cargo-make
|
||||
run: |
|
||||
cargo install rust-script --version "0.7.0"
|
||||
cargo install --force cargo-make --version 0.34.0
|
||||
cargo install cargo-make --version 0.34.0
|
||||
- name: "Cache wxWidgets builds"
|
||||
if: ${{ runner.os != 'Linux' }}
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
${{env.WX_WIDGETS_BUILD_OUT_DIR}}
|
||||
key: ${{ github.job }}-${{ runner.os }}-${{ hashFiles('espanso-modulo/build.rs') }}-${{ hashFiles('espanso-modulo/vendor/*') }}
|
||||
- name: Build
|
||||
run: |
|
||||
cargo make build-macos-arm-binary
|
||||
|
@ -85,5 +157,3 @@ jobs:
|
|||
# uses: mxschmitt/action-tmate@v3
|
||||
# with:
|
||||
# limit-access-to-actor: true
|
||||
|
||||
# TODO: add clippy check
|
69
.github/workflows/release.yml
vendored
69
.github/workflows/release.yml
vendored
|
@ -24,7 +24,7 @@ jobs:
|
|||
- name: "Extract version"
|
||||
id: "version"
|
||||
run: |
|
||||
ESPANSO_VERSION=$(cat espanso/Cargo.toml | grep version | head -1 | awk -F '"' '{ print $2 }')
|
||||
ESPANSO_VERSION=$(grep '^version' espanso/Cargo.toml | awk -F '"' '{ print $2 }')
|
||||
echo version: $ESPANSO_VERSION
|
||||
echo "::set-output name=version::v$ESPANSO_VERSION"
|
||||
|
||||
|
@ -167,6 +167,37 @@ jobs:
|
|||
run: cargo make create-bundle --profile release
|
||||
env:
|
||||
MACOSX_DEPLOYMENT_TARGET: "10.13"
|
||||
- name: Codesign executable
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
|
||||
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
|
||||
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
|
||||
run: |
|
||||
echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12
|
||||
security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" buildespanso.keychain
|
||||
security default-keychain -s buildespanso.keychain
|
||||
security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" buildespanso.keychain
|
||||
security import certificate.p12 -k buildespanso.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" buildespanso.keychain
|
||||
/usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime target/mac/Espanso.app -v
|
||||
- name: "Notarize executable"
|
||||
env:
|
||||
PROD_MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
|
||||
PROD_MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
|
||||
PROD_MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }}
|
||||
run: |
|
||||
echo "Create keychain profile"
|
||||
xcrun notarytool store-credentials "espanso-notarytool-profile" --apple-id "$PROD_MACOS_NOTARIZATION_APPLE_ID" --team-id "$PROD_MACOS_NOTARIZATION_TEAM_ID" --password "$PROD_MACOS_NOTARIZATION_PWD"
|
||||
|
||||
echo "Creating temp notarization archive"
|
||||
ditto -c -k --keepParent "target/mac/Espanso.app" "notarization.zip"
|
||||
|
||||
echo "Notarize app"
|
||||
xcrun notarytool submit "notarization.zip" --keychain-profile "espanso-notarytool-profile" --wait
|
||||
|
||||
echo "Attach staple"
|
||||
xcrun stapler staple "target/mac/Espanso.app"
|
||||
- name: Create ZIP archive
|
||||
run: |
|
||||
ditto -c -k --sequesterRsrc --keepParent target/mac/Espanso.app Espanso-Mac-Intel.zip
|
||||
|
@ -204,17 +235,35 @@ jobs:
|
|||
run: cargo make create-bundle --profile release --env BUILD_ARCH=aarch64-apple-darwin
|
||||
- name: Codesign executable
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
|
||||
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.MACOS_CI_KEYCHAIN_PWD }}
|
||||
MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PWD: ${{ secrets.PROD_MACOS_CERTIFICATE_PWD }}
|
||||
MACOS_CERTIFICATE_NAME: ${{ secrets.PROD_MACOS_CERTIFICATE_NAME }}
|
||||
MACOS_CI_KEYCHAIN_PWD: ${{ secrets.PROD_MACOS_CI_KEYCHAIN_PWD }}
|
||||
run: |
|
||||
echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12
|
||||
security create-keychain -p $MACOS_CI_KEYCHAIN_PWD buildespanso.keychain
|
||||
security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" buildespanso.keychain
|
||||
security default-keychain -s buildespanso.keychain
|
||||
security unlock-keychain -p $MACOS_CI_KEYCHAIN_PWD buildespanso.keychain
|
||||
security import certificate.p12 -k buildespanso.keychain -P $MACOS_CERTIFICATE_PWD -T /usr/bin/codesign
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $MACOS_CI_KEYCHAIN_PWD buildespanso.keychain
|
||||
/usr/bin/codesign --force -s "Espanso CI Self-Signed" target/mac/Espanso.app -v
|
||||
security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" buildespanso.keychain
|
||||
security import certificate.p12 -k buildespanso.keychain -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$MACOS_CI_KEYCHAIN_PWD" buildespanso.keychain
|
||||
/usr/bin/codesign --force -s "$MACOS_CERTIFICATE_NAME" --options runtime target/mac/Espanso.app -v
|
||||
- name: "Notarize executable"
|
||||
env:
|
||||
PROD_MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_APPLE_ID }}
|
||||
PROD_MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.PROD_MACOS_NOTARIZATION_TEAM_ID }}
|
||||
PROD_MACOS_NOTARIZATION_PWD: ${{ secrets.PROD_MACOS_NOTARIZATION_PWD }}
|
||||
run: |
|
||||
echo "Create keychain profile"
|
||||
xcrun notarytool store-credentials "espanso-notarytool-profile" --apple-id "$PROD_MACOS_NOTARIZATION_APPLE_ID" --team-id "$PROD_MACOS_NOTARIZATION_TEAM_ID" --password "$PROD_MACOS_NOTARIZATION_PWD"
|
||||
|
||||
echo "Creating temp notarization archive"
|
||||
ditto -c -k --keepParent "target/mac/Espanso.app" "notarization.zip"
|
||||
|
||||
echo "Notarize app"
|
||||
xcrun notarytool submit "notarization.zip" --keychain-profile "espanso-notarytool-profile" --wait
|
||||
|
||||
echo "Attach staple"
|
||||
xcrun stapler staple "target/mac/Espanso.app"
|
||||
- name: Create ZIP archive
|
||||
run: |
|
||||
ditto -c -k --sequesterRsrc --keepParent target/mac/Espanso.app Espanso-Mac-M1.zip
|
||||
|
@ -253,4 +302,4 @@ jobs:
|
|||
run: |
|
||||
VERSION="${{ needs.extract-version.outputs.espanso_version }}" ./scripts/publish_homebrew_version.sh
|
||||
|
||||
echo "Cask formula has been published here: "
|
||||
echo "Cask formula has been published here: "
|
||||
|
|
200
Cargo.lock
generated
200
Cargo.lock
generated
|
@ -264,12 +264,6 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb58b6451e8c2a812ad979ed1d83378caa5e927eef2622017a45f251457c2c9d"
|
||||
|
||||
[[package]]
|
||||
name = "const_fn"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6"
|
||||
|
||||
[[package]]
|
||||
name = "const_format"
|
||||
version = "0.2.14"
|
||||
|
@ -332,9 +326,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "crossbeam"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd01a6eb3daaafa260f6fc94c3a6c36390abc2080e38e3e34ced87393fb77d80"
|
||||
checksum = "4ae5588f6b3c3cb05239e90bd110f257254aecd01e4635400391aeae07497845"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"crossbeam-channel",
|
||||
|
@ -356,9 +350,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
|
||||
checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"crossbeam-epoch",
|
||||
|
@ -367,12 +361,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.1"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d"
|
||||
checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if 1.0.0",
|
||||
"const_fn",
|
||||
"crossbeam-utils",
|
||||
"lazy_static",
|
||||
"memoffset",
|
||||
|
@ -381,9 +375,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.1"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f6cb3c7f5b8e51bc3ebb73a2327ad4abdbd119dc13223f14f961d2f38486756"
|
||||
checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"crossbeam-utils",
|
||||
|
@ -391,11 +385,10 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.1"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
|
||||
checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if 1.0.0",
|
||||
"lazy_static",
|
||||
]
|
||||
|
@ -589,7 +582,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "espanso"
|
||||
version = "2.1.5-beta"
|
||||
version = "2.1.7-beta"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"caps",
|
||||
|
@ -631,6 +624,7 @@ dependencies = [
|
|||
"serde_json",
|
||||
"serde_yaml",
|
||||
"simplelog",
|
||||
"sysinfo",
|
||||
"tempdir",
|
||||
"thiserror",
|
||||
"widestring",
|
||||
|
@ -1048,6 +1042,19 @@ version = "0.3.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"proc-macro-hack",
|
||||
"proc-macro2",
|
||||
"quote 1.0.9",
|
||||
"syn 1.0.67",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.17"
|
||||
|
@ -1069,10 +1076,13 @@ dependencies = [
|
|||
"autocfg",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"proc-macro-hack",
|
||||
"proc-macro-nested",
|
||||
"slab",
|
||||
]
|
||||
|
||||
|
@ -1252,6 +1262,21 @@ dependencies = [
|
|||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"hyper",
|
||||
"log",
|
||||
"rustls",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
|
@ -1402,9 +1427,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.101"
|
||||
version = "0.2.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21"
|
||||
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
||||
|
||||
[[package]]
|
||||
name = "libdbus-sys"
|
||||
|
@ -2049,6 +2074,12 @@ version = "0.5.19"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-nested"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.24"
|
||||
|
@ -2198,6 +2229,30 @@ dependencies = [
|
|||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"crossbeam-deque",
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rdrand"
|
||||
version = "0.4.0"
|
||||
|
@ -2235,9 +2290,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.4.4"
|
||||
version = "1.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54fd1046a3107eb58f42de31d656fee6853e5d276c455fd943742dce89fc3dd3"
|
||||
checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
@ -2279,6 +2334,7 @@ dependencies = [
|
|||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"hyper-tls",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
|
@ -2288,17 +2344,35 @@ dependencies = [
|
|||
"native-tls",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls",
|
||||
"serde",
|
||||
"serde_urlencoded",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-rustls",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"webpki-roots",
|
||||
"winreg 0.7.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"spin",
|
||||
"untrusted",
|
||||
"web-sys",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-argon2"
|
||||
version = "0.8.3"
|
||||
|
@ -2311,6 +2385,19 @@ dependencies = [
|
|||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"log",
|
||||
"ring",
|
||||
"sct",
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
|
@ -2348,6 +2435,16 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "sct"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.3.1"
|
||||
|
@ -2496,6 +2593,12 @@ dependencies = [
|
|||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "squote"
|
||||
version = "0.1.2"
|
||||
|
@ -2614,6 +2717,21 @@ dependencies = [
|
|||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sysinfo"
|
||||
version = "0.24.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d80929a3b477bce3a64360ca82bfb361eacce1dcb7b1fb31e8e5e181e37c212"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"ntapi",
|
||||
"once_cell",
|
||||
"rayon",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempdir"
|
||||
version = "0.3.7"
|
||||
|
@ -2762,6 +2880,17 @@ dependencies = [
|
|||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"tokio",
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.6.7"
|
||||
|
@ -2883,6 +3012,12 @@ version = "0.1.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f14ee04d9415b52b3aeab06258a3f07093182b88ba0f9b8d203f211a7a7d41c7"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.2.2"
|
||||
|
@ -3112,6 +3247,25 @@ dependencies = [
|
|||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki"
|
||||
version = "0.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940"
|
||||
dependencies = [
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "widestring"
|
||||
version = "0.4.3"
|
||||
|
|
6
LICENSE
6
LICENSE
|
@ -631,8 +631,8 @@ to attach them to the start of each source file to most effectively
|
|||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
Espanso
|
||||
Copyright (C) 2019-2022 Federico Terzi
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
|
|||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
Espanso Copyright (C) 2019-2022 Federico Terzi
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
|
|
@ -34,7 +34,7 @@ ___
|
|||
* **Custom scripts** support
|
||||
* **Shell commands** support
|
||||
* **App-specific** configurations
|
||||
* Support [Forms](https://espanso.org/docs/forms/)
|
||||
* Support [Forms](https://espanso.org/docs/matches/forms/)
|
||||
* Expandable with **packages**
|
||||
* Built-in **package manager** for [espanso hub](https://hub.espanso.org/)
|
||||
* File based configuration
|
||||
|
|
|
@ -162,10 +162,22 @@ pub trait Config: Send + Sync {
|
|||
// not be targeted to the right application.
|
||||
fn post_search_delay(&self) -> usize;
|
||||
|
||||
// If enabled, Espanso emulates the Alt Code feature available on Windows
|
||||
// (keeping ALT pressed and then typing a char code with the numpad).
|
||||
// This feature is necessary on Windows because the mechanism used by Espanso
|
||||
// to intercept keystrokes disables the Windows' native Alt code functionality
|
||||
// as a side effect.
|
||||
// Because many users relied on this feature, we try to bring it back by emulating it.
|
||||
fn emulate_alt_codes(&self) -> bool;
|
||||
|
||||
// If true, use the `xclip` command to implement the clipboard instead of
|
||||
// the built-in native module on X11.
|
||||
fn x11_use_xclip_backend(&self) -> bool;
|
||||
|
||||
// If true, use an alternative injection backend based on the `xdotool` library.
|
||||
// This might improve the situation for certain locales/layouts on X11.
|
||||
fn x11_use_xdotool_backend(&self) -> bool;
|
||||
|
||||
// If true, filter out keyboard events without an explicit HID device source on Windows.
|
||||
// This is needed to filter out the software-generated events, including
|
||||
// those from espanso, but might need to be disabled when using some software-level keyboards.
|
||||
|
@ -212,6 +224,7 @@ pub trait Config: Send + Sync {
|
|||
secure_input_notification: {:?}
|
||||
|
||||
x11_use_xclip_backend: {:?}
|
||||
x11_use_xdotool_backend: {:?}
|
||||
win32_exclude_orphan_events: {:?}
|
||||
win32_keyboard_layout_cache_interval: {:?}
|
||||
|
||||
|
@ -246,6 +259,7 @@ pub trait Config: Send + Sync {
|
|||
self.secure_input_notification(),
|
||||
|
||||
self.x11_use_xclip_backend(),
|
||||
self.x11_use_xdotool_backend(),
|
||||
self.win32_exclude_orphan_events(),
|
||||
self.win32_keyboard_layout_cache_interval(),
|
||||
|
||||
|
|
|
@ -46,9 +46,11 @@ pub(crate) struct ParsedConfig {
|
|||
pub secure_input_notification: Option<bool>,
|
||||
pub post_form_delay: Option<usize>,
|
||||
pub post_search_delay: Option<usize>,
|
||||
pub emulate_alt_codes: Option<bool>,
|
||||
pub win32_exclude_orphan_events: Option<bool>,
|
||||
pub win32_keyboard_layout_cache_interval: Option<i64>,
|
||||
pub x11_use_xclip_backend: Option<bool>,
|
||||
pub x11_use_xdotool_backend: Option<bool>,
|
||||
|
||||
pub pre_paste_delay: Option<usize>,
|
||||
pub restore_clipboard_delay: Option<usize>,
|
||||
|
|
|
@ -112,6 +112,9 @@ pub(crate) struct YAMLConfig {
|
|||
#[serde(default)]
|
||||
pub secure_input_notification: Option<bool>,
|
||||
|
||||
#[serde(default)]
|
||||
pub emulate_alt_codes: Option<bool>,
|
||||
|
||||
#[serde(default)]
|
||||
pub win32_exclude_orphan_events: Option<bool>,
|
||||
|
||||
|
@ -121,6 +124,9 @@ pub(crate) struct YAMLConfig {
|
|||
#[serde(default)]
|
||||
pub x11_use_xclip_backend: Option<bool>,
|
||||
|
||||
#[serde(default)]
|
||||
pub x11_use_xdotool_backend: Option<bool>,
|
||||
|
||||
// Include/Exclude
|
||||
#[serde(default)]
|
||||
pub includes: Option<Vec<String>>,
|
||||
|
@ -210,9 +216,12 @@ impl TryFrom<YAMLConfig> for ParsedConfig {
|
|||
post_form_delay: yaml_config.post_form_delay,
|
||||
post_search_delay: yaml_config.post_search_delay,
|
||||
|
||||
emulate_alt_codes: yaml_config.emulate_alt_codes,
|
||||
|
||||
win32_exclude_orphan_events: yaml_config.win32_exclude_orphan_events,
|
||||
win32_keyboard_layout_cache_interval: yaml_config.win32_keyboard_layout_cache_interval,
|
||||
x11_use_xclip_backend: yaml_config.x11_use_xclip_backend,
|
||||
x11_use_xdotool_backend: yaml_config.x11_use_xdotool_backend,
|
||||
|
||||
use_standard_includes: yaml_config.use_standard_includes,
|
||||
includes: yaml_config.includes,
|
||||
|
@ -270,9 +279,11 @@ mod tests {
|
|||
secure_input_notification: false
|
||||
post_form_delay: 300
|
||||
post_search_delay: 400
|
||||
emulate_alt_codes: true
|
||||
win32_exclude_orphan_events: false
|
||||
win32_keyboard_layout_cache_interval: 300
|
||||
x11_use_xclip_backend: true
|
||||
x11_use_xdotool_backend: true
|
||||
|
||||
use_standard_includes: true
|
||||
includes: ["test1"]
|
||||
|
@ -324,11 +335,13 @@ mod tests {
|
|||
show_icon: Some(false),
|
||||
show_notifications: Some(false),
|
||||
secure_input_notification: Some(false),
|
||||
emulate_alt_codes: Some(true),
|
||||
post_form_delay: Some(300),
|
||||
post_search_delay: Some(400),
|
||||
win32_exclude_orphan_events: Some(false),
|
||||
win32_keyboard_layout_cache_interval: Some(300),
|
||||
x11_use_xclip_backend: Some(true),
|
||||
x11_use_xdotool_backend: Some(true),
|
||||
|
||||
pre_paste_delay: Some(300),
|
||||
evdev_modifier_delay: Some(40),
|
||||
|
|
|
@ -299,6 +299,10 @@ impl Config for ResolvedConfig {
|
|||
self.parsed.secure_input_notification.unwrap_or(true)
|
||||
}
|
||||
|
||||
fn emulate_alt_codes(&self) -> bool {
|
||||
self.parsed.emulate_alt_codes.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn post_form_delay(&self) -> usize {
|
||||
self
|
||||
.parsed
|
||||
|
@ -331,6 +335,10 @@ impl Config for ResolvedConfig {
|
|||
fn x11_use_xclip_backend(&self) -> bool {
|
||||
self.parsed.x11_use_xclip_backend.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn x11_use_xdotool_backend(&self) -> bool {
|
||||
self.parsed.x11_use_xdotool_backend.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl ResolvedConfig {
|
||||
|
@ -412,11 +420,13 @@ impl ResolvedConfig {
|
|||
show_icon,
|
||||
show_notifications,
|
||||
secure_input_notification,
|
||||
emulate_alt_codes,
|
||||
post_form_delay,
|
||||
post_search_delay,
|
||||
win32_exclude_orphan_events,
|
||||
win32_keyboard_layout_cache_interval,
|
||||
x11_use_xclip_backend,
|
||||
x11_use_xdotool_backend,
|
||||
includes,
|
||||
excludes,
|
||||
extra_includes,
|
||||
|
|
|
@ -64,7 +64,7 @@ impl ErrorRecord {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ErrorLevel {
|
||||
Error,
|
||||
Warning,
|
||||
|
|
|
@ -386,7 +386,7 @@ impl LegacyConfig {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq)]
|
||||
pub enum BackendType {
|
||||
Inject,
|
||||
Clipboard,
|
||||
|
@ -740,7 +740,7 @@ impl LegacyConfigSet {
|
|||
}
|
||||
|
||||
// Error handling
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
pub enum ConfigLoadError {
|
||||
FileNotFound,
|
||||
|
|
|
@ -395,6 +395,10 @@ impl Config for LegacyInteropConfig {
|
|||
crate::config::default::DEFAULT_POST_SEARCH_DELAY
|
||||
}
|
||||
|
||||
fn emulate_alt_codes(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn win32_exclude_orphan_events(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
@ -410,6 +414,10 @@ impl Config for LegacyInteropConfig {
|
|||
fn x11_use_xclip_backend(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn x11_use_xdotool_backend(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
struct LegacyMatchGroup {
|
||||
|
|
|
@ -21,7 +21,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
#[allow(non_camel_case_types)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq)]
|
||||
pub enum KeyModifier {
|
||||
CTRL,
|
||||
SHIFT,
|
||||
|
|
|
@ -119,7 +119,7 @@ pub struct YAMLMatch {
|
|||
pub search_terms: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
pub struct YAMLVariable {
|
||||
pub name: String,
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ pub trait MatchStore: Send {
|
|||
fn loaded_paths(&self) -> Vec<String>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MatchSet<'a> {
|
||||
pub matches: Vec<&'a Match>,
|
||||
pub global_vars: Vec<&'a Variable>,
|
||||
|
|
|
@ -379,6 +379,18 @@ fn key_sym_to_key(key_sym: i32) -> (Key, Option<Variant>) {
|
|||
0xFFD0 => (F19, None),
|
||||
0xFFD1 => (F20, None),
|
||||
|
||||
// Numpad
|
||||
0xFFB0 => (Numpad0, None),
|
||||
0xFFB1 => (Numpad1, None),
|
||||
0xFFB2 => (Numpad2, None),
|
||||
0xFFB3 => (Numpad3, None),
|
||||
0xFFB4 => (Numpad4, None),
|
||||
0xFFB5 => (Numpad5, None),
|
||||
0xFFB6 => (Numpad6, None),
|
||||
0xFFB7 => (Numpad7, None),
|
||||
0xFFB8 => (Numpad8, None),
|
||||
0xFFB9 => (Numpad9, None),
|
||||
|
||||
// Other keys, includes the raw code provided by the operating system
|
||||
_ => (Other(key_sym), None),
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
#[cfg(test)]
|
||||
use enum_as_inner::EnumAsInner;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(test, derive(EnumAsInner))]
|
||||
pub enum InputEvent {
|
||||
Mouse(MouseEvent),
|
||||
|
@ -32,7 +32,7 @@ pub enum InputEvent {
|
|||
AllModifiersReleased,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum MouseButton {
|
||||
Left,
|
||||
Right,
|
||||
|
@ -44,25 +44,25 @@ pub enum MouseButton {
|
|||
Button5,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct MouseEvent {
|
||||
pub button: MouseButton,
|
||||
pub status: Status,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Status {
|
||||
Pressed,
|
||||
Released,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Variant {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct KeyboardEvent {
|
||||
pub key: Key,
|
||||
pub value: Option<String>,
|
||||
|
@ -72,7 +72,7 @@ pub struct KeyboardEvent {
|
|||
}
|
||||
|
||||
// A subset of the Web's key values: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Key {
|
||||
// Modifiers
|
||||
Alt,
|
||||
|
@ -125,11 +125,23 @@ pub enum Key {
|
|||
F19,
|
||||
F20,
|
||||
|
||||
// Numpad keys
|
||||
Numpad0,
|
||||
Numpad1,
|
||||
Numpad2,
|
||||
Numpad3,
|
||||
Numpad4,
|
||||
Numpad5,
|
||||
Numpad6,
|
||||
Numpad7,
|
||||
Numpad8,
|
||||
Numpad9,
|
||||
|
||||
// Other keys, includes the raw code provided by the operating system
|
||||
Other(i32),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct HotKeyEvent {
|
||||
pub hotkey_id: i32,
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ lazy_static! {
|
|||
static ref RAW_PARSER: Regex = Regex::new(r"^RAW\((\d+)\)$").unwrap();
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[derive(Debug, PartialEq, Clone, Eq)]
|
||||
pub enum ShortcutKey {
|
||||
Alt,
|
||||
Control,
|
||||
|
|
|
@ -32,7 +32,7 @@ static MODIFIERS: &[ShortcutKey; 4] = &[
|
|||
ShortcutKey::Meta,
|
||||
];
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[derive(Debug, PartialEq, Clone, Eq)]
|
||||
pub struct HotKey {
|
||||
pub id: i32,
|
||||
pub key: ShortcutKey,
|
||||
|
|
|
@ -448,6 +448,18 @@ fn key_code_to_key(key_code: i32) -> (Key, Option<Variant>) {
|
|||
0x50 => (F19, None),
|
||||
0x5A => (F20, None),
|
||||
|
||||
// Numpad
|
||||
0x52 => (Numpad0, None),
|
||||
0x53 => (Numpad1, None),
|
||||
0x54 => (Numpad2, None),
|
||||
0x55 => (Numpad3, None),
|
||||
0x56 => (Numpad4, None),
|
||||
0x57 => (Numpad5, None),
|
||||
0x58 => (Numpad6, None),
|
||||
0x59 => (Numpad7, None),
|
||||
0x5B => (Numpad8, None),
|
||||
0x5C => (Numpad9, None),
|
||||
|
||||
// Other keys, includes the raw code provided by the operating system
|
||||
_ => (Other(key_code), None),
|
||||
}
|
||||
|
|
|
@ -395,6 +395,18 @@ fn key_code_to_key(key_code: i32) -> (Key, Option<Variant>) {
|
|||
0x82 => (F19, None),
|
||||
0x83 => (F20, None),
|
||||
|
||||
// Numpad
|
||||
0x60 => (Numpad0, None),
|
||||
0x61 => (Numpad1, None),
|
||||
0x62 => (Numpad2, None),
|
||||
0x63 => (Numpad3, None),
|
||||
0x64 => (Numpad4, None),
|
||||
0x65 => (Numpad5, None),
|
||||
0x66 => (Numpad6, None),
|
||||
0x67 => (Numpad7, None),
|
||||
0x68 => (Numpad8, None),
|
||||
0x69 => (Numpad9, None),
|
||||
|
||||
// Other keys, includes the raw code provided by the operating system
|
||||
_ => (Other(key_code), None),
|
||||
}
|
||||
|
|
|
@ -396,6 +396,18 @@ fn key_sym_to_key(key_sym: i32) -> (Key, Option<Variant>) {
|
|||
0xFFD0 => (F19, None),
|
||||
0xFFD1 => (F20, None),
|
||||
|
||||
// Numpad
|
||||
0xFFB0 => (Numpad0, None),
|
||||
0xFFB1 => (Numpad1, None),
|
||||
0xFFB2 => (Numpad2, None),
|
||||
0xFFB3 => (Numpad3, None),
|
||||
0xFFB4 => (Numpad4, None),
|
||||
0xFFB5 => (Numpad5, None),
|
||||
0xFFB6 => (Numpad6, None),
|
||||
0xFFB7 => (Numpad7, None),
|
||||
0xFFB8 => (Numpad8, None),
|
||||
0xFFB9 => (Numpad9, None),
|
||||
|
||||
// Other keys, includes the raw code provided by the operating system
|
||||
_ => (Other(key_sym), None),
|
||||
}
|
||||
|
|
|
@ -19,13 +19,13 @@
|
|||
|
||||
use super::input::Key;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TriggerCompensationEvent {
|
||||
pub trigger: String,
|
||||
pub left_separator: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CursorHintCompensationEvent {
|
||||
pub cursor_hint_back_count: usize,
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ pub struct HtmlInjectRequest {
|
|||
pub html: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[derive(Debug, PartialEq, Clone, Eq)]
|
||||
pub enum TextInjectMode {
|
||||
Keys,
|
||||
Clipboard,
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MatchExecRequestEvent {
|
||||
pub trigger: Option<String>,
|
||||
pub args: HashMap<String, String>,
|
||||
|
|
|
@ -17,19 +17,19 @@
|
|||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[derive(Debug, PartialEq, Clone, Eq)]
|
||||
pub enum Status {
|
||||
Pressed,
|
||||
Released,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[derive(Debug, PartialEq, Clone, Eq)]
|
||||
pub enum Variant {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[derive(Debug, PartialEq, Clone, Eq)]
|
||||
pub struct KeyboardEvent {
|
||||
pub key: Key,
|
||||
pub value: Option<String>,
|
||||
|
@ -37,7 +37,7 @@ pub struct KeyboardEvent {
|
|||
pub variant: Option<Variant>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum MouseButton {
|
||||
Left,
|
||||
Right,
|
||||
|
@ -49,13 +49,13 @@ pub enum MouseButton {
|
|||
Button5,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MouseEvent {
|
||||
pub button: MouseButton,
|
||||
pub status: Status,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Key {
|
||||
// Modifiers
|
||||
Alt,
|
||||
|
@ -108,16 +108,28 @@ pub enum Key {
|
|||
F19,
|
||||
F20,
|
||||
|
||||
// Numpad keys
|
||||
Numpad0,
|
||||
Numpad1,
|
||||
Numpad2,
|
||||
Numpad3,
|
||||
Numpad4,
|
||||
Numpad5,
|
||||
Numpad6,
|
||||
Numpad7,
|
||||
Numpad8,
|
||||
Numpad9,
|
||||
|
||||
// Other keys, includes the raw code provided by the operating system
|
||||
Other(i32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ContextMenuClickedEvent {
|
||||
pub context_item_id: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct HotKeyEvent {
|
||||
pub hotkey_id: i32,
|
||||
}
|
||||
|
|
|
@ -19,13 +19,13 @@
|
|||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MatchesDetectedEvent {
|
||||
pub matches: Vec<DetectedMatch>,
|
||||
pub is_search: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
#[derive(Debug, Clone, PartialEq, Default, Eq)]
|
||||
pub struct DetectedMatch {
|
||||
pub id: i32,
|
||||
pub trigger: Option<String>,
|
||||
|
@ -34,17 +34,17 @@ pub struct DetectedMatch {
|
|||
pub args: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MatchSelectedEvent {
|
||||
pub chosen: DetectedMatch,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CauseCompensatedMatchEvent {
|
||||
pub m: DetectedMatch,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct RenderingRequestedEvent {
|
||||
pub match_id: i32,
|
||||
pub trigger: Option<String>,
|
||||
|
@ -54,38 +54,38 @@ pub struct RenderingRequestedEvent {
|
|||
pub format: TextFormat,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum TextFormat {
|
||||
Plain,
|
||||
Markdown,
|
||||
Html,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ImageRequestedEvent {
|
||||
pub match_id: i32,
|
||||
pub image_path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ImageResolvedEvent {
|
||||
pub image_path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct RenderedEvent {
|
||||
pub match_id: i32,
|
||||
pub body: String,
|
||||
pub format: TextFormat,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DiscardPreviousEvent {
|
||||
// All Events with a source_id smaller than this one will be discarded
|
||||
pub minimum_source_id: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DiscardBetweenEvent {
|
||||
// All Events with a source_id between start_id (included) and end_id (excluded)
|
||||
// will be discarded
|
||||
|
@ -93,13 +93,13 @@ pub struct DiscardBetweenEvent {
|
|||
pub end_id: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SecureInputEnabledEvent {
|
||||
pub app_name: String,
|
||||
pub app_path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UndoEvent {
|
||||
pub match_id: i32,
|
||||
pub trigger: String,
|
||||
|
|
|
@ -29,7 +29,7 @@ pub enum MenuItem {
|
|||
Separator,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SimpleMenuItem {
|
||||
pub id: u32,
|
||||
pub label: String,
|
||||
|
@ -41,19 +41,19 @@ pub struct SubMenuItem {
|
|||
pub items: Vec<MenuItem>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct IconStatusChangeEvent {
|
||||
pub status: IconStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum IconStatus {
|
||||
Enabled,
|
||||
Disabled,
|
||||
SecureInputDisabled,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ShowTextEvent {
|
||||
pub title: String,
|
||||
pub text: String,
|
||||
|
|
|
@ -22,6 +22,7 @@ use log::trace;
|
|||
use super::{
|
||||
middleware::{
|
||||
action::{ActionMiddleware, EventSequenceProvider},
|
||||
alt_code_synthesizer::AltCodeSynthesizerMiddleware,
|
||||
cause::CauseCompensateMiddleware,
|
||||
cursor_hint::CursorHintMiddleware,
|
||||
delay_modifiers::{DelayForModifierReleaseMiddleware, ModifierStatusProvider},
|
||||
|
@ -32,10 +33,10 @@ use super::{
|
|||
multiplex::MultiplexMiddleware,
|
||||
render::RenderMiddleware,
|
||||
},
|
||||
DisableOptions, EnabledStatusProvider, MatchFilter, MatchInfoProvider, MatchProvider,
|
||||
MatchResolver, MatchSelector, Matcher, MatcherMiddlewareConfigProvider, Middleware,
|
||||
ModifierStateProvider, Multiplexer, NotificationManager, PathProvider, Processor, Renderer,
|
||||
UndoEnabledProvider,
|
||||
AltCodeSynthEnabledProvider, DisableOptions, EnabledStatusProvider, MatchFilter,
|
||||
MatchInfoProvider, MatchProvider, MatchResolver, MatchSelector, Matcher,
|
||||
MatcherMiddlewareConfigProvider, Middleware, ModifierStateProvider, Multiplexer,
|
||||
NotificationManager, PathProvider, Processor, Renderer, UndoEnabledProvider,
|
||||
};
|
||||
use crate::{
|
||||
event::{Event, EventType},
|
||||
|
@ -74,6 +75,7 @@ impl<'a> DefaultProcessor<'a> {
|
|||
modifier_state_provider: &'a dyn ModifierStateProvider,
|
||||
match_resolver: &'a dyn MatchResolver,
|
||||
notification_manager: &'a dyn NotificationManager,
|
||||
alt_code_synth_enabled_provider: &'a dyn AltCodeSynthEnabledProvider,
|
||||
) -> DefaultProcessor<'a> {
|
||||
Self {
|
||||
event_queue: VecDeque::new(),
|
||||
|
@ -81,6 +83,9 @@ impl<'a> DefaultProcessor<'a> {
|
|||
Box::new(EventsDiscardMiddleware::new()),
|
||||
Box::new(DisableMiddleware::new(disable_options)),
|
||||
Box::new(IconStatusMiddleware::new()),
|
||||
Box::new(AltCodeSynthesizerMiddleware::new(
|
||||
alt_code_synth_enabled_provider,
|
||||
)),
|
||||
Box::new(MatcherMiddleware::new(
|
||||
matchers,
|
||||
matcher_options_provider,
|
||||
|
|
675
espanso-engine/src/process/middleware/alt_code_synthesizer.rs
Normal file
675
espanso-engine/src/process/middleware/alt_code_synthesizer.rs
Normal file
|
@ -0,0 +1,675 @@
|
|||
/*
|
||||
* This file is part of espanso.
|
||||
*
|
||||
* Copyright (C) 2019-2022 Federico Terzi
|
||||
*
|
||||
* espanso is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* espanso is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
use log::debug;
|
||||
|
||||
use super::super::Middleware;
|
||||
use crate::event::{
|
||||
effect::TextInjectRequest,
|
||||
input::{Key, Status},
|
||||
Event, EventType,
|
||||
};
|
||||
|
||||
pub trait AltCodeSynthEnabledProvider {
|
||||
fn is_alt_code_synthesizer_enabled(&self) -> bool;
|
||||
}
|
||||
|
||||
pub struct AltCodeSynthesizerMiddleware<'a> {
|
||||
code_buffer: RefCell<Option<String>>,
|
||||
enabled_state_provider: &'a dyn AltCodeSynthEnabledProvider,
|
||||
}
|
||||
|
||||
impl<'a> AltCodeSynthesizerMiddleware<'a> {
|
||||
pub fn new(enabled_state_provider: &'a dyn AltCodeSynthEnabledProvider) -> Self {
|
||||
Self {
|
||||
code_buffer: RefCell::new(None),
|
||||
enabled_state_provider,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Middleware for AltCodeSynthesizerMiddleware<'a> {
|
||||
fn name(&self) -> &'static str {
|
||||
"alt_code_synthesizer"
|
||||
}
|
||||
|
||||
fn next(&self, event: Event, dispatch: &mut dyn FnMut(Event)) -> Event {
|
||||
match &event.etype {
|
||||
EventType::Keyboard(keyboard_event)
|
||||
if self
|
||||
.enabled_state_provider
|
||||
.is_alt_code_synthesizer_enabled() =>
|
||||
{
|
||||
let mut code_buffer = self.code_buffer.borrow_mut();
|
||||
|
||||
if keyboard_event.status == Status::Pressed {
|
||||
if let Key::Alt = &keyboard_event.key {
|
||||
*code_buffer = Some("".to_owned());
|
||||
} else if let Some(buffer) = &mut *code_buffer {
|
||||
match keyboard_event.key {
|
||||
Key::Numpad0 => buffer.push('0'),
|
||||
Key::Numpad1 => buffer.push('1'),
|
||||
Key::Numpad2 => buffer.push('2'),
|
||||
Key::Numpad3 => buffer.push('3'),
|
||||
Key::Numpad4 => buffer.push('4'),
|
||||
Key::Numpad5 => buffer.push('5'),
|
||||
Key::Numpad6 => buffer.push('6'),
|
||||
Key::Numpad7 => buffer.push('7'),
|
||||
Key::Numpad8 => buffer.push('8'),
|
||||
Key::Numpad9 => buffer.push('9'),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
} else if keyboard_event.key == Key::Alt {
|
||||
if let Some(codes) = &*code_buffer {
|
||||
if let Some(target_char) = convert_buffer_into_char(codes) {
|
||||
dispatch(Event::caused_by(
|
||||
event.source_id,
|
||||
EventType::TextInject(TextInjectRequest {
|
||||
text: target_char,
|
||||
..Default::default()
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
*code_buffer = None;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
event
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_buffer_into_char(buffer: &str) -> Option<String> {
|
||||
if buffer.is_empty() {
|
||||
debug!("unable to generate ALT code as the buffer is empty");
|
||||
return None;
|
||||
}
|
||||
|
||||
if !buffer.chars().all(char::is_numeric) {
|
||||
debug!("unable to generate ALT code as some of the buffer chars are not numeric");
|
||||
return None;
|
||||
}
|
||||
|
||||
// According to: https://en.wikipedia.org/wiki/Alt_code
|
||||
// The conversion works as follow:
|
||||
// * If the number is smaller than 256 and does not start with 0, then we use a patched version of the CP437 encoding
|
||||
// * If the number is smaller than 256 and starts with 0 we use the CP1252 encoding
|
||||
// * If the number is greater or equal than 256 we use the unicode encoding
|
||||
|
||||
let code = buffer.parse::<u32>().ok()?;
|
||||
let unicode_code = if code >= 256 {
|
||||
// Unicode
|
||||
code
|
||||
} else if buffer.starts_with('0') {
|
||||
// CP1252
|
||||
convert_cp1252_code_to_unicode(code)?
|
||||
} else {
|
||||
// CP437
|
||||
convert_cp437_code_to_unicode(code)?
|
||||
};
|
||||
|
||||
char::from_u32(unicode_code).map(|c| c.to_string())
|
||||
}
|
||||
|
||||
// Taken from: https://altcodeunicode.com/
|
||||
fn convert_cp437_code_to_unicode(code: u32) -> Option<u32> {
|
||||
match code {
|
||||
0 => Some(0x0000), // Control character - null (NUL)
|
||||
1 => Some(0x263A), // White smiling face, smiley face
|
||||
2 => Some(0x263B), // Black smiling face
|
||||
3 => Some(0x2665), // Black heart suit
|
||||
4 => Some(0x2666), // Black diamond suit
|
||||
5 => Some(0x2663), // Black club suit
|
||||
6 => Some(0x2660), // Black spade suit
|
||||
7 => Some(0x2022), // Bullet
|
||||
8 => Some(0x25D8), // Inverse bullet
|
||||
9 => Some(0x25CB), // White circle
|
||||
10 => Some(0x25D9), // Inverse white circle
|
||||
11 => Some(0x2642), // Male sign, mars, alchemical symbol for iron
|
||||
12 => Some(0x2640), // Female sign, venus, alchemical symbol for copper
|
||||
13 => Some(0x266A), // Eighth note, quaver
|
||||
14 => Some(0x266B), // Beamed eighth notes, barred eighth notes, beamed quavers
|
||||
15 => Some(0x263C), // White sun with rays
|
||||
16 => Some(0x25BA), // Black right-pointing pointer
|
||||
17 => Some(0x25C4), // Black left-pointing pointer
|
||||
18 => Some(0x2195), // Up down arrow
|
||||
19 => Some(0x203C), // Double exclamation mark
|
||||
20 => Some(0x00B6), // Pilcrow sign, paragraph sign
|
||||
21 => Some(0x00A7), // Section sign
|
||||
22 => Some(0x25AC), // Black rectangle
|
||||
23 => Some(0x21A8), // Up down arrow with base
|
||||
24 => Some(0x2191), // Upwards arrow
|
||||
25 => Some(0x2193), // Downwards arrow
|
||||
26 => Some(0x2192), // Rightwards arrow, Z notation total function
|
||||
27 => Some(0x2190), // Leftwards arrow
|
||||
28 => Some(0x221F), // Right angle
|
||||
29 => Some(0x2194), // Left right arrow, Z notation relation
|
||||
30 => Some(0x25B2), // Black up-pointing triangle
|
||||
31 => Some(0x25BC), // Black down-pointing triangle
|
||||
32 => Some(0x0020), // Space
|
||||
33 => Some(0x0021), // Exclamation mark, factorial
|
||||
34 => Some(0x0022), // Quotation mark
|
||||
35 => Some(0x0023), // Number sign, pound sign, hash, crosshatch, octothorpe
|
||||
36 => Some(0x0024), // Dollar sign, milréis, escudo
|
||||
37 => Some(0x0025), // Percent sign
|
||||
38 => Some(0x0026), // Ampersand
|
||||
39 => Some(0x0027), // Apostrophe
|
||||
40 => Some(0x0028), // Left parenthesis, opening parenthesis
|
||||
41 => Some(0x0029), // Right parenthesis, closing parenthesis
|
||||
42 => Some(0x002A), // Asterisk, star
|
||||
43 => Some(0x002B), // Plus sign
|
||||
44 => Some(0x002C), // Comma, decimal separator
|
||||
45 => Some(0x002D), // Hyphen, minus sign
|
||||
46 => Some(0x002E), // Full stop, period, dot, decimal point
|
||||
47 => Some(0x002F), // Solidus, slash, forward slash, virgule
|
||||
48 => Some(0x0030), // Digit zero
|
||||
49 => Some(0x0031), // Digit one
|
||||
50 => Some(0x0032), // Digit two
|
||||
51 => Some(0x0033), // Digit three
|
||||
52 => Some(0x0034), // Digit four
|
||||
53 => Some(0x0035), // Digit five
|
||||
54 => Some(0x0036), // Digit six
|
||||
55 => Some(0x0037), // Digit seven
|
||||
56 => Some(0x0038), // Digit eight
|
||||
57 => Some(0x0039), // Digit nine
|
||||
58 => Some(0x003A), // Colon
|
||||
59 => Some(0x003B), // Semicolon
|
||||
60 => Some(0x003C), // Less-than sign
|
||||
61 => Some(0x003D), // Equals sign
|
||||
62 => Some(0x003E), // Greater-than sign
|
||||
63 => Some(0x003F), // Question mark
|
||||
64 => Some(0x0040), // Commercial at, at sign
|
||||
65 => Some(0x0041), // Latin capital letter A
|
||||
66 => Some(0x0042), // Latin capital letter B
|
||||
67 => Some(0x0043), // Latin capital letter C
|
||||
68 => Some(0x0044), // Latin capital letter D
|
||||
69 => Some(0x0045), // Latin capital letter E
|
||||
70 => Some(0x0046), // Latin capital letter F
|
||||
71 => Some(0x0047), // Latin capital letter G
|
||||
72 => Some(0x0048), // Latin capital letter H
|
||||
73 => Some(0x0049), // Latin capital letter I
|
||||
74 => Some(0x004A), // Latin capital letter J
|
||||
75 => Some(0x004B), // Latin capital letter K
|
||||
76 => Some(0x004C), // Latin capital letter L
|
||||
77 => Some(0x004D), // Latin capital letter M
|
||||
78 => Some(0x004E), // Latin capital letter N
|
||||
79 => Some(0x004F), // Latin capital letter O
|
||||
80 => Some(0x0050), // Latin capital letter P
|
||||
81 => Some(0x0051), // Latin capital letter Q
|
||||
82 => Some(0x0052), // Latin capital letter R
|
||||
83 => Some(0x0053), // Latin capital letter S
|
||||
84 => Some(0x0054), // Latin capital letter T
|
||||
85 => Some(0x0055), // Latin capital letter U
|
||||
86 => Some(0x0056), // Latin capital letter V
|
||||
87 => Some(0x0057), // Latin capital letter W
|
||||
88 => Some(0x0058), // Latin capital letter X
|
||||
89 => Some(0x0059), // Latin capital letter Y
|
||||
90 => Some(0x005A), // Latin capital letter Z
|
||||
91 => Some(0x005B), // Left square bracket, opening square bracket
|
||||
92 => Some(0x005C), // Reverse solidus, back slash
|
||||
93 => Some(0x005D), // Right square bracket, closing square bracket
|
||||
94 => Some(0x005E), // Circumflex accent
|
||||
95 => Some(0x005F), // Low line, underscore
|
||||
96 => Some(0x0060), // Grave accent
|
||||
97 => Some(0x0061), // Latin small letter a
|
||||
98 => Some(0x0062), // Latin small letter b
|
||||
99 => Some(0x0063), // Latin small letter c
|
||||
100 => Some(0x0064), // Latin small letter d
|
||||
101 => Some(0x0065), // Latin small letter e
|
||||
102 => Some(0x0066), // Latin small letter f
|
||||
103 => Some(0x0067), // Latin small letter g
|
||||
104 => Some(0x0068), // Latin small letter h
|
||||
105 => Some(0x0069), // Latin small letter i
|
||||
106 => Some(0x006A), // Latin small letter j
|
||||
107 => Some(0x006B), // Latin small letter k
|
||||
108 => Some(0x006C), // Latin small letter l
|
||||
109 => Some(0x006D), // Latin small letter m
|
||||
110 => Some(0x006E), // Latin small letter n
|
||||
111 => Some(0x006F), // Latin small letter o
|
||||
112 => Some(0x0070), // Latin small letter p
|
||||
113 => Some(0x0071), // Latin small letter q
|
||||
114 => Some(0x0072), // Latin small letter r
|
||||
115 => Some(0x0073), // Latin small letter s
|
||||
116 => Some(0x0074), // Latin small letter t
|
||||
117 => Some(0x0075), // Latin small letter u
|
||||
118 => Some(0x0076), // Latin small letter v
|
||||
119 => Some(0x0077), // Latin small letter w
|
||||
120 => Some(0x0078), // Latin small letter x
|
||||
121 => Some(0x0079), // Latin small letter y
|
||||
122 => Some(0x007A), // Latin small letter z
|
||||
123 => Some(0x007B), // Left curly bracket, opening curly bracket, left brace
|
||||
124 => Some(0x007C), // Vertical line, vertical bar
|
||||
125 => Some(0x007D), // Right curly bracket, closing curly bracket, right brace
|
||||
126 => Some(0x007E), // Tilde
|
||||
127 => Some(0x2302), // House
|
||||
128 => Some(0x00C7), // Latin capital letter C with cedilla
|
||||
129 => Some(0x00FC), // Latin small letter u with diaeresis
|
||||
130 => Some(0x00E9), // Latin small letter e with acute
|
||||
131 => Some(0x00E2), // Latin small letter a with circumflex
|
||||
132 => Some(0x00E4), // Latin small letter a with diaeresis
|
||||
133 => Some(0x00E0), // Latin small letter a with grave
|
||||
134 => Some(0x00E5), // Latin small letter a with ring above
|
||||
135 => Some(0x00E7), // Latin small letter c with cedilla
|
||||
136 => Some(0x00EA), // Latin small letter e with circumflex
|
||||
137 => Some(0x00EB), // Latin small letter e with diaeresis
|
||||
138 => Some(0x00E8), // Latin small letter e with grave
|
||||
139 => Some(0x00EF), // Latin small letter i with diaeresis
|
||||
140 => Some(0x00EE), // Latin small letter i with circumflex
|
||||
141 => Some(0x00EC), // Latin small letter i with grave
|
||||
142 => Some(0x00C4), // Latin capital letter A with diaeresis
|
||||
143 => Some(0x00C5), // Latin capital letter A with ring above
|
||||
144 => Some(0x00C9), // Latin capital letter E with acute
|
||||
145 => Some(0x00E6), // Latin small letter ae, ash (from Old English æsc)
|
||||
146 => Some(0x00C6), // Latin capital letter AE
|
||||
147 => Some(0x00F4), // Latin small letter o with circumflex
|
||||
148 => Some(0x00F6), // Latin small letter o with diaeresis
|
||||
149 => Some(0x00F2), // Latin small letter o with grave
|
||||
150 => Some(0x00FB), // Latin small letter u with circumflex
|
||||
151 => Some(0x00F9), // Latin small letter u with grave
|
||||
152 => Some(0x00FF), // Latin small letter y with diaeresis
|
||||
153 => Some(0x00D6), // Latin capital letter O with diaeresis
|
||||
154 => Some(0x00DC), // Latin capital letter U with diaeresis
|
||||
155 => Some(0x00A2), // Cent sign
|
||||
156 => Some(0x00A3), // Pound sign, pound sterling, Irish punt, lira sign
|
||||
157 => Some(0x00A5), // Yen sign, yuan sign
|
||||
158 => Some(0x20A7), // Peseta sign
|
||||
159 => Some(0x0192), // Latin small letter f with hook, florin currency symbol, function symbol
|
||||
160 => Some(0x00E1), // Latin small letter a with acute
|
||||
161 => Some(0x00ED), // Latin small letter i with acute
|
||||
162 => Some(0x00F3), // Latin small letter o with acute
|
||||
163 => Some(0x00FA), // Latin small letter u with acute
|
||||
164 => Some(0x00F1), // Latin small letter n with tilde, small letter enye
|
||||
165 => Some(0x00D1), // Latin capital letter N with tilde, capital letter enye
|
||||
166 => Some(0x00AA), // Feminine ordinal indicator
|
||||
167 => Some(0x00BA), // Masculine ordinal indicator
|
||||
168 => Some(0x00BF), // Inverted question mark, turned question mark
|
||||
169 => Some(0x2310), // Reversed not sign, beginning of line
|
||||
170 => Some(0x00AC), // Not sign, angled dash
|
||||
171 => Some(0x00BD), // Vulgar fraction one half
|
||||
172 => Some(0x00BC), // Vulgar fraction one quarter
|
||||
173 => Some(0x00A1), // Inverted exclamation mark
|
||||
174 => Some(0x00AB), // Left-pointing double angle quotation mark, left guillemet, chevrons (in typography)
|
||||
175 => Some(0x00BB), // Right-pointing double angle quotation mark, right guillemet
|
||||
176 => Some(0x2591), // Light shade
|
||||
177 => Some(0x2592), // Medium shade, speckles fill, dotted fill
|
||||
178 => Some(0x2593), // Dark shade
|
||||
179 => Some(0x2502), // Box drawings light vertical
|
||||
180 => Some(0x2524), // Box drawings light vertical and left
|
||||
181 => Some(0x2561), // Box drawings vertical single and left double
|
||||
182 => Some(0x2562), // Box drawings vertical double and left single
|
||||
183 => Some(0x2556), // Box drawings down double and left single
|
||||
184 => Some(0x2555), // Box drawings down single and left double
|
||||
185 => Some(0x2563), // Box drawings double vertical and left
|
||||
186 => Some(0x2551), // Box drawings double vertical
|
||||
187 => Some(0x2557), // Box drawings double down and left
|
||||
188 => Some(0x255D), // Box drawings double up and left
|
||||
189 => Some(0x255C), // Box drawings up double and left single
|
||||
190 => Some(0x255B), // Box drawings up single and left double
|
||||
191 => Some(0x2510), // Box drawings light down and left
|
||||
192 => Some(0x2514), // Box drawings light up and right
|
||||
193 => Some(0x2534), // Box drawings light up and horizontal
|
||||
194 => Some(0x252C), // Box drawings light down and horizontal
|
||||
195 => Some(0x251C), // Box drawings light vertical and right
|
||||
196 => Some(0x2500), // Box drawings light horizontal
|
||||
197 => Some(0x253C), // Box drawings light vertical and horizontal
|
||||
198 => Some(0x255E), // Box drawings vertical single and right double
|
||||
199 => Some(0x255F), // Box drawings vertical double and right single
|
||||
200 => Some(0x255A), // Box drawings double up and right
|
||||
201 => Some(0x2554), // Box drawings double down and right
|
||||
202 => Some(0x2569), // Box drawings double up and horizontal
|
||||
203 => Some(0x2566), // Box drawings double down and horizontal
|
||||
204 => Some(0x2560), // Box drawings double vertical and right
|
||||
205 => Some(0x2550), // Box drawings double horizontal
|
||||
206 => Some(0x256C), // Box drawings double vertical and horizontal
|
||||
207 => Some(0x2567), // Box drawings up single and horizontal double
|
||||
208 => Some(0x2568), // Box drawings up double and horizontal single
|
||||
209 => Some(0x2564), // Box drawings down single and horizontal double
|
||||
210 => Some(0x2565), // Box drawings down double and horizontal single
|
||||
211 => Some(0x2559), // Box drawings up double and right single
|
||||
212 => Some(0x2558), // Box drawings up single and right double
|
||||
213 => Some(0x2552), // Box drawings down single and right double
|
||||
214 => Some(0x2553), // Box drawings down double and right single
|
||||
215 => Some(0x256B), // Box drawings vertical double and horizontal single
|
||||
216 => Some(0x256A), // Box drawings vertical single and horizontal double
|
||||
217 => Some(0x2518), // Box drawings light up and left
|
||||
218 => Some(0x250C), // Box drawings light down and right
|
||||
219 => Some(0x2588), // Full block, solid block
|
||||
220 => Some(0x2584), // Lower half block
|
||||
221 => Some(0x258C), // Left half block
|
||||
222 => Some(0x2590), // Right half block
|
||||
223 => Some(0x2580), // Upper half block
|
||||
224 => Some(0x03B1), // Greek small letter alpha
|
||||
225 => Some(0x00DF), // Latin small letter sharp s, eszett
|
||||
226 => Some(0x0393), // Greek capital letter gamma
|
||||
227 => Some(0x03C0), // Greek small letter pi
|
||||
228 => Some(0x03A3), // Greek capital letter sigma
|
||||
229 => Some(0x03C3), // Greek small letter sigma
|
||||
230 => Some(0x00B5), // Micro sign
|
||||
231 => Some(0x03A4), // Greek capital letter tau
|
||||
232 => Some(0x03A6), // Greek capital letter phi
|
||||
233 => Some(0x0398), // Greek capital letter theta
|
||||
234 => Some(0x03A9), // Greek capital letter omega
|
||||
235 => Some(0x03B4), // Greek small letter delta
|
||||
236 => Some(0x221E), // Infinity
|
||||
237 => Some(0x03C6), // Greek small letter phi
|
||||
238 => Some(0x03B5), // Greek small letter epsilon
|
||||
239 => Some(0x2229), // Intersection
|
||||
240 => Some(0x2261), // Identical to
|
||||
241 => Some(0x00B1), // Plus-minus sign
|
||||
242 => Some(0x2265), // Greater-than or equal to
|
||||
243 => Some(0x2264), // Less-than or equal to
|
||||
244 => Some(0x2320), // Top half integral
|
||||
245 => Some(0x2321), // Bottom half integral
|
||||
246 => Some(0x00F7), // Division sign, obelus
|
||||
247 => Some(0x2248), // Almost equal to, asymptotic to
|
||||
248 => Some(0x00B0), // Degree sign
|
||||
249 => Some(0x2219), // Bullet operator
|
||||
250 => Some(0x00B7), // Middle dot, midpoint (in typography), interpunct, Georgian comma, Greek ano teleia
|
||||
251 => Some(0x221A), // Square root, radical sign
|
||||
252 => Some(0x207F), // Superscript Latin small letter n
|
||||
253 => Some(0x00B2), // Superscript two, squared
|
||||
254 => Some(0x25A0), // Black square
|
||||
255 => Some(0x00A0), // No-break space, non-breaking space, nbsp
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// Taken from here: https://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT
|
||||
/*
|
||||
#
|
||||
# Name: cp1252 to Unicode table
|
||||
# Unicode version: 2.0
|
||||
# Table version: 2.01
|
||||
# Table format: Format A
|
||||
# Date: 04/15/98
|
||||
#
|
||||
# Contact: Shawn.Steele@microsoft.com
|
||||
#
|
||||
# General notes: none
|
||||
#
|
||||
# Format: Three tab-separated columns
|
||||
# Column #1 is the cp1252 code (in hex)
|
||||
# Column #2 is the Unicode (in hex as 0xXXXX)
|
||||
# Column #3 is the Unicode name (follows a comment sign, '#')
|
||||
#
|
||||
# The entries are in cp1252 order
|
||||
#
|
||||
*/
|
||||
fn convert_cp1252_code_to_unicode(code: u32) -> Option<u32> {
|
||||
match code {
|
||||
0x00 => Some(0x0000), // #NULL
|
||||
0x01 => Some(0x0001), // #START OF HEADING
|
||||
0x02 => Some(0x0002), // #START OF TEXT
|
||||
0x03 => Some(0x0003), // #END OF TEXT
|
||||
0x04 => Some(0x0004), // #END OF TRANSMISSION
|
||||
0x05 => Some(0x0005), // #ENQUIRY
|
||||
0x06 => Some(0x0006), // #ACKNOWLEDGE
|
||||
0x07 => Some(0x0007), // #BELL
|
||||
0x08 => Some(0x0008), // #BACKSPACE
|
||||
0x09 => Some(0x0009), // #HORIZONTAL TABULATION
|
||||
0x0A => Some(0x000A), // #LINE FEED
|
||||
0x0B => Some(0x000B), // #VERTICAL TABULATION
|
||||
0x0C => Some(0x000C), // #FORM FEED
|
||||
0x0D => Some(0x000D), // #CARRIAGE RETURN
|
||||
0x0E => Some(0x000E), // #SHIFT OUT
|
||||
0x0F => Some(0x000F), // #SHIFT IN
|
||||
0x10 => Some(0x0010), // #DATA LINK ESCAPE
|
||||
0x11 => Some(0x0011), // #DEVICE CONTROL ONE
|
||||
0x12 => Some(0x0012), // #DEVICE CONTROL TWO
|
||||
0x13 => Some(0x0013), // #DEVICE CONTROL THREE
|
||||
0x14 => Some(0x0014), // #DEVICE CONTROL FOUR
|
||||
0x15 => Some(0x0015), // #NEGATIVE ACKNOWLEDGE
|
||||
0x16 => Some(0x0016), // #SYNCHRONOUS IDLE
|
||||
0x17 => Some(0x0017), // #END OF TRANSMISSION BLOCK
|
||||
0x18 => Some(0x0018), // #CANCEL
|
||||
0x19 => Some(0x0019), // #END OF MEDIUM
|
||||
0x1A => Some(0x001A), // #SUBSTITUTE
|
||||
0x1B => Some(0x001B), // #ESCAPE
|
||||
0x1C => Some(0x001C), // #FILE SEPARATOR
|
||||
0x1D => Some(0x001D), // #GROUP SEPARATOR
|
||||
0x1E => Some(0x001E), // #RECORD SEPARATOR
|
||||
0x1F => Some(0x001F), // #UNIT SEPARATOR
|
||||
0x20 => Some(0x0020), // #SPACE
|
||||
0x21 => Some(0x0021), // #EXCLAMATION MARK
|
||||
0x22 => Some(0x0022), // #QUOTATION MARK
|
||||
0x23 => Some(0x0023), // #NUMBER SIGN
|
||||
0x24 => Some(0x0024), // #DOLLAR SIGN
|
||||
0x25 => Some(0x0025), // #PERCENT SIGN
|
||||
0x26 => Some(0x0026), // #AMPERSAND
|
||||
0x27 => Some(0x0027), // #APOSTROPHE
|
||||
0x28 => Some(0x0028), // #LEFT PARENTHESIS
|
||||
0x29 => Some(0x0029), // #RIGHT PARENTHESIS
|
||||
0x2A => Some(0x002A), // #ASTERISK
|
||||
0x2B => Some(0x002B), // #PLUS SIGN
|
||||
0x2C => Some(0x002C), // #COMMA
|
||||
0x2D => Some(0x002D), // #HYPHEN-MINUS
|
||||
0x2E => Some(0x002E), // #FULL STOP
|
||||
0x2F => Some(0x002F), // #SOLIDUS
|
||||
0x30 => Some(0x0030), // #DIGIT ZERO
|
||||
0x31 => Some(0x0031), // #DIGIT ONE
|
||||
0x32 => Some(0x0032), // #DIGIT TWO
|
||||
0x33 => Some(0x0033), // #DIGIT THREE
|
||||
0x34 => Some(0x0034), // #DIGIT FOUR
|
||||
0x35 => Some(0x0035), // #DIGIT FIVE
|
||||
0x36 => Some(0x0036), // #DIGIT SIX
|
||||
0x37 => Some(0x0037), // #DIGIT SEVEN
|
||||
0x38 => Some(0x0038), // #DIGIT EIGHT
|
||||
0x39 => Some(0x0039), // #DIGIT NINE
|
||||
0x3A => Some(0x003A), // #COLON
|
||||
0x3B => Some(0x003B), // #SEMICOLON
|
||||
0x3C => Some(0x003C), // #LESS-THAN SIGN
|
||||
0x3D => Some(0x003D), // #EQUALS SIGN
|
||||
0x3E => Some(0x003E), // #GREATER-THAN SIGN
|
||||
0x3F => Some(0x003F), // #QUESTION MARK
|
||||
0x40 => Some(0x0040), // #COMMERCIAL AT
|
||||
0x41 => Some(0x0041), // #LATIN CAPITAL LETTER A
|
||||
0x42 => Some(0x0042), // #LATIN CAPITAL LETTER B
|
||||
0x43 => Some(0x0043), // #LATIN CAPITAL LETTER C
|
||||
0x44 => Some(0x0044), // #LATIN CAPITAL LETTER D
|
||||
0x45 => Some(0x0045), // #LATIN CAPITAL LETTER E
|
||||
0x46 => Some(0x0046), // #LATIN CAPITAL LETTER F
|
||||
0x47 => Some(0x0047), // #LATIN CAPITAL LETTER G
|
||||
0x48 => Some(0x0048), // #LATIN CAPITAL LETTER H
|
||||
0x49 => Some(0x0049), // #LATIN CAPITAL LETTER I
|
||||
0x4A => Some(0x004A), // #LATIN CAPITAL LETTER J
|
||||
0x4B => Some(0x004B), // #LATIN CAPITAL LETTER K
|
||||
0x4C => Some(0x004C), // #LATIN CAPITAL LETTER L
|
||||
0x4D => Some(0x004D), // #LATIN CAPITAL LETTER M
|
||||
0x4E => Some(0x004E), // #LATIN CAPITAL LETTER N
|
||||
0x4F => Some(0x004F), // #LATIN CAPITAL LETTER O
|
||||
0x50 => Some(0x0050), // #LATIN CAPITAL LETTER P
|
||||
0x51 => Some(0x0051), // #LATIN CAPITAL LETTER Q
|
||||
0x52 => Some(0x0052), // #LATIN CAPITAL LETTER R
|
||||
0x53 => Some(0x0053), // #LATIN CAPITAL LETTER S
|
||||
0x54 => Some(0x0054), // #LATIN CAPITAL LETTER T
|
||||
0x55 => Some(0x0055), // #LATIN CAPITAL LETTER U
|
||||
0x56 => Some(0x0056), // #LATIN CAPITAL LETTER V
|
||||
0x57 => Some(0x0057), // #LATIN CAPITAL LETTER W
|
||||
0x58 => Some(0x0058), // #LATIN CAPITAL LETTER X
|
||||
0x59 => Some(0x0059), // #LATIN CAPITAL LETTER Y
|
||||
0x5A => Some(0x005A), // #LATIN CAPITAL LETTER Z
|
||||
0x5B => Some(0x005B), // #LEFT SQUARE BRACKET
|
||||
0x5C => Some(0x005C), // #REVERSE SOLIDUS
|
||||
0x5D => Some(0x005D), // #RIGHT SQUARE BRACKET
|
||||
0x5E => Some(0x005E), // #CIRCUMFLEX ACCENT
|
||||
0x5F => Some(0x005F), // #LOW LINE
|
||||
0x60 => Some(0x0060), // #GRAVE ACCENT
|
||||
0x61 => Some(0x0061), // #LATIN SMALL LETTER A
|
||||
0x62 => Some(0x0062), // #LATIN SMALL LETTER B
|
||||
0x63 => Some(0x0063), // #LATIN SMALL LETTER C
|
||||
0x64 => Some(0x0064), // #LATIN SMALL LETTER D
|
||||
0x65 => Some(0x0065), // #LATIN SMALL LETTER E
|
||||
0x66 => Some(0x0066), // #LATIN SMALL LETTER F
|
||||
0x67 => Some(0x0067), // #LATIN SMALL LETTER G
|
||||
0x68 => Some(0x0068), // #LATIN SMALL LETTER H
|
||||
0x69 => Some(0x0069), // #LATIN SMALL LETTER I
|
||||
0x6A => Some(0x006A), // #LATIN SMALL LETTER J
|
||||
0x6B => Some(0x006B), // #LATIN SMALL LETTER K
|
||||
0x6C => Some(0x006C), // #LATIN SMALL LETTER L
|
||||
0x6D => Some(0x006D), // #LATIN SMALL LETTER M
|
||||
0x6E => Some(0x006E), // #LATIN SMALL LETTER N
|
||||
0x6F => Some(0x006F), // #LATIN SMALL LETTER O
|
||||
0x70 => Some(0x0070), // #LATIN SMALL LETTER P
|
||||
0x71 => Some(0x0071), // #LATIN SMALL LETTER Q
|
||||
0x72 => Some(0x0072), // #LATIN SMALL LETTER R
|
||||
0x73 => Some(0x0073), // #LATIN SMALL LETTER S
|
||||
0x74 => Some(0x0074), // #LATIN SMALL LETTER T
|
||||
0x75 => Some(0x0075), // #LATIN SMALL LETTER U
|
||||
0x76 => Some(0x0076), // #LATIN SMALL LETTER V
|
||||
0x77 => Some(0x0077), // #LATIN SMALL LETTER W
|
||||
0x78 => Some(0x0078), // #LATIN SMALL LETTER X
|
||||
0x79 => Some(0x0079), // #LATIN SMALL LETTER Y
|
||||
0x7A => Some(0x007A), // #LATIN SMALL LETTER Z
|
||||
0x7B => Some(0x007B), // #LEFT CURLY BRACKET
|
||||
0x7C => Some(0x007C), // #VERTICAL LINE
|
||||
0x7D => Some(0x007D), // #RIGHT CURLY BRACKET
|
||||
0x7E => Some(0x007E), // #TILDE
|
||||
0x7F => Some(0x007F), // #DELETE
|
||||
0x80 => Some(0x20AC), // #EURO SIGN
|
||||
0x82 => Some(0x201A), // #SINGLE LOW-9 QUOTATION MARK
|
||||
0x83 => Some(0x0192), // #LATIN SMALL LETTER F WITH HOOK
|
||||
0x84 => Some(0x201E), // #DOUBLE LOW-9 QUOTATION MARK
|
||||
0x85 => Some(0x2026), // #HORIZONTAL ELLIPSIS
|
||||
0x86 => Some(0x2020), // #DAGGER
|
||||
0x87 => Some(0x2021), // #DOUBLE DAGGER
|
||||
0x88 => Some(0x02C6), // #MODIFIER LETTER CIRCUMFLEX ACCENT
|
||||
0x89 => Some(0x2030), // #PER MILLE SIGN
|
||||
0x8A => Some(0x0160), // #LATIN CAPITAL LETTER S WITH CARON
|
||||
0x8B => Some(0x2039), // #SINGLE LEFT-POINTING ANGLE QUOTATION MARK
|
||||
0x8C => Some(0x0152), // #LATIN CAPITAL LIGATURE OE
|
||||
0x8E => Some(0x017D), // #LATIN CAPITAL LETTER Z WITH CARON
|
||||
0x91 => Some(0x2018), // #LEFT SINGLE QUOTATION MARK
|
||||
0x92 => Some(0x2019), // #RIGHT SINGLE QUOTATION MARK
|
||||
0x93 => Some(0x201C), // #LEFT DOUBLE QUOTATION MARK
|
||||
0x94 => Some(0x201D), // #RIGHT DOUBLE QUOTATION MARK
|
||||
0x95 => Some(0x2022), // #BULLET
|
||||
0x96 => Some(0x2013), // #EN DASH
|
||||
0x97 => Some(0x2014), // #EM DASH
|
||||
0x98 => Some(0x02DC), // #SMALL TILDE
|
||||
0x99 => Some(0x2122), // #TRADE MARK SIGN
|
||||
0x9A => Some(0x0161), // #LATIN SMALL LETTER S WITH CARON
|
||||
0x9B => Some(0x203A), // #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
|
||||
0x9C => Some(0x0153), // #LATIN SMALL LIGATURE OE
|
||||
0x9E => Some(0x017E), // #LATIN SMALL LETTER Z WITH CARON
|
||||
0x9F => Some(0x0178), // #LATIN CAPITAL LETTER Y WITH DIAERESIS
|
||||
0xA0 => Some(0x00A0), // #NO-BREAK SPACE
|
||||
0xA1 => Some(0x00A1), // #INVERTED EXCLAMATION MARK
|
||||
0xA2 => Some(0x00A2), // #CENT SIGN
|
||||
0xA3 => Some(0x00A3), // #POUND SIGN
|
||||
0xA4 => Some(0x00A4), // #CURRENCY SIGN
|
||||
0xA5 => Some(0x00A5), // #YEN SIGN
|
||||
0xA6 => Some(0x00A6), // #BROKEN BAR
|
||||
0xA7 => Some(0x00A7), // #SECTION SIGN
|
||||
0xA8 => Some(0x00A8), // #DIAERESIS
|
||||
0xA9 => Some(0x00A9), // #COPYRIGHT SIGN
|
||||
0xAA => Some(0x00AA), // #FEMININE ORDINAL INDICATOR
|
||||
0xAB => Some(0x00AB), // #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
|
||||
0xAC => Some(0x00AC), // #NOT SIGN
|
||||
0xAD => Some(0x00AD), // #SOFT HYPHEN
|
||||
0xAE => Some(0x00AE), // #REGISTERED SIGN
|
||||
0xAF => Some(0x00AF), // #MACRON
|
||||
0xB0 => Some(0x00B0), // #DEGREE SIGN
|
||||
0xB1 => Some(0x00B1), // #PLUS-MINUS SIGN
|
||||
0xB2 => Some(0x00B2), // #SUPERSCRIPT TWO
|
||||
0xB3 => Some(0x00B3), // #SUPERSCRIPT THREE
|
||||
0xB4 => Some(0x00B4), // #ACUTE ACCENT
|
||||
0xB5 => Some(0x00B5), // #MICRO SIGN
|
||||
0xB6 => Some(0x00B6), // #PILCROW SIGN
|
||||
0xB7 => Some(0x00B7), // #MIDDLE DOT
|
||||
0xB8 => Some(0x00B8), // #CEDILLA
|
||||
0xB9 => Some(0x00B9), // #SUPERSCRIPT ONE
|
||||
0xBA => Some(0x00BA), // #MASCULINE ORDINAL INDICATOR
|
||||
0xBB => Some(0x00BB), // #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
|
||||
0xBC => Some(0x00BC), // #VULGAR FRACTION ONE QUARTER
|
||||
0xBD => Some(0x00BD), // #VULGAR FRACTION ONE HALF
|
||||
0xBE => Some(0x00BE), // #VULGAR FRACTION THREE QUARTERS
|
||||
0xBF => Some(0x00BF), // #INVERTED QUESTION MARK
|
||||
0xC0 => Some(0x00C0), // #LATIN CAPITAL LETTER A WITH GRAVE
|
||||
0xC1 => Some(0x00C1), // #LATIN CAPITAL LETTER A WITH ACUTE
|
||||
0xC2 => Some(0x00C2), // #LATIN CAPITAL LETTER A WITH CIRCUMFLEX
|
||||
0xC3 => Some(0x00C3), // #LATIN CAPITAL LETTER A WITH TILDE
|
||||
0xC4 => Some(0x00C4), // #LATIN CAPITAL LETTER A WITH DIAERESIS
|
||||
0xC5 => Some(0x00C5), // #LATIN CAPITAL LETTER A WITH RING ABOVE
|
||||
0xC6 => Some(0x00C6), // #LATIN CAPITAL LETTER AE
|
||||
0xC7 => Some(0x00C7), // #LATIN CAPITAL LETTER C WITH CEDILLA
|
||||
0xC8 => Some(0x00C8), // #LATIN CAPITAL LETTER E WITH GRAVE
|
||||
0xC9 => Some(0x00C9), // #LATIN CAPITAL LETTER E WITH ACUTE
|
||||
0xCA => Some(0x00CA), // #LATIN CAPITAL LETTER E WITH CIRCUMFLEX
|
||||
0xCB => Some(0x00CB), // #LATIN CAPITAL LETTER E WITH DIAERESIS
|
||||
0xCC => Some(0x00CC), // #LATIN CAPITAL LETTER I WITH GRAVE
|
||||
0xCD => Some(0x00CD), // #LATIN CAPITAL LETTER I WITH ACUTE
|
||||
0xCE => Some(0x00CE), // #LATIN CAPITAL LETTER I WITH CIRCUMFLEX
|
||||
0xCF => Some(0x00CF), // #LATIN CAPITAL LETTER I WITH DIAERESIS
|
||||
0xD0 => Some(0x00D0), // #LATIN CAPITAL LETTER ETH
|
||||
0xD1 => Some(0x00D1), // #LATIN CAPITAL LETTER N WITH TILDE
|
||||
0xD2 => Some(0x00D2), // #LATIN CAPITAL LETTER O WITH GRAVE
|
||||
0xD3 => Some(0x00D3), // #LATIN CAPITAL LETTER O WITH ACUTE
|
||||
0xD4 => Some(0x00D4), // #LATIN CAPITAL LETTER O WITH CIRCUMFLEX
|
||||
0xD5 => Some(0x00D5), // #LATIN CAPITAL LETTER O WITH TILDE
|
||||
0xD6 => Some(0x00D6), // #LATIN CAPITAL LETTER O WITH DIAERESIS
|
||||
0xD7 => Some(0x00D7), // #MULTIPLICATION SIGN
|
||||
0xD8 => Some(0x00D8), // #LATIN CAPITAL LETTER O WITH STROKE
|
||||
0xD9 => Some(0x00D9), // #LATIN CAPITAL LETTER U WITH GRAVE
|
||||
0xDA => Some(0x00DA), // #LATIN CAPITAL LETTER U WITH ACUTE
|
||||
0xDB => Some(0x00DB), // #LATIN CAPITAL LETTER U WITH CIRCUMFLEX
|
||||
0xDC => Some(0x00DC), // #LATIN CAPITAL LETTER U WITH DIAERESIS
|
||||
0xDD => Some(0x00DD), // #LATIN CAPITAL LETTER Y WITH ACUTE
|
||||
0xDE => Some(0x00DE), // #LATIN CAPITAL LETTER THORN
|
||||
0xDF => Some(0x00DF), // #LATIN SMALL LETTER SHARP S
|
||||
0xE0 => Some(0x00E0), // #LATIN SMALL LETTER A WITH GRAVE
|
||||
0xE1 => Some(0x00E1), // #LATIN SMALL LETTER A WITH ACUTE
|
||||
0xE2 => Some(0x00E2), // #LATIN SMALL LETTER A WITH CIRCUMFLEX
|
||||
0xE3 => Some(0x00E3), // #LATIN SMALL LETTER A WITH TILDE
|
||||
0xE4 => Some(0x00E4), // #LATIN SMALL LETTER A WITH DIAERESIS
|
||||
0xE5 => Some(0x00E5), // #LATIN SMALL LETTER A WITH RING ABOVE
|
||||
0xE6 => Some(0x00E6), // #LATIN SMALL LETTER AE
|
||||
0xE7 => Some(0x00E7), // #LATIN SMALL LETTER C WITH CEDILLA
|
||||
0xE8 => Some(0x00E8), // #LATIN SMALL LETTER E WITH GRAVE
|
||||
0xE9 => Some(0x00E9), // #LATIN SMALL LETTER E WITH ACUTE
|
||||
0xEA => Some(0x00EA), // #LATIN SMALL LETTER E WITH CIRCUMFLEX
|
||||
0xEB => Some(0x00EB), // #LATIN SMALL LETTER E WITH DIAERESIS
|
||||
0xEC => Some(0x00EC), // #LATIN SMALL LETTER I WITH GRAVE
|
||||
0xED => Some(0x00ED), // #LATIN SMALL LETTER I WITH ACUTE
|
||||
0xEE => Some(0x00EE), // #LATIN SMALL LETTER I WITH CIRCUMFLEX
|
||||
0xEF => Some(0x00EF), // #LATIN SMALL LETTER I WITH DIAERESIS
|
||||
0xF0 => Some(0x00F0), // #LATIN SMALL LETTER ETH
|
||||
0xF1 => Some(0x00F1), // #LATIN SMALL LETTER N WITH TILDE
|
||||
0xF2 => Some(0x00F2), // #LATIN SMALL LETTER O WITH GRAVE
|
||||
0xF3 => Some(0x00F3), // #LATIN SMALL LETTER O WITH ACUTE
|
||||
0xF4 => Some(0x00F4), // #LATIN SMALL LETTER O WITH CIRCUMFLEX
|
||||
0xF5 => Some(0x00F5), // #LATIN SMALL LETTER O WITH TILDE
|
||||
0xF6 => Some(0x00F6), // #LATIN SMALL LETTER O WITH DIAERESIS
|
||||
0xF7 => Some(0x00F7), // #DIVISION SIGN
|
||||
0xF8 => Some(0x00F8), // #LATIN SMALL LETTER O WITH STROKE
|
||||
0xF9 => Some(0x00F9), // #LATIN SMALL LETTER U WITH GRAVE
|
||||
0xFA => Some(0x00FA), // #LATIN SMALL LETTER U WITH ACUTE
|
||||
0xFB => Some(0x00FB), // #LATIN SMALL LETTER U WITH CIRCUMFLEX
|
||||
0xFC => Some(0x00FC), // #LATIN SMALL LETTER U WITH DIAERESIS
|
||||
0xFD => Some(0x00FD), // #LATIN SMALL LETTER Y WITH ACUTE
|
||||
0xFE => Some(0x00FE), // #LATIN SMALL LETTER THORN
|
||||
0xFF => Some(0x00FF), // #LATIN SMALL LETTER Y WITH DIAERESIS
|
||||
_ => None,
|
||||
}
|
||||
}
|
|
@ -44,7 +44,7 @@ pub enum MatcherEvent {
|
|||
VirtualSeparator,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MatchResult {
|
||||
pub id: i32,
|
||||
pub trigger: String,
|
||||
|
@ -104,7 +104,7 @@ impl<'a, State> Middleware for MatcherMiddleware<'a, State> {
|
|||
if is_event_of_interest(&event.etype) {
|
||||
let mut matcher_states = self.matcher_states.borrow_mut();
|
||||
let prev_states = if !matcher_states.is_empty() {
|
||||
matcher_states.get(matcher_states.len() - 1)
|
||||
matcher_states.back()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
pub mod action;
|
||||
pub mod alt_code_synthesizer;
|
||||
pub mod cause;
|
||||
pub mod context_menu;
|
||||
pub mod cursor_hint;
|
||||
|
|
|
@ -34,6 +34,7 @@ pub trait Processor {
|
|||
// Dependency inversion entities
|
||||
|
||||
pub use middleware::action::{EventSequenceProvider, MatchInfoProvider};
|
||||
pub use middleware::alt_code_synthesizer::AltCodeSynthEnabledProvider;
|
||||
pub use middleware::delay_modifiers::ModifierStatusProvider;
|
||||
pub use middleware::disable::DisableOptions;
|
||||
pub use middleware::image_resolve::PathProvider;
|
||||
|
@ -69,6 +70,7 @@ pub fn default<'a, MatcherState>(
|
|||
modifier_state_provider: &'a dyn ModifierStateProvider,
|
||||
match_resolver: &'a dyn MatchResolver,
|
||||
notification_manager: &'a dyn NotificationManager,
|
||||
alt_code_synth_enabled_provider: &'a dyn AltCodeSynthEnabledProvider,
|
||||
) -> impl Processor + 'a {
|
||||
default::DefaultProcessor::new(
|
||||
matchers,
|
||||
|
@ -88,5 +90,6 @@ pub fn default<'a, MatcherState>(
|
|||
modifier_state_provider,
|
||||
match_resolver,
|
||||
notification_manager,
|
||||
alt_code_synth_enabled_provider,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -37,6 +37,9 @@ fn cc_config() {
|
|||
fn cc_config() {
|
||||
println!("cargo:rerun-if-changed=src/evdev/native.h");
|
||||
println!("cargo:rerun-if-changed=src/evdev/native.c");
|
||||
println!("cargo:rerun-if-changed=src/x11/xdotool/vendor/xdo.c");
|
||||
println!("cargo:rerun-if-changed=src/x11/xdotool/vendor/xdo.h");
|
||||
println!("cargo:rerun-if-changed=src/x11/xdotool/vendor/xdo_util.h");
|
||||
cc::Build::new()
|
||||
.include("src/evdev")
|
||||
.file("src/evdev/native.c")
|
||||
|
@ -47,6 +50,14 @@ fn cc_config() {
|
|||
println!("cargo:rustc-link-lib=dylib=xkbcommon");
|
||||
|
||||
if cfg!(not(feature = "wayland")) {
|
||||
cc::Build::new()
|
||||
.cpp(false)
|
||||
.include("src/x11/xdotool/vendor/xdo.h")
|
||||
.include("src/x11/xdotool/vendor/xdo_util.h")
|
||||
.file("src/x11/xdotool/vendor/xdo.c")
|
||||
.compile("xdotoolvendor");
|
||||
|
||||
println!("cargo:rustc-link-lib=static=xdotoolvendor");
|
||||
println!("cargo:rustc-link-lib=dylib=X11");
|
||||
println!("cargo:rustc-link-lib=dylib=Xtst");
|
||||
}
|
||||
|
|
|
@ -61,6 +61,10 @@ pub struct InjectionOptions {
|
|||
// Used to set a modifier-specific delay.
|
||||
// NOTE: Only relevant on Wayland systems.
|
||||
pub evdev_modifier_delay: u32,
|
||||
|
||||
// If true, use the xdotool fallback to perform the expansions.
|
||||
// NOTE: Only relevant on Linux-X11 systems.
|
||||
pub x11_use_xdotool_fallback: bool,
|
||||
}
|
||||
|
||||
impl Default for InjectionOptions {
|
||||
|
@ -84,6 +88,7 @@ impl Default for InjectionOptions {
|
|||
delay: default_delay,
|
||||
disable_fast_inject: false,
|
||||
evdev_modifier_delay: 10,
|
||||
x11_use_xdotool_fallback: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -148,8 +153,8 @@ pub fn get_injector(options: InjectorCreationOptions) -> Result<Box<dyn Injector
|
|||
info!("using EVDEVInjector");
|
||||
Ok(Box::new(evdev::EVDEVInjector::new(options)?))
|
||||
} else {
|
||||
info!("using X11Injector");
|
||||
Ok(Box::new(x11::X11Injector::new()?))
|
||||
info!("using X11ProxyInjector");
|
||||
Ok(Box::new(x11::X11ProxyInjector::new()?))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
Same approach as evdev, but the lookup logic is:
|
||||
|
||||
#include <locale.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <X11/Xlibint.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/cursorfont.h>
|
||||
#include <X11/keysymdef.h>
|
||||
#include <X11/keysym.h>
|
||||
#include <X11/extensions/record.h>
|
||||
#include <X11/extensions/XTest.h>
|
||||
#include <X11/XKBlib.h>
|
||||
#include <X11/Xatom.h>
|
||||
|
||||
Display *data_disp = NULL;
|
||||
|
||||
int main() {
|
||||
data_disp = XOpenDisplay(NULL);
|
||||
|
||||
for (int code = 0; code<256; code++) {
|
||||
for (int state = 0; state < 256; state++) {
|
||||
XKeyEvent event;
|
||||
event.display = data_disp;
|
||||
event.window = XDefaultRootWindow(data_disp);
|
||||
event.root = XDefaultRootWindow(data_disp);
|
||||
event.subwindow = None;
|
||||
event.time = 0;
|
||||
event.x = 1;
|
||||
event.y = 1;
|
||||
event.x_root = 1;
|
||||
event.y_root = 1;
|
||||
event.same_screen = True;
|
||||
event.keycode = code + 8;
|
||||
event.state = state;
|
||||
event.type = KeyPress;
|
||||
|
||||
char buffer[10];
|
||||
int res = XLookupString(&event, buffer, 9, NULL, NULL);
|
||||
|
||||
printf("hey %d %d %s\n", code, state, buffer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
This way, we get the state mask associated with a character, and we can pass it directly when injecting a character:
|
||||
https://github.com/federico-terzi/espanso/blob/master/native/liblinuxbridge/fast_xdo.cpp#L37
|
598
espanso-inject/src/x11/default/mod.rs
Normal file
598
espanso-inject/src/x11/default/mod.rs
Normal file
|
@ -0,0 +1,598 @@
|
|||
/*
|
||||
* This file is part of espanso.
|
||||
*
|
||||
* Copyright (C) 2019-2021 Federico Terzi
|
||||
*
|
||||
* espanso is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* espanso is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
ffi::{CStr, CString},
|
||||
os::raw::c_char,
|
||||
slice,
|
||||
};
|
||||
|
||||
use crate::x11::ffi::{
|
||||
Display, KeyCode, KeyPress, KeyRelease, KeySym, Window, XCloseDisplay, XDefaultRootWindow,
|
||||
XFlush, XFreeModifiermap, XGetInputFocus, XGetModifierMapping, XKeyEvent, XQueryKeymap,
|
||||
XSendEvent, XSync, XTestFakeKeyEvent,
|
||||
};
|
||||
use libc::c_void;
|
||||
use log::{debug, error};
|
||||
|
||||
use crate::{linux::raw_keys::convert_to_sym_array, x11::ffi::Xutf8LookupString};
|
||||
use anyhow::{bail, Result};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{keys, InjectionOptions, Injector};
|
||||
|
||||
use crate::x11::ffi::{
|
||||
XCloseIM, XCreateIC, XDestroyIC, XFilterEvent, XFree, XIMPreeditNothing, XIMStatusNothing,
|
||||
XNClientWindow_0, XNInputStyle_0, XOpenIM, XmbResetIC, XIC,
|
||||
};
|
||||
|
||||
// Offset between evdev keycodes (where KEY_ESCAPE is 1), and the evdev XKB
|
||||
// keycode set (where ESC is 9).
|
||||
const EVDEV_OFFSET: u32 = 8;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct KeyPair {
|
||||
// Keycode
|
||||
code: u32,
|
||||
// Modifier state which combined with the code produces the char
|
||||
// This is a bit mask:
|
||||
state: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct KeyRecord {
|
||||
main: KeyPair,
|
||||
|
||||
// Under some keyboard layouts (de, es), a deadkey
|
||||
// press might be needed to generate the right char
|
||||
preceding_dead_key: Option<KeyPair>,
|
||||
}
|
||||
|
||||
type CharMap = HashMap<String, KeyRecord>;
|
||||
type SymMap = HashMap<KeySym, KeyRecord>;
|
||||
|
||||
pub struct X11DefaultInjector {
|
||||
display: *mut Display,
|
||||
|
||||
char_map: CharMap,
|
||||
sym_map: SymMap,
|
||||
}
|
||||
|
||||
#[allow(clippy::new_without_default)]
|
||||
impl X11DefaultInjector {
|
||||
pub fn new() -> Result<Self> {
|
||||
// Necessary to properly handle non-ascii chars
|
||||
let empty_string = CString::new("")?;
|
||||
unsafe {
|
||||
libc::setlocale(libc::LC_ALL, empty_string.as_ptr());
|
||||
}
|
||||
|
||||
let display = unsafe { crate::x11::ffi::XOpenDisplay(std::ptr::null()) };
|
||||
if display.is_null() {
|
||||
return Err(X11InjectorError::Init().into());
|
||||
}
|
||||
|
||||
let (char_map, sym_map) = Self::generate_maps(display)?;
|
||||
|
||||
Ok(Self {
|
||||
display,
|
||||
char_map,
|
||||
sym_map,
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_maps(display: *mut Display) -> Result<(CharMap, SymMap)> {
|
||||
debug!("generating key maps");
|
||||
|
||||
let mut char_map = HashMap::new();
|
||||
let mut sym_map = HashMap::new();
|
||||
|
||||
let input_method = unsafe {
|
||||
XOpenIM(
|
||||
display,
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
if input_method.is_null() {
|
||||
bail!("could not open input method");
|
||||
}
|
||||
let _im_guard = scopeguard::guard((), |_| {
|
||||
unsafe { XCloseIM(input_method) };
|
||||
});
|
||||
|
||||
let input_context = unsafe {
|
||||
XCreateIC(
|
||||
input_method,
|
||||
XNInputStyle_0.as_ptr(),
|
||||
XIMPreeditNothing | XIMStatusNothing,
|
||||
XNClientWindow_0.as_ptr(),
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
if input_context.is_null() {
|
||||
bail!("could not open input context");
|
||||
}
|
||||
let _ic_guard = scopeguard::guard((), |_| {
|
||||
unsafe { XDestroyIC(input_context) };
|
||||
});
|
||||
|
||||
let deadkeys = Self::find_deadkeys(display, &input_context)?;
|
||||
|
||||
// Cycle through all state/code combinations to populate the reverse lookup tables
|
||||
for key_code in 0..256u32 {
|
||||
for modifier_state in 0..256u32 {
|
||||
for dead_key in deadkeys.iter() {
|
||||
let code_with_offset = key_code + EVDEV_OFFSET;
|
||||
|
||||
let preceding_dead_key = if let Some(dead_key) = dead_key {
|
||||
let mut dead_key_event = XKeyEvent {
|
||||
display,
|
||||
keycode: dead_key.code,
|
||||
state: dead_key.state,
|
||||
|
||||
// These might not even need to be filled
|
||||
window: 0,
|
||||
root: 0,
|
||||
same_screen: 1,
|
||||
time: 0,
|
||||
type_: KeyPress,
|
||||
x_root: 1,
|
||||
y_root: 1,
|
||||
x: 1,
|
||||
y: 1,
|
||||
subwindow: 0,
|
||||
serial: 0,
|
||||
send_event: 0,
|
||||
};
|
||||
|
||||
unsafe { XFilterEvent(&mut dead_key_event, 0) };
|
||||
|
||||
Some(*dead_key)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut key_event = XKeyEvent {
|
||||
display,
|
||||
keycode: code_with_offset,
|
||||
state: modifier_state,
|
||||
|
||||
// These might not even need to be filled
|
||||
window: 0,
|
||||
root: 0,
|
||||
same_screen: 1,
|
||||
time: 0,
|
||||
type_: KeyPress,
|
||||
x_root: 1,
|
||||
y_root: 1,
|
||||
x: 1,
|
||||
y: 1,
|
||||
subwindow: 0,
|
||||
serial: 0,
|
||||
send_event: 0,
|
||||
};
|
||||
|
||||
unsafe { XFilterEvent(&mut key_event, 0) };
|
||||
let mut sym: KeySym = 0;
|
||||
let mut buffer: [c_char; 10] = [0; 10];
|
||||
|
||||
let result = unsafe {
|
||||
Xutf8LookupString(
|
||||
input_context,
|
||||
&mut key_event,
|
||||
buffer.as_mut_ptr(),
|
||||
(buffer.len() - 1) as i32,
|
||||
&mut sym,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
let key_record = KeyRecord {
|
||||
main: KeyPair {
|
||||
code: code_with_offset,
|
||||
state: modifier_state,
|
||||
},
|
||||
preceding_dead_key,
|
||||
};
|
||||
|
||||
// Keysym was found
|
||||
if sym != 0 {
|
||||
sym_map.entry(sym).or_insert(key_record);
|
||||
};
|
||||
|
||||
// Char was found
|
||||
if result > 0 {
|
||||
let raw_string = unsafe { CStr::from_ptr(buffer.as_ptr()) };
|
||||
let string = raw_string.to_string_lossy().to_string();
|
||||
char_map.entry(string).or_insert(key_record);
|
||||
};
|
||||
|
||||
// We need to reset the context state to prevent
|
||||
// deadkeys effect to propagate to the next combination
|
||||
let _reset = unsafe { XmbResetIC(input_context) };
|
||||
unsafe { XFree(_reset as *mut c_void) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Populated char_map with {} symbols", char_map.len());
|
||||
debug!("Populated sym_map with {} symbols", sym_map.len());
|
||||
debug!("Detected {} dead key combinations", deadkeys.len());
|
||||
|
||||
Ok((char_map, sym_map))
|
||||
}
|
||||
|
||||
fn find_deadkeys(display: *mut Display, input_context: &XIC) -> Result<Vec<Option<KeyPair>>> {
|
||||
let mut deadkeys = vec![None];
|
||||
let mut seen_keysyms: HashSet<KeySym> = HashSet::new();
|
||||
|
||||
// Cycle through all state/code combinations to populate the reverse lookup tables
|
||||
for key_code in 0..256u32 {
|
||||
for modifier_state in 0..256u32 {
|
||||
let code_with_offset = key_code + EVDEV_OFFSET;
|
||||
let mut event = XKeyEvent {
|
||||
display,
|
||||
keycode: code_with_offset,
|
||||
state: modifier_state,
|
||||
|
||||
// These might not even need to be filled
|
||||
window: 0,
|
||||
root: 0,
|
||||
same_screen: 1,
|
||||
time: 0,
|
||||
type_: KeyPress,
|
||||
x_root: 1,
|
||||
y_root: 1,
|
||||
x: 1,
|
||||
y: 1,
|
||||
subwindow: 0,
|
||||
serial: 0,
|
||||
send_event: 0,
|
||||
};
|
||||
|
||||
let filter = unsafe { XFilterEvent(&mut event, 0) };
|
||||
if filter == 1 {
|
||||
let mut sym: KeySym = 0;
|
||||
let mut buffer: [c_char; 10] = [0; 10];
|
||||
|
||||
unsafe {
|
||||
Xutf8LookupString(
|
||||
*input_context,
|
||||
&mut event,
|
||||
buffer.as_mut_ptr(),
|
||||
(buffer.len() - 1) as i32,
|
||||
&mut sym,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
if sym != 0 && !seen_keysyms.contains(&sym) {
|
||||
let key_record = KeyPair {
|
||||
code: code_with_offset,
|
||||
state: modifier_state,
|
||||
};
|
||||
deadkeys.push(Some(key_record));
|
||||
seen_keysyms.insert(sym);
|
||||
}
|
||||
}
|
||||
|
||||
let _reset = unsafe { XmbResetIC(*input_context) };
|
||||
unsafe { XFree(_reset as *mut c_void) };
|
||||
}
|
||||
}
|
||||
|
||||
Ok(deadkeys)
|
||||
}
|
||||
|
||||
fn convert_to_record_array(&self, syms: &[KeySym]) -> Result<Vec<KeyRecord>> {
|
||||
syms
|
||||
.iter()
|
||||
.map(|sym| {
|
||||
self
|
||||
.sym_map
|
||||
.get(sym)
|
||||
.cloned()
|
||||
.ok_or_else(|| X11InjectorError::SymMapping(*sym).into())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
// This method was inspired by the wonderful xdotool by Jordan Sissel
|
||||
// https://github.com/jordansissel/xdotool
|
||||
fn get_modifier_codes(&self) -> Vec<Vec<KeyCode>> {
|
||||
let modifiers_ptr = unsafe { XGetModifierMapping(self.display) };
|
||||
let modifiers = unsafe { *modifiers_ptr };
|
||||
|
||||
let mut modifiers_codes = Vec::new();
|
||||
|
||||
for mod_index in 0..=7 {
|
||||
let mut modifier_codes = Vec::new();
|
||||
for mod_key in 0..modifiers.max_keypermod {
|
||||
let modifier_map = unsafe {
|
||||
slice::from_raw_parts(
|
||||
modifiers.modifiermap,
|
||||
(8 * modifiers.max_keypermod) as usize,
|
||||
)
|
||||
};
|
||||
let keycode = modifier_map[(mod_index * modifiers.max_keypermod + mod_key) as usize];
|
||||
if keycode != 0 {
|
||||
modifier_codes.push(keycode);
|
||||
}
|
||||
}
|
||||
modifiers_codes.push(modifier_codes);
|
||||
}
|
||||
|
||||
unsafe { XFreeModifiermap(modifiers_ptr) };
|
||||
|
||||
modifiers_codes
|
||||
}
|
||||
|
||||
fn render_key_combination(&self, original_records: &[KeyRecord]) -> Vec<KeyRecord> {
|
||||
let modifiers_codes = self.get_modifier_codes();
|
||||
let mut records = Vec::new();
|
||||
|
||||
let mut current_state = 0u32;
|
||||
for record in original_records {
|
||||
let mut current_record = *record;
|
||||
|
||||
// Render the state by applying the modifiers
|
||||
for (mod_index, modifier) in modifiers_codes.iter().enumerate() {
|
||||
if modifier.contains(&(record.main.code as u8)) {
|
||||
current_state |= 1 << mod_index;
|
||||
}
|
||||
}
|
||||
|
||||
current_record.main.state = current_state;
|
||||
records.push(current_record);
|
||||
}
|
||||
|
||||
records
|
||||
}
|
||||
|
||||
fn get_focused_window(&self) -> Window {
|
||||
let mut focused_window: Window = 0;
|
||||
let mut revert_to = 0;
|
||||
unsafe {
|
||||
XGetInputFocus(self.display, &mut focused_window, &mut revert_to);
|
||||
}
|
||||
focused_window
|
||||
}
|
||||
|
||||
fn send_key(&self, window: Window, record: &KeyPair, pressed: bool, delay_us: u32) {
|
||||
let root_window = unsafe { XDefaultRootWindow(self.display) };
|
||||
let mut event = XKeyEvent {
|
||||
display: self.display,
|
||||
keycode: record.code,
|
||||
state: record.state,
|
||||
window,
|
||||
root: root_window,
|
||||
same_screen: 1,
|
||||
time: 0,
|
||||
type_: if pressed { KeyPress } else { KeyRelease },
|
||||
x_root: 1,
|
||||
y_root: 1,
|
||||
x: 1,
|
||||
y: 1,
|
||||
subwindow: 0,
|
||||
serial: 0,
|
||||
send_event: 0,
|
||||
};
|
||||
unsafe {
|
||||
XSendEvent(self.display, window, 1, 0, &mut event);
|
||||
XFlush(self.display);
|
||||
}
|
||||
|
||||
if delay_us != 0 {
|
||||
unsafe {
|
||||
libc::usleep(delay_us);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn xtest_send_modifiers(&self, modmask: u32, pressed: bool) {
|
||||
let modifiers_codes = self.get_modifier_codes();
|
||||
for (mod_index, modifier_codes) in modifiers_codes.into_iter().enumerate() {
|
||||
if (modmask & (1 << mod_index)) != 0 {
|
||||
for keycode in modifier_codes {
|
||||
let is_press = if pressed { 1 } else { 0 };
|
||||
unsafe {
|
||||
XTestFakeKeyEvent(self.display, keycode as u32, is_press, 0);
|
||||
XSync(self.display, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn xtest_send_key(&self, record: &KeyPair, pressed: bool, delay_us: u32) {
|
||||
// If the key requires any modifier, we need to send those events
|
||||
if record.state != 0 {
|
||||
self.xtest_send_modifiers(record.state, pressed);
|
||||
}
|
||||
|
||||
let is_press = if pressed { 1 } else { 0 };
|
||||
unsafe {
|
||||
XTestFakeKeyEvent(self.display, record.code, is_press, 0);
|
||||
XSync(self.display, 0);
|
||||
XFlush(self.display);
|
||||
}
|
||||
|
||||
if delay_us != 0 {
|
||||
unsafe {
|
||||
libc::usleep(delay_us);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn xtest_release_all_keys(&self) {
|
||||
let mut keys: [u8; 32] = [0; 32];
|
||||
unsafe {
|
||||
XQueryKeymap(self.display, keys.as_mut_ptr());
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for i in 0..32 {
|
||||
// Only those that are pressed should be changed
|
||||
if keys[i] != 0 {
|
||||
for k in 0..8 {
|
||||
if (keys[i] & (1 << k)) != 0 {
|
||||
let key_code = i * 8 + k;
|
||||
unsafe {
|
||||
XTestFakeKeyEvent(self.display, key_code as u32, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for X11DefaultInjector {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
XCloseDisplay(self.display);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Injector for X11DefaultInjector {
|
||||
fn send_string(&self, string: &str, options: InjectionOptions) -> Result<()> {
|
||||
let focused_window = self.get_focused_window();
|
||||
|
||||
if options.disable_fast_inject {
|
||||
self.xtest_release_all_keys();
|
||||
}
|
||||
|
||||
// Compute all the key record sequence first to make sure a mapping is available
|
||||
let records: Result<Vec<KeyRecord>> = string
|
||||
.chars()
|
||||
.map(|c| c.to_string())
|
||||
.map(|char| {
|
||||
self
|
||||
.char_map
|
||||
.get(&char)
|
||||
.cloned()
|
||||
.ok_or_else(|| X11InjectorError::CharMapping(char).into())
|
||||
})
|
||||
.collect();
|
||||
|
||||
let delay_us = options.delay as u32 * 1000; // Convert to micro seconds
|
||||
|
||||
for record in records? {
|
||||
if options.disable_fast_inject {
|
||||
if let Some(deadkey) = &record.preceding_dead_key {
|
||||
self.xtest_send_key(deadkey, true, delay_us);
|
||||
self.xtest_send_key(deadkey, false, delay_us);
|
||||
}
|
||||
|
||||
self.xtest_send_key(&record.main, true, delay_us);
|
||||
self.xtest_send_key(&record.main, false, delay_us);
|
||||
} else {
|
||||
if let Some(deadkey) = &record.preceding_dead_key {
|
||||
self.send_key(focused_window, deadkey, true, delay_us);
|
||||
self.send_key(focused_window, deadkey, false, delay_us);
|
||||
}
|
||||
|
||||
self.send_key(focused_window, &record.main, true, delay_us);
|
||||
self.send_key(focused_window, &record.main, false, delay_us);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_keys(&self, keys: &[keys::Key], options: InjectionOptions) -> Result<()> {
|
||||
let focused_window = self.get_focused_window();
|
||||
|
||||
// Compute all the key record sequence first to make sure a mapping is available
|
||||
let syms = convert_to_sym_array(keys)?;
|
||||
let records = self.convert_to_record_array(&syms)?;
|
||||
|
||||
if options.disable_fast_inject {
|
||||
self.xtest_release_all_keys();
|
||||
}
|
||||
|
||||
let delay_us = options.delay as u32 * 1000; // Convert to micro seconds
|
||||
|
||||
for record in records {
|
||||
if options.disable_fast_inject {
|
||||
self.xtest_send_key(&record.main, true, delay_us);
|
||||
self.xtest_send_key(&record.main, false, delay_us);
|
||||
} else {
|
||||
self.send_key(focused_window, &record.main, true, delay_us);
|
||||
self.send_key(focused_window, &record.main, false, delay_us);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_key_combination(&self, keys: &[keys::Key], options: InjectionOptions) -> Result<()> {
|
||||
let focused_window = self.get_focused_window();
|
||||
|
||||
// Compute all the key record sequence first to make sure a mapping is available
|
||||
let syms = convert_to_sym_array(keys)?;
|
||||
let records = self.convert_to_record_array(&syms)?;
|
||||
|
||||
// Render the correct modifier mask for the given sequence
|
||||
let records = self.render_key_combination(&records);
|
||||
|
||||
if options.disable_fast_inject {
|
||||
self.xtest_release_all_keys();
|
||||
}
|
||||
|
||||
let delay_us = options.delay as u32 * 1000; // Convert to micro seconds
|
||||
|
||||
// First press the keys
|
||||
for record in records.iter() {
|
||||
if options.disable_fast_inject {
|
||||
self.xtest_send_key(&record.main, true, delay_us);
|
||||
} else {
|
||||
self.send_key(focused_window, &record.main, true, delay_us);
|
||||
}
|
||||
}
|
||||
|
||||
// Then release them
|
||||
for record in records.iter().rev() {
|
||||
if options.disable_fast_inject {
|
||||
self.xtest_send_key(&record.main, false, delay_us);
|
||||
} else {
|
||||
self.send_key(focused_window, &record.main, false, delay_us);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum X11InjectorError {
|
||||
#[error("failed to initialize x11 display")]
|
||||
Init(),
|
||||
|
||||
#[error("missing vkey mapping for char `{0}`")]
|
||||
CharMapping(String),
|
||||
|
||||
#[error("missing record mapping for sym `{0}`")]
|
||||
SymMapping(u64),
|
||||
}
|
|
@ -6,7 +6,7 @@ use std::{
|
|||
os::raw::{c_char, c_long, c_uint, c_ulong},
|
||||
};
|
||||
|
||||
use libc::c_int;
|
||||
use libc::{c_int, c_uchar};
|
||||
|
||||
pub enum Display {}
|
||||
pub type Window = u64;
|
||||
|
@ -20,7 +20,7 @@ pub const KeyPress: c_int = 2;
|
|||
#[allow(non_upper_case_globals)]
|
||||
pub const KeyRelease: c_int = 3;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(C)]
|
||||
pub struct XKeyEvent {
|
||||
pub type_: c_int,
|
||||
|
@ -40,7 +40,7 @@ pub struct XKeyEvent {
|
|||
pub same_screen: Bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(C)]
|
||||
pub struct XModifierKeymap {
|
||||
pub max_keypermod: c_int,
|
||||
|
@ -123,4 +123,6 @@ extern "C" {
|
|||
pub fn XFilterEvent(event: *mut XKeyEvent, window: c_ulong) -> c_int;
|
||||
pub fn XCloseIM(input_method: XIM) -> c_int;
|
||||
pub fn XFree(data: *mut c_void) -> c_int;
|
||||
pub fn XKeycodeToKeysym(display: *mut Display, keycode: c_uchar, index: c_int) -> c_ulong;
|
||||
pub fn XKeysymToString(keysym: c_ulong) -> *mut c_char;
|
||||
}
|
||||
|
|
|
@ -17,584 +17,89 @@
|
|||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use crate::Injector;
|
||||
|
||||
use anyhow::{bail, ensure, Result};
|
||||
use log::{error, warn};
|
||||
|
||||
mod default;
|
||||
mod ffi;
|
||||
mod xdotool;
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
ffi::{CStr, CString},
|
||||
os::raw::c_char,
|
||||
slice,
|
||||
};
|
||||
|
||||
use ffi::{
|
||||
Display, KeyCode, KeyPress, KeyRelease, KeySym, Window, XCloseDisplay, XDefaultRootWindow,
|
||||
XFlush, XFreeModifiermap, XGetInputFocus, XGetModifierMapping, XKeyEvent, XQueryKeymap,
|
||||
XSendEvent, XSync, XTestFakeKeyEvent,
|
||||
};
|
||||
use libc::c_void;
|
||||
use log::{debug, error};
|
||||
|
||||
use crate::{linux::raw_keys::convert_to_sym_array, x11::ffi::Xutf8LookupString};
|
||||
use anyhow::{bail, Result};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{keys, InjectionOptions, Injector};
|
||||
|
||||
use self::ffi::{
|
||||
XCloseIM, XCreateIC, XDestroyIC, XFilterEvent, XFree, XIMPreeditNothing, XIMStatusNothing,
|
||||
XNClientWindow_0, XNInputStyle_0, XOpenIM, XmbResetIC, XIC,
|
||||
};
|
||||
|
||||
// Offset between evdev keycodes (where KEY_ESCAPE is 1), and the evdev XKB
|
||||
// keycode set (where ESC is 9).
|
||||
const EVDEV_OFFSET: u32 = 8;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct KeyPair {
|
||||
// Keycode
|
||||
code: u32,
|
||||
// Modifier state which combined with the code produces the char
|
||||
// This is a bit mask:
|
||||
state: u32,
|
||||
pub struct X11ProxyInjector {
|
||||
default_injector: Option<default::X11DefaultInjector>,
|
||||
xdotool_injector: Option<xdotool::X11XDOToolInjector>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct KeyRecord {
|
||||
main: KeyPair,
|
||||
|
||||
// Under some keyboard layouts (de, es), a deadkey
|
||||
// press might be needed to generate the right char
|
||||
preceding_dead_key: Option<KeyPair>,
|
||||
}
|
||||
|
||||
type CharMap = HashMap<String, KeyRecord>;
|
||||
type SymMap = HashMap<KeySym, KeyRecord>;
|
||||
|
||||
pub struct X11Injector {
|
||||
display: *mut Display,
|
||||
|
||||
char_map: CharMap,
|
||||
sym_map: SymMap,
|
||||
}
|
||||
|
||||
#[allow(clippy::new_without_default)]
|
||||
impl X11Injector {
|
||||
impl X11ProxyInjector {
|
||||
pub fn new() -> Result<Self> {
|
||||
// Necessary to properly handle non-ascii chars
|
||||
let empty_string = CString::new("")?;
|
||||
unsafe {
|
||||
libc::setlocale(libc::LC_ALL, empty_string.as_ptr());
|
||||
let default_injector = match default::X11DefaultInjector::new() {
|
||||
Ok(injector) => Some(injector),
|
||||
Err(err) => {
|
||||
error!("X11DefaultInjector could not be initialized: {:?}", err);
|
||||
warn!("falling back to xdotool injector");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let xdotool_injector = match xdotool::X11XDOToolInjector::new() {
|
||||
Ok(injector) => Some(injector),
|
||||
Err(err) => {
|
||||
error!("X11XDOToolInjector could not be initialized: {:?}", err);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
if default_injector.is_none() && xdotool_injector.is_none() {
|
||||
bail!("unable to initialize injectors, neither the default or xdotool fallback could be initialized");
|
||||
}
|
||||
|
||||
let display = unsafe { ffi::XOpenDisplay(std::ptr::null()) };
|
||||
if display.is_null() {
|
||||
return Err(X11InjectorError::Init().into());
|
||||
}
|
||||
|
||||
let (char_map, sym_map) = Self::generate_maps(display)?;
|
||||
|
||||
Ok(Self {
|
||||
display,
|
||||
char_map,
|
||||
sym_map,
|
||||
Ok(X11ProxyInjector {
|
||||
default_injector,
|
||||
xdotool_injector,
|
||||
})
|
||||
}
|
||||
|
||||
fn generate_maps(display: *mut Display) -> Result<(CharMap, SymMap)> {
|
||||
debug!("generating key maps");
|
||||
fn get_active_injector(&self, options: &crate::InjectionOptions) -> Result<&dyn Injector> {
|
||||
ensure!(
|
||||
self.default_injector.is_some() || self.xdotool_injector.is_some(),
|
||||
"unable to get active injector, neither default or xdotool fallback are available."
|
||||
);
|
||||
|
||||
let mut char_map = HashMap::new();
|
||||
let mut sym_map = HashMap::new();
|
||||
|
||||
let input_method = unsafe {
|
||||
XOpenIM(
|
||||
display,
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
if input_method.is_null() {
|
||||
bail!("could not open input method");
|
||||
}
|
||||
let _im_guard = scopeguard::guard((), |_| {
|
||||
unsafe { XCloseIM(input_method) };
|
||||
});
|
||||
|
||||
let input_context = unsafe {
|
||||
XCreateIC(
|
||||
input_method,
|
||||
XNInputStyle_0.as_ptr(),
|
||||
XIMPreeditNothing | XIMStatusNothing,
|
||||
XNClientWindow_0.as_ptr(),
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
if input_context.is_null() {
|
||||
bail!("could not open input context");
|
||||
}
|
||||
let _ic_guard = scopeguard::guard((), |_| {
|
||||
unsafe { XDestroyIC(input_context) };
|
||||
});
|
||||
|
||||
let deadkeys = Self::find_deadkeys(display, &input_context)?;
|
||||
|
||||
// Cycle through all state/code combinations to populate the reverse lookup tables
|
||||
for key_code in 0..256u32 {
|
||||
for modifier_state in 0..256u32 {
|
||||
for dead_key in deadkeys.iter() {
|
||||
let code_with_offset = key_code + EVDEV_OFFSET;
|
||||
|
||||
let preceding_dead_key = if let Some(dead_key) = dead_key {
|
||||
let mut dead_key_event = XKeyEvent {
|
||||
display,
|
||||
keycode: dead_key.code,
|
||||
state: dead_key.state,
|
||||
|
||||
// These might not even need to be filled
|
||||
window: 0,
|
||||
root: 0,
|
||||
same_screen: 1,
|
||||
time: 0,
|
||||
type_: KeyPress,
|
||||
x_root: 1,
|
||||
y_root: 1,
|
||||
x: 1,
|
||||
y: 1,
|
||||
subwindow: 0,
|
||||
serial: 0,
|
||||
send_event: 0,
|
||||
};
|
||||
|
||||
unsafe { XFilterEvent(&mut dead_key_event, 0) };
|
||||
|
||||
Some(*dead_key)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut key_event = XKeyEvent {
|
||||
display,
|
||||
keycode: code_with_offset,
|
||||
state: modifier_state,
|
||||
|
||||
// These might not even need to be filled
|
||||
window: 0,
|
||||
root: 0,
|
||||
same_screen: 1,
|
||||
time: 0,
|
||||
type_: KeyPress,
|
||||
x_root: 1,
|
||||
y_root: 1,
|
||||
x: 1,
|
||||
y: 1,
|
||||
subwindow: 0,
|
||||
serial: 0,
|
||||
send_event: 0,
|
||||
};
|
||||
|
||||
unsafe { XFilterEvent(&mut key_event, 0) };
|
||||
let mut sym: KeySym = 0;
|
||||
let mut buffer: [c_char; 10] = [0; 10];
|
||||
|
||||
let result = unsafe {
|
||||
Xutf8LookupString(
|
||||
input_context,
|
||||
&mut key_event,
|
||||
buffer.as_mut_ptr(),
|
||||
(buffer.len() - 1) as i32,
|
||||
&mut sym,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
let key_record = KeyRecord {
|
||||
main: KeyPair {
|
||||
code: code_with_offset,
|
||||
state: modifier_state,
|
||||
},
|
||||
preceding_dead_key,
|
||||
};
|
||||
|
||||
// Keysym was found
|
||||
if sym != 0 {
|
||||
sym_map.entry(sym).or_insert(key_record);
|
||||
};
|
||||
|
||||
// Char was found
|
||||
if result > 0 {
|
||||
let raw_string = unsafe { CStr::from_ptr(buffer.as_ptr()) };
|
||||
let string = raw_string.to_string_lossy().to_string();
|
||||
char_map.entry(string).or_insert(key_record);
|
||||
};
|
||||
|
||||
// We need to reset the context state to prevent
|
||||
// deadkeys effect to propagate to the next combination
|
||||
let _reset = unsafe { XmbResetIC(input_context) };
|
||||
unsafe { XFree(_reset as *mut c_void) };
|
||||
}
|
||||
if options.x11_use_xdotool_fallback {
|
||||
if let Some(xdotool_injector) = self.xdotool_injector.as_ref() {
|
||||
return Ok(xdotool_injector);
|
||||
} else if let Some(default_injector) = self.default_injector.as_ref() {
|
||||
return Ok(default_injector);
|
||||
}
|
||||
} else if let Some(default_injector) = self.default_injector.as_ref() {
|
||||
return Ok(default_injector);
|
||||
} else if let Some(xdotool_injector) = self.xdotool_injector.as_ref() {
|
||||
return Ok(xdotool_injector);
|
||||
}
|
||||
|
||||
debug!("Populated char_map with {} symbols", char_map.len());
|
||||
debug!("Populated sym_map with {} symbols", sym_map.len());
|
||||
debug!("Detected {} dead key combinations", deadkeys.len());
|
||||
|
||||
Ok((char_map, sym_map))
|
||||
}
|
||||
|
||||
fn find_deadkeys(display: *mut Display, input_context: &XIC) -> Result<Vec<Option<KeyPair>>> {
|
||||
let mut deadkeys = vec![None];
|
||||
let mut seen_keysyms: HashSet<KeySym> = HashSet::new();
|
||||
|
||||
// Cycle through all state/code combinations to populate the reverse lookup tables
|
||||
for key_code in 0..256u32 {
|
||||
for modifier_state in 0..256u32 {
|
||||
let code_with_offset = key_code + EVDEV_OFFSET;
|
||||
let mut event = XKeyEvent {
|
||||
display,
|
||||
keycode: code_with_offset,
|
||||
state: modifier_state,
|
||||
|
||||
// These might not even need to be filled
|
||||
window: 0,
|
||||
root: 0,
|
||||
same_screen: 1,
|
||||
time: 0,
|
||||
type_: KeyPress,
|
||||
x_root: 1,
|
||||
y_root: 1,
|
||||
x: 1,
|
||||
y: 1,
|
||||
subwindow: 0,
|
||||
serial: 0,
|
||||
send_event: 0,
|
||||
};
|
||||
|
||||
let filter = unsafe { XFilterEvent(&mut event, 0) };
|
||||
if filter == 1 {
|
||||
let mut sym: KeySym = 0;
|
||||
let mut buffer: [c_char; 10] = [0; 10];
|
||||
|
||||
unsafe {
|
||||
Xutf8LookupString(
|
||||
*input_context,
|
||||
&mut event,
|
||||
buffer.as_mut_ptr(),
|
||||
(buffer.len() - 1) as i32,
|
||||
&mut sym,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
};
|
||||
|
||||
if sym != 0 && !seen_keysyms.contains(&sym) {
|
||||
let key_record = KeyPair {
|
||||
code: code_with_offset,
|
||||
state: modifier_state,
|
||||
};
|
||||
deadkeys.push(Some(key_record));
|
||||
seen_keysyms.insert(sym);
|
||||
}
|
||||
}
|
||||
|
||||
let _reset = unsafe { XmbResetIC(*input_context) };
|
||||
unsafe { XFree(_reset as *mut c_void) };
|
||||
}
|
||||
}
|
||||
|
||||
Ok(deadkeys)
|
||||
}
|
||||
|
||||
fn convert_to_record_array(&self, syms: &[KeySym]) -> Result<Vec<KeyRecord>> {
|
||||
syms
|
||||
.iter()
|
||||
.map(|sym| {
|
||||
self
|
||||
.sym_map
|
||||
.get(sym)
|
||||
.cloned()
|
||||
.ok_or_else(|| X11InjectorError::SymMapping(*sym).into())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
// This method was inspired by the wonderful xdotool by Jordan Sissel
|
||||
// https://github.com/jordansissel/xdotool
|
||||
fn get_modifier_codes(&self) -> Vec<Vec<KeyCode>> {
|
||||
let modifiers_ptr = unsafe { XGetModifierMapping(self.display) };
|
||||
let modifiers = unsafe { *modifiers_ptr };
|
||||
|
||||
let mut modifiers_codes = Vec::new();
|
||||
|
||||
for mod_index in 0..=7 {
|
||||
let mut modifier_codes = Vec::new();
|
||||
for mod_key in 0..modifiers.max_keypermod {
|
||||
let modifier_map = unsafe {
|
||||
slice::from_raw_parts(
|
||||
modifiers.modifiermap,
|
||||
(8 * modifiers.max_keypermod) as usize,
|
||||
)
|
||||
};
|
||||
let keycode = modifier_map[(mod_index * modifiers.max_keypermod + mod_key) as usize];
|
||||
if keycode != 0 {
|
||||
modifier_codes.push(keycode);
|
||||
}
|
||||
}
|
||||
modifiers_codes.push(modifier_codes);
|
||||
}
|
||||
|
||||
unsafe { XFreeModifiermap(modifiers_ptr) };
|
||||
|
||||
modifiers_codes
|
||||
}
|
||||
|
||||
fn render_key_combination(&self, original_records: &[KeyRecord]) -> Vec<KeyRecord> {
|
||||
let modifiers_codes = self.get_modifier_codes();
|
||||
let mut records = Vec::new();
|
||||
|
||||
let mut current_state = 0u32;
|
||||
for record in original_records {
|
||||
let mut current_record = *record;
|
||||
|
||||
// Render the state by applying the modifiers
|
||||
for (mod_index, modifier) in modifiers_codes.iter().enumerate() {
|
||||
if modifier.contains(&(record.main.code as u8)) {
|
||||
current_state |= 1 << mod_index;
|
||||
}
|
||||
}
|
||||
|
||||
current_record.main.state = current_state;
|
||||
records.push(current_record);
|
||||
}
|
||||
|
||||
records
|
||||
}
|
||||
|
||||
fn get_focused_window(&self) -> Window {
|
||||
let mut focused_window: Window = 0;
|
||||
let mut revert_to = 0;
|
||||
unsafe {
|
||||
XGetInputFocus(self.display, &mut focused_window, &mut revert_to);
|
||||
}
|
||||
focused_window
|
||||
}
|
||||
|
||||
fn send_key(&self, window: Window, record: &KeyPair, pressed: bool, delay_us: u32) {
|
||||
let root_window = unsafe { XDefaultRootWindow(self.display) };
|
||||
let mut event = XKeyEvent {
|
||||
display: self.display,
|
||||
keycode: record.code,
|
||||
state: record.state,
|
||||
window,
|
||||
root: root_window,
|
||||
same_screen: 1,
|
||||
time: 0,
|
||||
type_: if pressed { KeyPress } else { KeyRelease },
|
||||
x_root: 1,
|
||||
y_root: 1,
|
||||
x: 1,
|
||||
y: 1,
|
||||
subwindow: 0,
|
||||
serial: 0,
|
||||
send_event: 0,
|
||||
};
|
||||
unsafe {
|
||||
XSendEvent(self.display, window, 1, 0, &mut event);
|
||||
XFlush(self.display);
|
||||
}
|
||||
|
||||
if delay_us != 0 {
|
||||
unsafe {
|
||||
libc::usleep(delay_us);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn xtest_send_modifiers(&self, modmask: u32, pressed: bool) {
|
||||
let modifiers_codes = self.get_modifier_codes();
|
||||
for (mod_index, modifier_codes) in modifiers_codes.into_iter().enumerate() {
|
||||
if (modmask & (1 << mod_index)) != 0 {
|
||||
for keycode in modifier_codes {
|
||||
let is_press = if pressed { 1 } else { 0 };
|
||||
unsafe {
|
||||
XTestFakeKeyEvent(self.display, keycode as u32, is_press, 0);
|
||||
XSync(self.display, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn xtest_send_key(&self, record: &KeyPair, pressed: bool, delay_us: u32) {
|
||||
// If the key requires any modifier, we need to send those events
|
||||
if record.state != 0 {
|
||||
self.xtest_send_modifiers(record.state, pressed);
|
||||
}
|
||||
|
||||
let is_press = if pressed { 1 } else { 0 };
|
||||
unsafe {
|
||||
XTestFakeKeyEvent(self.display, record.code, is_press, 0);
|
||||
XSync(self.display, 0);
|
||||
XFlush(self.display);
|
||||
}
|
||||
|
||||
if delay_us != 0 {
|
||||
unsafe {
|
||||
libc::usleep(delay_us);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn xtest_release_all_keys(&self) {
|
||||
let mut keys: [u8; 32] = [0; 32];
|
||||
unsafe {
|
||||
XQueryKeymap(self.display, keys.as_mut_ptr());
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for i in 0..32 {
|
||||
// Only those that are pressed should be changed
|
||||
if keys[i] != 0 {
|
||||
for k in 0..8 {
|
||||
if (keys[i] & (1 << k)) != 0 {
|
||||
let key_code = i * 8 + k;
|
||||
unsafe {
|
||||
XTestFakeKeyEvent(self.display, key_code as u32, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for X11Injector {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
XCloseDisplay(self.display);
|
||||
}
|
||||
impl Injector for X11ProxyInjector {
|
||||
fn send_string(&self, string: &str, options: crate::InjectionOptions) -> Result<()> {
|
||||
self
|
||||
.get_active_injector(&options)?
|
||||
.send_string(string, options)
|
||||
}
|
||||
|
||||
fn send_keys(&self, keys: &[crate::keys::Key], options: crate::InjectionOptions) -> Result<()> {
|
||||
self.get_active_injector(&options)?.send_keys(keys, options)
|
||||
}
|
||||
|
||||
fn send_key_combination(
|
||||
&self,
|
||||
keys: &[crate::keys::Key],
|
||||
options: crate::InjectionOptions,
|
||||
) -> Result<()> {
|
||||
self
|
||||
.get_active_injector(&options)?
|
||||
.send_key_combination(keys, options)
|
||||
}
|
||||
}
|
||||
|
||||
impl Injector for X11Injector {
|
||||
fn send_string(&self, string: &str, options: InjectionOptions) -> Result<()> {
|
||||
let focused_window = self.get_focused_window();
|
||||
|
||||
if options.disable_fast_inject {
|
||||
self.xtest_release_all_keys();
|
||||
}
|
||||
|
||||
// Compute all the key record sequence first to make sure a mapping is available
|
||||
let records: Result<Vec<KeyRecord>> = string
|
||||
.chars()
|
||||
.map(|c| c.to_string())
|
||||
.map(|char| {
|
||||
self
|
||||
.char_map
|
||||
.get(&char)
|
||||
.cloned()
|
||||
.ok_or_else(|| X11InjectorError::CharMapping(char).into())
|
||||
})
|
||||
.collect();
|
||||
|
||||
let delay_us = options.delay as u32 * 1000; // Convert to micro seconds
|
||||
|
||||
for record in records? {
|
||||
if options.disable_fast_inject {
|
||||
if let Some(deadkey) = &record.preceding_dead_key {
|
||||
self.xtest_send_key(deadkey, true, delay_us);
|
||||
self.xtest_send_key(deadkey, false, delay_us);
|
||||
}
|
||||
|
||||
self.xtest_send_key(&record.main, true, delay_us);
|
||||
self.xtest_send_key(&record.main, false, delay_us);
|
||||
} else {
|
||||
if let Some(deadkey) = &record.preceding_dead_key {
|
||||
self.send_key(focused_window, deadkey, true, delay_us);
|
||||
self.send_key(focused_window, deadkey, false, delay_us);
|
||||
}
|
||||
|
||||
self.send_key(focused_window, &record.main, true, delay_us);
|
||||
self.send_key(focused_window, &record.main, false, delay_us);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_keys(&self, keys: &[keys::Key], options: InjectionOptions) -> Result<()> {
|
||||
let focused_window = self.get_focused_window();
|
||||
|
||||
// Compute all the key record sequence first to make sure a mapping is available
|
||||
let syms = convert_to_sym_array(keys)?;
|
||||
let records = self.convert_to_record_array(&syms)?;
|
||||
|
||||
if options.disable_fast_inject {
|
||||
self.xtest_release_all_keys();
|
||||
}
|
||||
|
||||
let delay_us = options.delay as u32 * 1000; // Convert to micro seconds
|
||||
|
||||
for record in records {
|
||||
if options.disable_fast_inject {
|
||||
self.xtest_send_key(&record.main, true, delay_us);
|
||||
self.xtest_send_key(&record.main, false, delay_us);
|
||||
} else {
|
||||
self.send_key(focused_window, &record.main, true, delay_us);
|
||||
self.send_key(focused_window, &record.main, false, delay_us);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_key_combination(&self, keys: &[keys::Key], options: InjectionOptions) -> Result<()> {
|
||||
let focused_window = self.get_focused_window();
|
||||
|
||||
// Compute all the key record sequence first to make sure a mapping is available
|
||||
let syms = convert_to_sym_array(keys)?;
|
||||
let records = self.convert_to_record_array(&syms)?;
|
||||
|
||||
// Render the correct modifier mask for the given sequence
|
||||
let records = self.render_key_combination(&records);
|
||||
|
||||
if options.disable_fast_inject {
|
||||
self.xtest_release_all_keys();
|
||||
}
|
||||
|
||||
let delay_us = options.delay as u32 * 1000; // Convert to micro seconds
|
||||
|
||||
// First press the keys
|
||||
for record in records.iter() {
|
||||
if options.disable_fast_inject {
|
||||
self.xtest_send_key(&record.main, true, delay_us);
|
||||
} else {
|
||||
self.send_key(focused_window, &record.main, true, delay_us);
|
||||
}
|
||||
}
|
||||
|
||||
// Then release them
|
||||
for record in records.iter().rev() {
|
||||
if options.disable_fast_inject {
|
||||
self.xtest_send_key(&record.main, false, delay_us);
|
||||
} else {
|
||||
self.send_key(focused_window, &record.main, false, delay_us);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum X11InjectorError {
|
||||
#[error("failed to initialize x11 display")]
|
||||
Init(),
|
||||
|
||||
#[error("missing vkey mapping for char `{0}`")]
|
||||
CharMapping(String),
|
||||
|
||||
#[error("missing record mapping for sym `{0}`")]
|
||||
SymMapping(u64),
|
||||
}
|
||||
|
|
2
espanso-inject/src/x11/xdotool/README.md
Normal file
2
espanso-inject/src/x11/xdotool/README.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
This is a fallback injection module that relies on the awesome xdotool project:
|
||||
https://github.com/jordansissel/xdotool
|
61
espanso-inject/src/x11/xdotool/ffi.rs
Normal file
61
espanso-inject/src/x11/xdotool/ffi.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
use libc::{c_char, c_int, c_long, useconds_t, wchar_t};
|
||||
|
||||
use crate::x11::ffi::{Display, Window};
|
||||
|
||||
#[repr(C)]
|
||||
pub struct charcodemap_t {
|
||||
pub key: wchar_t,
|
||||
pub code: c_char,
|
||||
pub symbol: c_long,
|
||||
pub group: c_int,
|
||||
pub modmask: c_int,
|
||||
pub needs_binding: c_int,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct xdo_t {
|
||||
pub xdpy: *mut Display,
|
||||
pub display_name: *const c_char,
|
||||
pub charcodes: *const charcodemap_t,
|
||||
pub charcodes_len: c_int,
|
||||
pub keycode_high: c_int,
|
||||
pub keycode_low: c_int,
|
||||
pub keysyms_per_keycode: c_int,
|
||||
pub close_display_when_freed: c_int,
|
||||
pub quiet: c_int,
|
||||
pub debug: c_int,
|
||||
pub features_mask: c_int,
|
||||
}
|
||||
|
||||
pub const CURRENTWINDOW: u64 = 0;
|
||||
|
||||
#[link(name = "xdotoolvendor", kind = "static")]
|
||||
extern "C" {
|
||||
pub fn xdo_new(display: *const c_char) -> *mut xdo_t;
|
||||
pub fn xdo_free(xdo: *const xdo_t);
|
||||
pub fn xdo_enter_text_window(
|
||||
xdo: *const xdo_t,
|
||||
window: Window,
|
||||
string: *const c_char,
|
||||
delay: useconds_t,
|
||||
);
|
||||
pub fn xdo_send_keysequence_window(
|
||||
xdo: *const xdo_t,
|
||||
window: Window,
|
||||
keysequence: *const c_char,
|
||||
delay: useconds_t,
|
||||
);
|
||||
pub fn fast_send_event(xdo: *const xdo_t, window: Window, keycode: c_int, pressed: c_int);
|
||||
pub fn fast_enter_text_window(
|
||||
xdo: *const xdo_t,
|
||||
window: Window,
|
||||
string: *const c_char,
|
||||
delay: useconds_t,
|
||||
);
|
||||
pub fn fast_send_keysequence_window(
|
||||
xdo: *const xdo_t,
|
||||
window: Window,
|
||||
keysequence: *const c_char,
|
||||
delay: useconds_t,
|
||||
);
|
||||
}
|
348
espanso-inject/src/x11/xdotool/mod.rs
Normal file
348
espanso-inject/src/x11/xdotool/mod.rs
Normal file
|
@ -0,0 +1,348 @@
|
|||
/*
|
||||
* This file is part of espanso.
|
||||
*
|
||||
* Copyright (C) 2019-2022 Federico Terzi
|
||||
*
|
||||
* espanso is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* espanso is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::{
|
||||
convert::TryInto,
|
||||
ffi::{CStr, CString},
|
||||
};
|
||||
|
||||
use crate::Injector;
|
||||
use anyhow::{bail, Context, Result};
|
||||
use log::debug;
|
||||
|
||||
mod ffi;
|
||||
use self::ffi::{fast_send_keysequence_window, xdo_send_keysequence_window, xdo_t, CURRENTWINDOW};
|
||||
|
||||
use super::ffi::{
|
||||
Display, Window, XGetInputFocus, XKeycodeToKeysym, XKeysymToString, XQueryKeymap,
|
||||
XTestFakeKeyEvent,
|
||||
};
|
||||
|
||||
pub struct X11XDOToolInjector {
|
||||
xdo: *const xdo_t,
|
||||
}
|
||||
|
||||
impl X11XDOToolInjector {
|
||||
pub fn new() -> Result<Self> {
|
||||
let xdo = unsafe { ffi::xdo_new(std::ptr::null()) };
|
||||
if xdo.is_null() {
|
||||
bail!("unable to initialize xdo_t instance");
|
||||
}
|
||||
|
||||
debug!("initialized xdo_t object");
|
||||
|
||||
Ok(Self { xdo })
|
||||
}
|
||||
|
||||
fn xfake_release_all_keys(&self) {
|
||||
let mut keys: [u8; 32] = [0; 32];
|
||||
unsafe {
|
||||
XQueryKeymap((*self.xdo).xdpy, keys.as_mut_ptr());
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for i in 0..32 {
|
||||
// Only those that are pressed should be changed
|
||||
if keys[i] != 0 {
|
||||
for k in 0..8 {
|
||||
if (keys[i] & (1 << k)) != 0 {
|
||||
let key_code = i * 8 + k;
|
||||
unsafe {
|
||||
XTestFakeKeyEvent((*self.xdo).xdpy, key_code as u32, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_focused_window(&self) -> Window {
|
||||
let mut focused_window: Window = 0;
|
||||
let mut revert_to = 0;
|
||||
unsafe {
|
||||
XGetInputFocus((*self.xdo).xdpy, &mut focused_window, &mut revert_to);
|
||||
}
|
||||
focused_window
|
||||
}
|
||||
|
||||
fn xfake_send_string(
|
||||
&self,
|
||||
string: &str,
|
||||
options: crate::InjectionOptions,
|
||||
) -> anyhow::Result<()> {
|
||||
// It may happen that when an expansion is triggered, some keys are still pressed.
|
||||
// This causes a problem if the expanded match contains that character, as the injection
|
||||
// will not be able to register that keypress (as it is already pressed).
|
||||
// To solve the problem, before an expansion we get which keys are currently pressed
|
||||
// and inject a key_release event so that they can be further registered.
|
||||
self.xfake_release_all_keys();
|
||||
|
||||
let c_string = CString::new(string).context("unable to create CString")?;
|
||||
let delay = options.delay * 1000;
|
||||
|
||||
unsafe {
|
||||
ffi::xdo_enter_text_window(
|
||||
self.xdo,
|
||||
CURRENTWINDOW,
|
||||
c_string.as_ptr(),
|
||||
delay.try_into().unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fast_release_all_keys(&self) {
|
||||
let mut keys: [u8; 32] = [0; 32];
|
||||
unsafe {
|
||||
XQueryKeymap((*self.xdo).xdpy, keys.as_mut_ptr());
|
||||
}
|
||||
|
||||
let focused_window = self.get_focused_window();
|
||||
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
for i in 0..32 {
|
||||
// Only those that are pressed should be changed
|
||||
if keys[i] != 0 {
|
||||
for k in 0..8 {
|
||||
if (keys[i] & (1 << k)) != 0 {
|
||||
let key_code = i * 8 + k;
|
||||
unsafe {
|
||||
ffi::fast_send_event(self.xdo, focused_window, key_code.try_into().unwrap(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fast_send_string(&self, string: &str, options: crate::InjectionOptions) -> anyhow::Result<()> {
|
||||
// It may happen that when an expansion is triggered, some keys are still pressed.
|
||||
// This causes a problem if the expanded match contains that character, as the injection
|
||||
// will not be able to register that keypress (as it is already pressed).
|
||||
// To solve the problem, before an expansion we get which keys are currently pressed
|
||||
// and inject a key_release event so that they can be further registered.
|
||||
self.fast_release_all_keys();
|
||||
|
||||
let c_string = CString::new(string).context("unable to create CString")?;
|
||||
let delay = options.delay * 1000;
|
||||
|
||||
unsafe {
|
||||
ffi::fast_enter_text_window(
|
||||
self.xdo,
|
||||
self.get_focused_window(),
|
||||
c_string.as_ptr(),
|
||||
delay.try_into().unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Injector for X11XDOToolInjector {
|
||||
fn send_string(&self, string: &str, options: crate::InjectionOptions) -> anyhow::Result<()> {
|
||||
if options.disable_fast_inject {
|
||||
self.xfake_send_string(string, options)
|
||||
} else {
|
||||
self.fast_send_string(string, options)
|
||||
}
|
||||
}
|
||||
|
||||
fn send_keys(
|
||||
&self,
|
||||
keys: &[crate::keys::Key],
|
||||
options: crate::InjectionOptions,
|
||||
) -> anyhow::Result<()> {
|
||||
let key_syms: Vec<String> = keys
|
||||
.iter()
|
||||
.filter_map(|key| unsafe { convert_key_to_keysym((*self.xdo).xdpy, key) })
|
||||
.collect();
|
||||
|
||||
let delay = options.delay * 1000;
|
||||
|
||||
for key in key_syms {
|
||||
let c_str = CString::new(key).context("unable to generate CString")?;
|
||||
|
||||
if options.disable_fast_inject {
|
||||
unsafe {
|
||||
xdo_send_keysequence_window(
|
||||
self.xdo,
|
||||
CURRENTWINDOW,
|
||||
c_str.as_ptr(),
|
||||
delay.try_into().unwrap(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
fast_send_keysequence_window(
|
||||
self.xdo,
|
||||
self.get_focused_window(),
|
||||
c_str.as_ptr(),
|
||||
delay.try_into().unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_key_combination(
|
||||
&self,
|
||||
keys: &[crate::keys::Key],
|
||||
options: crate::InjectionOptions,
|
||||
) -> anyhow::Result<()> {
|
||||
let key_syms: Vec<String> = keys
|
||||
.iter()
|
||||
.filter_map(|key| unsafe { convert_key_to_keysym((*self.xdo).xdpy, key) })
|
||||
.collect();
|
||||
let key_combination = key_syms.join("+");
|
||||
|
||||
let delay = options.delay * 1000;
|
||||
|
||||
let c_key_combination = CString::new(key_combination).context("unable to generate CString")?;
|
||||
|
||||
if options.disable_fast_inject {
|
||||
unsafe {
|
||||
xdo_send_keysequence_window(
|
||||
self.xdo,
|
||||
CURRENTWINDOW,
|
||||
c_key_combination.as_ptr(),
|
||||
delay.try_into().unwrap(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
unsafe {
|
||||
fast_send_keysequence_window(
|
||||
self.xdo,
|
||||
self.get_focused_window(),
|
||||
c_key_combination.as_ptr(),
|
||||
delay.try_into().unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for X11XDOToolInjector {
|
||||
fn drop(&mut self) {
|
||||
unsafe { ffi::xdo_free(self.xdo) }
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_key_to_keysym(display: *mut Display, key: &crate::keys::Key) -> Option<String> {
|
||||
match key {
|
||||
crate::keys::Key::Alt => Some("Alt_L".to_string()),
|
||||
crate::keys::Key::CapsLock => Some("Caps_Lock".to_string()),
|
||||
crate::keys::Key::Control => Some("Control_L".to_string()),
|
||||
crate::keys::Key::Meta => Some("Meta_L".to_string()),
|
||||
crate::keys::Key::NumLock => Some("Num_Lock".to_string()),
|
||||
crate::keys::Key::Shift => Some("Shift_L".to_string()),
|
||||
crate::keys::Key::Enter => Some("Return".to_string()),
|
||||
crate::keys::Key::Tab => Some("Tab".to_string()),
|
||||
crate::keys::Key::Space => Some("space".to_string()),
|
||||
crate::keys::Key::ArrowDown => Some("downarrow".to_string()),
|
||||
crate::keys::Key::ArrowLeft => Some("leftarrow".to_string()),
|
||||
crate::keys::Key::ArrowRight => Some("rightarrow".to_string()),
|
||||
crate::keys::Key::ArrowUp => Some("uparrow".to_string()),
|
||||
crate::keys::Key::End => Some("End".to_string()),
|
||||
crate::keys::Key::Home => Some("Home".to_string()),
|
||||
crate::keys::Key::PageDown => Some("Page_Down".to_string()),
|
||||
crate::keys::Key::PageUp => Some("Page_Up".to_string()),
|
||||
crate::keys::Key::Escape => Some("Escape".to_string()),
|
||||
crate::keys::Key::Backspace => Some("BackSpace".to_string()),
|
||||
crate::keys::Key::Insert => Some("Insert".to_string()),
|
||||
crate::keys::Key::Delete => Some("Delete".to_string()),
|
||||
crate::keys::Key::F1 => Some("F1".to_string()),
|
||||
crate::keys::Key::F2 => Some("F2".to_string()),
|
||||
crate::keys::Key::F3 => Some("F3".to_string()),
|
||||
crate::keys::Key::F4 => Some("F4".to_string()),
|
||||
crate::keys::Key::F5 => Some("F5".to_string()),
|
||||
crate::keys::Key::F6 => Some("F6".to_string()),
|
||||
crate::keys::Key::F7 => Some("F7".to_string()),
|
||||
crate::keys::Key::F8 => Some("F8".to_string()),
|
||||
crate::keys::Key::F9 => Some("F9".to_string()),
|
||||
crate::keys::Key::F10 => Some("F10".to_string()),
|
||||
crate::keys::Key::F11 => Some("F11".to_string()),
|
||||
crate::keys::Key::F12 => Some("F12".to_string()),
|
||||
crate::keys::Key::F13 => Some("F13".to_string()),
|
||||
crate::keys::Key::F14 => Some("F14".to_string()),
|
||||
crate::keys::Key::F15 => Some("F15".to_string()),
|
||||
crate::keys::Key::F16 => Some("F16".to_string()),
|
||||
crate::keys::Key::F17 => Some("F17".to_string()),
|
||||
crate::keys::Key::F18 => Some("F18".to_string()),
|
||||
crate::keys::Key::F19 => Some("F19".to_string()),
|
||||
crate::keys::Key::F20 => Some("F20".to_string()),
|
||||
crate::keys::Key::A => Some("a".to_string()),
|
||||
crate::keys::Key::B => Some("b".to_string()),
|
||||
crate::keys::Key::C => Some("c".to_string()),
|
||||
crate::keys::Key::D => Some("d".to_string()),
|
||||
crate::keys::Key::E => Some("e".to_string()),
|
||||
crate::keys::Key::F => Some("f".to_string()),
|
||||
crate::keys::Key::G => Some("g".to_string()),
|
||||
crate::keys::Key::H => Some("h".to_string()),
|
||||
crate::keys::Key::I => Some("i".to_string()),
|
||||
crate::keys::Key::J => Some("j".to_string()),
|
||||
crate::keys::Key::K => Some("k".to_string()),
|
||||
crate::keys::Key::L => Some("l".to_string()),
|
||||
crate::keys::Key::M => Some("m".to_string()),
|
||||
crate::keys::Key::N => Some("n".to_string()),
|
||||
crate::keys::Key::O => Some("o".to_string()),
|
||||
crate::keys::Key::P => Some("p".to_string()),
|
||||
crate::keys::Key::Q => Some("q".to_string()),
|
||||
crate::keys::Key::R => Some("r".to_string()),
|
||||
crate::keys::Key::S => Some("s".to_string()),
|
||||
crate::keys::Key::T => Some("t".to_string()),
|
||||
crate::keys::Key::U => Some("u".to_string()),
|
||||
crate::keys::Key::V => Some("v".to_string()),
|
||||
crate::keys::Key::W => Some("w".to_string()),
|
||||
crate::keys::Key::X => Some("x".to_string()),
|
||||
crate::keys::Key::Y => Some("y".to_string()),
|
||||
crate::keys::Key::Z => Some("z".to_string()),
|
||||
crate::keys::Key::N0 => Some("0".to_string()),
|
||||
crate::keys::Key::N1 => Some("1".to_string()),
|
||||
crate::keys::Key::N2 => Some("2".to_string()),
|
||||
crate::keys::Key::N3 => Some("3".to_string()),
|
||||
crate::keys::Key::N4 => Some("4".to_string()),
|
||||
crate::keys::Key::N5 => Some("5".to_string()),
|
||||
crate::keys::Key::N6 => Some("6".to_string()),
|
||||
crate::keys::Key::N7 => Some("7".to_string()),
|
||||
crate::keys::Key::N8 => Some("8".to_string()),
|
||||
crate::keys::Key::N9 => Some("9".to_string()),
|
||||
crate::keys::Key::Numpad0 => Some("KP_0".to_string()),
|
||||
crate::keys::Key::Numpad1 => Some("KP_1".to_string()),
|
||||
crate::keys::Key::Numpad2 => Some("KP_2".to_string()),
|
||||
crate::keys::Key::Numpad3 => Some("KP_3".to_string()),
|
||||
crate::keys::Key::Numpad4 => Some("KP_4".to_string()),
|
||||
crate::keys::Key::Numpad5 => Some("KP_5".to_string()),
|
||||
crate::keys::Key::Numpad6 => Some("KP_6".to_string()),
|
||||
crate::keys::Key::Numpad7 => Some("KP_7".to_string()),
|
||||
crate::keys::Key::Numpad8 => Some("KP_8".to_string()),
|
||||
crate::keys::Key::Numpad9 => Some("KP_9".to_string()),
|
||||
crate::keys::Key::Raw(key_code) => unsafe {
|
||||
let key_sym = XKeycodeToKeysym(display, (*key_code).try_into().unwrap(), 0);
|
||||
let string = XKeysymToString(key_sym);
|
||||
let c_str = CStr::from_ptr(string);
|
||||
Some(c_str.to_string_lossy().to_string())
|
||||
},
|
||||
}
|
||||
}
|
24
espanso-inject/src/x11/xdotool/vendor/COPYRIGHT
vendored
Normal file
24
espanso-inject/src/x11/xdotool/vendor/COPYRIGHT
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
Copyright (c) 2007, 2008, 2009: Jordan Sissel.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Jordan Sissel nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY JORDAN SISSEL ``AS IS'' AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL JORDAN SISSEL BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
2266
espanso-inject/src/x11/xdotool/vendor/xdo.c
vendored
Normal file
2266
espanso-inject/src/x11/xdotool/vendor/xdo.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
938
espanso-inject/src/x11/xdotool/vendor/xdo.h
vendored
Normal file
938
espanso-inject/src/x11/xdotool/vendor/xdo.h
vendored
Normal file
|
@ -0,0 +1,938 @@
|
|||
/**
|
||||
* @file xdo.h
|
||||
*/
|
||||
#ifndef _XDO_H_
|
||||
#define _XDO_H_
|
||||
|
||||
#ifndef __USE_XOPEN
|
||||
#define __USE_XOPEN
|
||||
#endif /* __USE_XOPEN */
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/X.h>
|
||||
#include <unistd.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @mainpage
|
||||
*
|
||||
* libxdo helps you send fake mouse and keyboard input, search for windows,
|
||||
* perform various window management tasks such as desktop changes, window
|
||||
* movement, etc.
|
||||
*
|
||||
* For examples on libxdo usage, the xdotool source code is a good reference.
|
||||
*
|
||||
* @see xdo.h
|
||||
* @see xdo_new
|
||||
*/
|
||||
|
||||
/**
|
||||
* When issuing a window size change, giving this flag will make the size
|
||||
* change be relative to the size hints of the window. For terminals, this
|
||||
* generally means that the window size will be relative to the font size,
|
||||
* allowing you to change window sizes based on character rows and columns
|
||||
* instead of pixels.
|
||||
*/
|
||||
#define SIZE_USEHINTS (1L << 0)
|
||||
#define SIZE_USEHINTS_X (1L << 1)
|
||||
#define SIZE_USEHINTS_Y (1L << 2)
|
||||
|
||||
/**
|
||||
* CURRENTWINDOW is a special identify for xdo input faking (mouse and
|
||||
* keyboard) functions like xdo_send_keysequence_window that indicate we should target the
|
||||
* current window, not a specific window.
|
||||
*
|
||||
* Generally, this means we will use XTEST instead of XSendEvent when sending
|
||||
* events.
|
||||
*/
|
||||
#define CURRENTWINDOW (0)
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Map character to whatever information we need to be able to send
|
||||
* this key (keycode, modifiers, group, etc)
|
||||
*/
|
||||
typedef struct charcodemap {
|
||||
wchar_t key; /** the letter for this key, like 'a' */
|
||||
KeyCode code; /** the keycode that this key is on */
|
||||
KeySym symbol; /** the symbol representing this key */
|
||||
int group; /** the keyboard group that has this key in it */
|
||||
int modmask; /** the modifiers to apply when sending this key */
|
||||
/** if this key need to be bound at runtime because it does not
|
||||
* exist in the current keymap, this will be set to 1. */
|
||||
int needs_binding;
|
||||
} charcodemap_t;
|
||||
|
||||
typedef enum {
|
||||
XDO_FEATURE_XTEST, /** Is XTest available? */
|
||||
} XDO_FEATURES;
|
||||
|
||||
/**
|
||||
* The main context.
|
||||
*/
|
||||
typedef struct xdo {
|
||||
|
||||
/** The Display for Xlib */
|
||||
Display *xdpy;
|
||||
|
||||
/** The display name, if any. NULL if not specified. */
|
||||
char *display_name;
|
||||
|
||||
/** @internal Array of known keys/characters */
|
||||
charcodemap_t *charcodes;
|
||||
|
||||
/** @internal Length of charcodes array */
|
||||
int charcodes_len;
|
||||
|
||||
/** @internal highest keycode value */
|
||||
int keycode_high; /* highest and lowest keycodes */
|
||||
|
||||
/** @internal lowest keycode value */
|
||||
int keycode_low; /* used by this X server */
|
||||
|
||||
/** @internal number of keysyms per keycode */
|
||||
int keysyms_per_keycode;
|
||||
|
||||
/** Should we close the display when calling xdo_free? */
|
||||
int close_display_when_freed;
|
||||
|
||||
/** Be extra quiet? (omits some error/message output) */
|
||||
int quiet;
|
||||
|
||||
/** Enable debug output? */
|
||||
int debug;
|
||||
|
||||
/** Feature flags, such as XDO_FEATURE_XTEST, etc... */
|
||||
int features_mask;
|
||||
|
||||
} xdo_t;
|
||||
|
||||
|
||||
/**
|
||||
* Search only window title. DEPRECATED - Use SEARCH_NAME
|
||||
* @see xdo_search_windows
|
||||
*/
|
||||
#define SEARCH_TITLE (1UL << 0)
|
||||
|
||||
/**
|
||||
* Search only window class.
|
||||
* @see xdo_search_windows
|
||||
*/
|
||||
#define SEARCH_CLASS (1UL << 1)
|
||||
|
||||
/**
|
||||
* Search only window name.
|
||||
* @see xdo_search_windows
|
||||
*/
|
||||
#define SEARCH_NAME (1UL << 2)
|
||||
|
||||
/**
|
||||
* Search only window pid.
|
||||
* @see xdo_search_windows
|
||||
*/
|
||||
#define SEARCH_PID (1UL << 3)
|
||||
|
||||
/**
|
||||
* Search only visible windows.
|
||||
* @see xdo_search_windows
|
||||
*/
|
||||
#define SEARCH_ONLYVISIBLE (1UL << 4)
|
||||
|
||||
/**
|
||||
* Search only a specific screen.
|
||||
* @see xdo_search.screen
|
||||
* @see xdo_search_windows
|
||||
*/
|
||||
#define SEARCH_SCREEN (1UL << 5)
|
||||
|
||||
/**
|
||||
* Search only window class name.
|
||||
* @see xdo_search
|
||||
*/
|
||||
#define SEARCH_CLASSNAME (1UL << 6)
|
||||
|
||||
/**
|
||||
* Search a specific desktop
|
||||
* @see xdo_search.screen
|
||||
* @see xdo_search_windows
|
||||
*/
|
||||
#define SEARCH_DESKTOP (1UL << 7)
|
||||
|
||||
/**
|
||||
* Search only window role.
|
||||
* @see xdo_search
|
||||
*/
|
||||
#define SEARCH_ROLE (1UL << 8)
|
||||
|
||||
/**
|
||||
* The window search query structure.
|
||||
*
|
||||
* @see xdo_search_windows
|
||||
*/
|
||||
typedef struct xdo_search {
|
||||
const char *title; /** pattern to test against a window title */
|
||||
const char *winclass; /** pattern to test against a window class */
|
||||
const char *winclassname; /** pattern to test against a window class */
|
||||
const char *winname; /** pattern to test against a window name */
|
||||
const char *winrole; /** pattern to test against a window role */
|
||||
int pid; /** window pid (From window atom _NET_WM_PID) */
|
||||
long max_depth; /** depth of search. 1 means only toplevel windows */
|
||||
int only_visible; /** boolean; set true to search only visible windows */
|
||||
int screen; /** what screen to search, if any. If none given, search
|
||||
all screens */
|
||||
|
||||
/** Should the tests be 'and' or 'or' ? If 'and', any failure will skip the
|
||||
* window. If 'or', any success will keep the window in search results. */
|
||||
enum { SEARCH_ANY, SEARCH_ALL } require;
|
||||
|
||||
/** bitmask of things you are searching for, such as SEARCH_NAME, etc.
|
||||
* @see SEARCH_NAME, SEARCH_CLASS, SEARCH_PID, SEARCH_CLASSNAME, etc
|
||||
*/
|
||||
unsigned int searchmask;
|
||||
|
||||
/** What desktop to search, if any. If none given, search all screens. */
|
||||
long desktop;
|
||||
|
||||
/** How many results to return? If 0, return all. */
|
||||
unsigned int limit;
|
||||
} xdo_search_t;
|
||||
|
||||
#define XDO_ERROR 1
|
||||
#define XDO_SUCCESS 0
|
||||
|
||||
/**
|
||||
* Create a new xdo_t instance.
|
||||
*
|
||||
* @param display the string display name, such as ":0". If null, uses the
|
||||
* environment variable DISPLAY just like XOpenDisplay(NULL).
|
||||
*
|
||||
* @return Pointer to a new xdo_t or NULL on failure
|
||||
*/
|
||||
xdo_t* xdo_new(const char *display);
|
||||
|
||||
/**
|
||||
* Create a new xdo_t instance with an existing X11 Display instance.
|
||||
*
|
||||
* @param xdpy the Display pointer given by a previous XOpenDisplay()
|
||||
* @param display the string display name
|
||||
* @param close_display_when_freed If true, we will close the display when
|
||||
* xdo_free is called. Otherwise, we leave it open.
|
||||
*/
|
||||
xdo_t* xdo_new_with_opened_display(Display *xdpy, const char *display,
|
||||
int close_display_when_freed);
|
||||
|
||||
/**
|
||||
* Return a string representing the version of this library
|
||||
*/
|
||||
const char *xdo_version(void);
|
||||
|
||||
/**
|
||||
* Free and destroy an xdo_t instance.
|
||||
*
|
||||
* If close_display_when_freed is set, then we will also close the Display.
|
||||
*/
|
||||
void xdo_free(xdo_t *xdo);
|
||||
|
||||
/**
|
||||
* Move the mouse to a specific location.
|
||||
*
|
||||
* @param x the target X coordinate on the screen in pixels.
|
||||
* @param y the target Y coordinate on the screen in pixels.
|
||||
* @param screen the screen (number) you want to move on.
|
||||
*/
|
||||
int xdo_move_mouse(const xdo_t *xdo, int x, int y, int screen);
|
||||
|
||||
/**
|
||||
* Move the mouse to a specific location relative to the top-left corner
|
||||
* of a window.
|
||||
*
|
||||
* @param x the target X coordinate on the screen in pixels.
|
||||
* @param y the target Y coordinate on the screen in pixels.
|
||||
*/
|
||||
int xdo_move_mouse_relative_to_window(const xdo_t *xdo, Window window, int x, int y);
|
||||
|
||||
/**
|
||||
* Move the mouse relative to it's current position.
|
||||
*
|
||||
* @param x the distance in pixels to move on the X axis.
|
||||
* @param y the distance in pixels to move on the Y axis.
|
||||
*/
|
||||
int xdo_move_mouse_relative(const xdo_t *xdo, int x, int y);
|
||||
|
||||
/**
|
||||
* Send a mouse press (aka mouse down) for a given button at the current mouse
|
||||
* location.
|
||||
*
|
||||
* @param window The window you want to send the event to or CURRENTWINDOW
|
||||
* @param button The mouse button. Generally, 1 is left, 2 is middle, 3 is
|
||||
* right, 4 is wheel up, 5 is wheel down.
|
||||
*/
|
||||
int xdo_mouse_down(const xdo_t *xdo, Window window, int button);
|
||||
|
||||
/**
|
||||
* Send a mouse release (aka mouse up) for a given button at the current mouse
|
||||
* location.
|
||||
*
|
||||
* @param window The window you want to send the event to or CURRENTWINDOW
|
||||
* @param button The mouse button. Generally, 1 is left, 2 is middle, 3 is
|
||||
* right, 4 is wheel up, 5 is wheel down.
|
||||
*/
|
||||
int xdo_mouse_up(const xdo_t *xdo, Window window, int button);
|
||||
|
||||
/**
|
||||
* Get the current mouse location (coordinates and screen number).
|
||||
*
|
||||
* @param x integer pointer where the X coordinate will be stored
|
||||
* @param y integer pointer where the Y coordinate will be stored
|
||||
* @param screen_num integer pointer where the screen number will be stored
|
||||
*/
|
||||
int xdo_get_mouse_location(const xdo_t *xdo, int *x, int *y, int *screen_num);
|
||||
|
||||
/**
|
||||
* Get the window the mouse is currently over
|
||||
*
|
||||
* @param window_ret Window pointer where the window will be stored.
|
||||
*/
|
||||
int xdo_get_window_at_mouse(const xdo_t *xdo, Window *window_ret);
|
||||
|
||||
/**
|
||||
* Get all mouse location-related data.
|
||||
*
|
||||
* If null is passed for any parameter, we simply do not store it.
|
||||
* Useful if you only want the 'y' coordinate, for example.
|
||||
*
|
||||
* @param x integer pointer where the X coordinate will be stored
|
||||
* @param y integer pointer where the Y coordinate will be stored
|
||||
* @param screen_num integer pointer where the screen number will be stored
|
||||
* @param window Window pointer where the window/client the mouse is over
|
||||
* will be stored.
|
||||
*/
|
||||
int xdo_get_mouse_location2(const xdo_t *xdo, int *x_ret, int *y_ret,
|
||||
int *screen_num_ret, Window *window_ret);
|
||||
|
||||
/**
|
||||
* Wait for the mouse to move from a location. This function will block
|
||||
* until the condition has been satisfied.
|
||||
*
|
||||
* @param origin_x the X position you expect the mouse to move from
|
||||
* @param origin_y the Y position you expect the mouse to move from
|
||||
*/
|
||||
int xdo_wait_for_mouse_move_from(const xdo_t *xdo, int origin_x, int origin_y);
|
||||
|
||||
/**
|
||||
* Wait for the mouse to move to a location. This function will block
|
||||
* until the condition has been satisfied.
|
||||
*
|
||||
* @param dest_x the X position you expect the mouse to move to
|
||||
* @param dest_y the Y position you expect the mouse to move to
|
||||
*/
|
||||
int xdo_wait_for_mouse_move_to(const xdo_t *xdo, int dest_x, int dest_y);
|
||||
|
||||
/**
|
||||
* Send a click for a specific mouse button at the current mouse location.
|
||||
*
|
||||
* @param window The window you want to send the event to or CURRENTWINDOW
|
||||
* @param button The mouse button. Generally, 1 is left, 2 is middle, 3 is
|
||||
* right, 4 is wheel up, 5 is wheel down.
|
||||
*/
|
||||
int xdo_click_window(const xdo_t *xdo, Window window, int button);
|
||||
|
||||
/**
|
||||
* Send a one or more clicks for a specific mouse button at the current mouse
|
||||
* location.
|
||||
*
|
||||
* @param window The window you want to send the event to or CURRENTWINDOW
|
||||
* @param button The mouse button. Generally, 1 is left, 2 is middle, 3 is
|
||||
* right, 4 is wheel up, 5 is wheel down.
|
||||
*/
|
||||
int xdo_click_window_multiple(const xdo_t *xdo, Window window, int button,
|
||||
int repeat, useconds_t delay);
|
||||
|
||||
/**
|
||||
* Type a string to the specified window.
|
||||
*
|
||||
* If you want to send a specific key or key sequence, such as "alt+l", you
|
||||
* want instead xdo_send_keysequence_window(...).
|
||||
*
|
||||
* @param window The window you want to send keystrokes to or CURRENTWINDOW
|
||||
* @param string The string to type, like "Hello world!"
|
||||
* @param delay The delay between keystrokes in microseconds. 12000 is a decent
|
||||
* choice if you don't have other plans.
|
||||
*/
|
||||
int xdo_enter_text_window(const xdo_t *xdo, Window window, const char *string, useconds_t delay);
|
||||
|
||||
/**
|
||||
* Send a keysequence to the specified window.
|
||||
*
|
||||
* This allows you to send keysequences by symbol name. Any combination
|
||||
* of X11 KeySym names separated by '+' are valid. Single KeySym names
|
||||
* are valid, too.
|
||||
*
|
||||
* Examples:
|
||||
* "l"
|
||||
* "semicolon"
|
||||
* "alt+Return"
|
||||
* "Alt_L+Tab"
|
||||
*
|
||||
* If you want to type a string, such as "Hello world." you want to instead
|
||||
* use xdo_enter_text_window.
|
||||
*
|
||||
* @param window The window you want to send the keysequence to or
|
||||
* CURRENTWINDOW
|
||||
* @param keysequence The string keysequence to send.
|
||||
* @param delay The delay between keystrokes in microseconds.
|
||||
*/
|
||||
int xdo_send_keysequence_window(const xdo_t *xdo, Window window,
|
||||
const char *keysequence, useconds_t delay);
|
||||
|
||||
/**
|
||||
* Send key release (up) events for the given key sequence.
|
||||
*
|
||||
* @see xdo_send_keysequence_window
|
||||
*/
|
||||
int xdo_send_keysequence_window_up(const xdo_t *xdo, Window window,
|
||||
const char *keysequence, useconds_t delay);
|
||||
|
||||
/**
|
||||
* Send key press (down) events for the given key sequence.
|
||||
*
|
||||
* @see xdo_send_keysequence_window
|
||||
*/
|
||||
int xdo_send_keysequence_window_down(const xdo_t *xdo, Window window,
|
||||
const char *keysequence, useconds_t delay);
|
||||
|
||||
/**
|
||||
* Send a series of keystrokes.
|
||||
*
|
||||
* @param window The window to send events to or CURRENTWINDOW
|
||||
* @param keys The array of charcodemap_t entities to send.
|
||||
* @param nkeys The length of the keys parameter
|
||||
* @param pressed 1 for key press, 0 for key release.
|
||||
* @param modifier Pointer to integer to record the modifiers activated by
|
||||
* the keys being pressed. If NULL, we don't save the modifiers.
|
||||
* @param delay The delay between keystrokes in microseconds.
|
||||
*/
|
||||
int xdo_send_keysequence_window_list_do(const xdo_t *xdo, Window window,
|
||||
charcodemap_t *keys, int nkeys,
|
||||
int pressed, int *modifier, useconds_t delay);
|
||||
|
||||
|
||||
/**
|
||||
* Wait for a window to have a specific map state.
|
||||
*
|
||||
* State possibilities:
|
||||
* IsUnmapped - window is not displayed.
|
||||
* IsViewable - window is mapped and shown (though may be clipped by windows
|
||||
* on top of it)
|
||||
* IsUnviewable - window is mapped but a parent window is unmapped.
|
||||
*
|
||||
* @param wid the window you want to wait for.
|
||||
* @param map_state the state to wait for.
|
||||
*/
|
||||
int xdo_wait_for_window_map_state(const xdo_t *xdo, Window wid, int map_state);
|
||||
|
||||
#define SIZE_TO 0
|
||||
#define SIZE_FROM 1
|
||||
int xdo_wait_for_window_size(const xdo_t *xdo, Window window, unsigned int width,
|
||||
unsigned int height, int flags, int to_or_from);
|
||||
|
||||
|
||||
/**
|
||||
* Move a window to a specific location.
|
||||
*
|
||||
* The top left corner of the window will be moved to the x,y coordinate.
|
||||
*
|
||||
* @param wid the window to move
|
||||
* @param x the X coordinate to move to.
|
||||
* @param y the Y coordinate to move to.
|
||||
*/
|
||||
int xdo_move_window(const xdo_t *xdo, Window wid, int x, int y);
|
||||
|
||||
/**
|
||||
* Apply a window's sizing hints (if any) to a given width and height.
|
||||
*
|
||||
* This function wraps XGetWMNormalHints() and applies any
|
||||
* resize increment and base size to your given width and height values.
|
||||
*
|
||||
* @param window the window to use
|
||||
* @param width the unit width you want to translate
|
||||
* @param height the unit height you want to translate
|
||||
* @param width_ret the return location of the translated width
|
||||
* @param height_ret the return location of the translated height
|
||||
*/
|
||||
int xdo_translate_window_with_sizehint(const xdo_t *xdo, Window window,
|
||||
unsigned int width, unsigned int height,
|
||||
unsigned int *width_ret, unsigned int *height_ret);
|
||||
|
||||
/**
|
||||
* Change the window size.
|
||||
*
|
||||
* @param wid the window to resize
|
||||
* @param w the new desired width
|
||||
* @param h the new desired height
|
||||
* @param flags if 0, use pixels for units. If SIZE_USEHINTS, then
|
||||
* the units will be relative to the window size hints.
|
||||
*/
|
||||
int xdo_set_window_size(const xdo_t *xdo, Window wid, int w, int h, int flags);
|
||||
|
||||
/**
|
||||
* Change a window property.
|
||||
*
|
||||
* Example properties you can change are WM_NAME, WM_ICON_NAME, etc.
|
||||
*
|
||||
* @param wid The window to change a property of.
|
||||
* @param property the string name of the property.
|
||||
* @param value the string value of the property.
|
||||
*/
|
||||
int xdo_set_window_property(const xdo_t *xdo, Window wid, const char *property,
|
||||
const char *value);
|
||||
|
||||
/**
|
||||
* Change the window's classname and or class.
|
||||
*
|
||||
* @param name The new class name. If NULL, no change.
|
||||
* @param _class The new class. If NULL, no change.
|
||||
*/
|
||||
int xdo_set_window_class(const xdo_t *xdo, Window wid, const char *name,
|
||||
const char *_class);
|
||||
|
||||
/**
|
||||
* Sets the urgency hint for a window.
|
||||
*/
|
||||
int xdo_set_window_urgency (const xdo_t *xdo, Window wid, int urgency);
|
||||
|
||||
/**
|
||||
* Set the override_redirect value for a window. This generally means
|
||||
* whether or not a window manager will manage this window.
|
||||
*
|
||||
* If you set it to 1, the window manager will usually not draw borders on the
|
||||
* window, etc. If you set it to 0, the window manager will see it like a
|
||||
* normal application window.
|
||||
*
|
||||
*/
|
||||
int xdo_set_window_override_redirect(const xdo_t *xdo, Window wid,
|
||||
int override_redirect);
|
||||
|
||||
/**
|
||||
* Focus a window.
|
||||
*
|
||||
* @see xdo_activate_window
|
||||
* @param wid the window to focus.
|
||||
*/
|
||||
int xdo_focus_window(const xdo_t *xdo, Window wid);
|
||||
|
||||
/**
|
||||
* Raise a window to the top of the window stack. This is also sometimes
|
||||
* termed as bringing the window forward.
|
||||
*
|
||||
* @param wid The window to raise.
|
||||
*/
|
||||
int xdo_raise_window(const xdo_t *xdo, Window wid);
|
||||
|
||||
/**
|
||||
* Get the window currently having focus.
|
||||
*
|
||||
* @param window_ret Pointer to a window where the currently-focused window
|
||||
* will be stored.
|
||||
*/
|
||||
int xdo_get_focused_window(const xdo_t *xdo, Window *window_ret);
|
||||
|
||||
/**
|
||||
* Wait for a window to have or lose focus.
|
||||
*
|
||||
* @param window The window to wait on
|
||||
* @param want_focus If 1, wait for focus. If 0, wait for loss of focus.
|
||||
*/
|
||||
int xdo_wait_for_window_focus(const xdo_t *xdo, Window window, int want_focus);
|
||||
|
||||
/**
|
||||
* Get the PID owning a window. Not all applications support this.
|
||||
* It looks at the _NET_WM_PID property of the window.
|
||||
*
|
||||
* @param window the window to query.
|
||||
* @return the process id or 0 if no pid found.
|
||||
*/
|
||||
int xdo_get_pid_window(const xdo_t *xdo, Window window);
|
||||
|
||||
/**
|
||||
* Like xdo_get_focused_window, but return the first ancestor-or-self window *
|
||||
* having a property of WM_CLASS. This allows you to get the "real" or
|
||||
* top-level-ish window having focus rather than something you may not expect
|
||||
* to be the window having focused.
|
||||
*
|
||||
* @param window_ret Pointer to a window where the currently-focused window
|
||||
* will be stored.
|
||||
*/
|
||||
int xdo_get_focused_window_sane(const xdo_t *xdo, Window *window_ret);
|
||||
|
||||
/**
|
||||
* Activate a window. This is generally a better choice than xdo_focus_window
|
||||
* for a variety of reasons, but it requires window manager support:
|
||||
* - If the window is on another desktop, that desktop is switched to.
|
||||
* - It moves the window forward rather than simply focusing it
|
||||
*
|
||||
* Requires your window manager to support this.
|
||||
* Uses _NET_ACTIVE_WINDOW from the EWMH spec.
|
||||
*
|
||||
* @param wid the window to activate
|
||||
*/
|
||||
int xdo_activate_window(const xdo_t *xdo, Window wid);
|
||||
|
||||
/**
|
||||
* Wait for a window to be active or not active.
|
||||
*
|
||||
* Requires your window manager to support this.
|
||||
* Uses _NET_ACTIVE_WINDOW from the EWMH spec.
|
||||
*
|
||||
* @param window the window to wait on
|
||||
* @param active If 1, wait for active. If 0, wait for inactive.
|
||||
*/
|
||||
int xdo_wait_for_window_active(const xdo_t *xdo, Window window, int active);
|
||||
|
||||
/**
|
||||
* Map a window. This mostly means to make the window visible if it is
|
||||
* not currently mapped.
|
||||
*
|
||||
* @param wid the window to map.
|
||||
*/
|
||||
int xdo_map_window(const xdo_t *xdo, Window wid);
|
||||
|
||||
/**
|
||||
* Unmap a window
|
||||
*
|
||||
* @param wid the window to unmap
|
||||
*/
|
||||
int xdo_unmap_window(const xdo_t *xdo, Window wid);
|
||||
|
||||
/**
|
||||
* Minimize a window.
|
||||
*/
|
||||
int xdo_minimize_window(const xdo_t *xdo, Window wid);
|
||||
|
||||
#define _NET_WM_STATE_REMOVE 0 /* remove/unset property */
|
||||
#define _NET_WM_STATE_ADD 1 /* add/set property */
|
||||
#define _NET_WM_STATE_TOGGLE 2 /* toggle property */
|
||||
|
||||
/**
|
||||
* Get window classname
|
||||
* @param window the window
|
||||
* @param class_ret Pointer to the window classname WM_CLASS
|
||||
*/
|
||||
int xdo_get_window_classname(const xdo_t *xdo, Window window, unsigned char **class_ret);
|
||||
|
||||
/**
|
||||
* Change window state
|
||||
* @param action the _NET_WM_STATE action
|
||||
*/
|
||||
int xdo_window_state(xdo_t *xdo, Window window, unsigned long action, const char *property);
|
||||
|
||||
/**
|
||||
* Reparents a window
|
||||
*
|
||||
* @param wid_source the window to reparent
|
||||
* @param wid_target the new parent window
|
||||
*/
|
||||
int xdo_reparent_window(const xdo_t *xdo, Window wid_source, Window wid_target);
|
||||
|
||||
/**
|
||||
* Get a window's location.
|
||||
*
|
||||
* @param wid the window to query
|
||||
* @param x_ret pointer to int where the X location is stored. If NULL, X is
|
||||
* ignored.
|
||||
* @param y_ret pointer to int where the Y location is stored. If NULL, X is
|
||||
* ignored.
|
||||
* @param screen_ret Pointer to Screen* where the Screen* the window on is
|
||||
* stored. If NULL, this parameter is ignored.
|
||||
*/
|
||||
int xdo_get_window_location(const xdo_t *xdo, Window wid,
|
||||
int *x_ret, int *y_ret, Screen **screen_ret);
|
||||
|
||||
/**
|
||||
* Get a window's size.
|
||||
*
|
||||
* @param wid the window to query
|
||||
* @param width_ret pointer to unsigned int where the width is stored.
|
||||
* @param height_ret pointer to unsigned int where the height is stored.
|
||||
*/
|
||||
int xdo_get_window_size(const xdo_t *xdo, Window wid, unsigned int *width_ret,
|
||||
unsigned int *height_ret);
|
||||
|
||||
/* pager-like behaviors */
|
||||
|
||||
/**
|
||||
* Get the currently-active window.
|
||||
* Requires your window manager to support this.
|
||||
* Uses _NET_ACTIVE_WINDOW from the EWMH spec.
|
||||
*
|
||||
* @param window_ret Pointer to Window where the active window is stored.
|
||||
*/
|
||||
int xdo_get_active_window(const xdo_t *xdo, Window *window_ret);
|
||||
|
||||
/**
|
||||
* Get a window ID by clicking on it. This function blocks until a selection
|
||||
* is made.
|
||||
*
|
||||
* @param window_ret Pointer to Window where the selected window is stored.
|
||||
*/
|
||||
int xdo_select_window_with_click(const xdo_t *xdo, Window *window_ret);
|
||||
|
||||
/**
|
||||
* Set the number of desktops.
|
||||
* Uses _NET_NUMBER_OF_DESKTOPS of the EWMH spec.
|
||||
*
|
||||
* @param ndesktops the new number of desktops to set.
|
||||
*/
|
||||
int xdo_set_number_of_desktops(const xdo_t *xdo, long ndesktops);
|
||||
|
||||
/**
|
||||
* Get the current number of desktops.
|
||||
* Uses _NET_NUMBER_OF_DESKTOPS of the EWMH spec.
|
||||
*
|
||||
* @param ndesktops pointer to long where the current number of desktops is
|
||||
* stored
|
||||
*/
|
||||
int xdo_get_number_of_desktops(const xdo_t *xdo, long *ndesktops);
|
||||
|
||||
/**
|
||||
* Switch to another desktop.
|
||||
* Uses _NET_CURRENT_DESKTOP of the EWMH spec.
|
||||
*
|
||||
* @param desktop The desktop number to switch to.
|
||||
*/
|
||||
int xdo_set_current_desktop(const xdo_t *xdo, long desktop);
|
||||
|
||||
/**
|
||||
* Get the current desktop.
|
||||
* Uses _NET_CURRENT_DESKTOP of the EWMH spec.
|
||||
*
|
||||
* @param desktop pointer to long where the current desktop number is stored.
|
||||
*/
|
||||
int xdo_get_current_desktop(const xdo_t *xdo, long *desktop);
|
||||
|
||||
/**
|
||||
* Move a window to another desktop
|
||||
* Uses _NET_WM_DESKTOP of the EWMH spec.
|
||||
*
|
||||
* @param wid the window to move
|
||||
* @param desktop the desktop destination for the window
|
||||
*/
|
||||
int xdo_set_desktop_for_window(const xdo_t *xdo, Window wid, long desktop);
|
||||
|
||||
/**
|
||||
* Get the desktop a window is on.
|
||||
* Uses _NET_WM_DESKTOP of the EWMH spec.
|
||||
*
|
||||
* If your desktop does not support _NET_WM_DESKTOP, then '*desktop' remains
|
||||
* unmodified.
|
||||
*
|
||||
* @param wid the window to query
|
||||
* @param deskto pointer to long where the desktop of the window is stored
|
||||
*/
|
||||
int xdo_get_desktop_for_window(const xdo_t *xdo, Window wid, long *desktop);
|
||||
|
||||
/**
|
||||
* Search for windows.
|
||||
*
|
||||
* @param search the search query.
|
||||
* @param windowlist_ret the list of matching windows to return
|
||||
* @param nwindows_ret the number of windows (length of windowlist_ret)
|
||||
* @see xdo_search_t
|
||||
*/
|
||||
int xdo_search_windows(const xdo_t *xdo, const xdo_search_t *search,
|
||||
Window **windowlist_ret, unsigned int *nwindows_ret);
|
||||
|
||||
/**
|
||||
* Generic property fetch.
|
||||
*
|
||||
* @param window the window to query
|
||||
* @param atom the Atom to request
|
||||
* @param nitems the number of items
|
||||
* @param type the type of the return
|
||||
* @param size the size of the type
|
||||
* @return data consisting of 'nitems' items of size 'size' and type 'type'
|
||||
* will need to be cast to the type before using.
|
||||
*/
|
||||
unsigned char *xdo_get_window_property_by_atom(const xdo_t *xdo, Window window, Atom atom,
|
||||
long *nitems, Atom *type, int *size);
|
||||
|
||||
/**
|
||||
* Get property of window by name of atom.
|
||||
*
|
||||
* @param window the window to query
|
||||
* @param property the name of the atom
|
||||
* @param nitems the number of items
|
||||
* @param type the type of the return
|
||||
* @param size the size of the type
|
||||
* @return data consisting of 'nitems' items of size 'size' and type 'type'
|
||||
* will need to be cast to the type before using.
|
||||
*/
|
||||
int xdo_get_window_property(const xdo_t *xdo, Window window, const char *property,
|
||||
unsigned char **value, long *nitems, Atom *type, int *size);
|
||||
|
||||
/**
|
||||
* Get the current input state. This is a mask value containing any of the
|
||||
* following: ShiftMask, LockMask, ControlMask, Mod1Mask, Mod2Mask, Mod3Mask,
|
||||
* Mod4Mask, or Mod5Mask.
|
||||
*
|
||||
* @return the input mask
|
||||
*/
|
||||
unsigned int xdo_get_input_state(const xdo_t *xdo);
|
||||
|
||||
/**
|
||||
* If you need the symbol map, use this method.
|
||||
*
|
||||
* The symbol map is an array of string pairs mapping common tokens to X Keysym
|
||||
* strings, such as "alt" to "Alt_L"
|
||||
*
|
||||
* @returns array of strings.
|
||||
*/
|
||||
const char **xdo_get_symbol_map(void);
|
||||
|
||||
/* active modifiers stuff */
|
||||
|
||||
/**
|
||||
* Get a list of active keys. Uses XQueryKeymap.
|
||||
*
|
||||
* @param keys Pointer to the array of charcodemap_t that will be allocated
|
||||
* by this function.
|
||||
* @param nkeys Pointer to integer where the number of keys will be stored.
|
||||
*/
|
||||
int xdo_get_active_modifiers(const xdo_t *xdo, charcodemap_t **keys,
|
||||
int *nkeys);
|
||||
|
||||
/**
|
||||
* Send any events necessary to clear the active modifiers.
|
||||
* For example, if you are holding 'alt' when xdo_get_active_modifiers is
|
||||
* called, then this method will send a key-up for 'alt'
|
||||
*/
|
||||
int xdo_clear_active_modifiers(const xdo_t *xdo, Window window,
|
||||
charcodemap_t *active_mods,
|
||||
int active_mods_n);
|
||||
|
||||
/**
|
||||
* Send any events necessary to make these modifiers active.
|
||||
* This is useful if you just cleared the active modifiers and then wish
|
||||
* to restore them after.
|
||||
*/
|
||||
int xdo_set_active_modifiers(const xdo_t *xdo, Window window,
|
||||
charcodemap_t *active_mods,
|
||||
int active_mods_n);
|
||||
|
||||
/**
|
||||
* Get the position of the current viewport.
|
||||
*
|
||||
* This is only relevant if your window manager supports
|
||||
* _NET_DESKTOP_VIEWPORT
|
||||
*/
|
||||
int xdo_get_desktop_viewport(const xdo_t *xdo, int *x_ret, int *y_ret);
|
||||
|
||||
/**
|
||||
* Set the position of the current viewport.
|
||||
*
|
||||
* This is only relevant if your window manager supports
|
||||
* _NET_DESKTOP_VIEWPORT
|
||||
*/
|
||||
int xdo_set_desktop_viewport(const xdo_t *xdo, int x, int y);
|
||||
|
||||
/**
|
||||
* Kill a window and the client owning it.
|
||||
*
|
||||
*/
|
||||
int xdo_kill_window(const xdo_t *xdo, Window window);
|
||||
|
||||
/**
|
||||
* Close a window without trying to kill the client.
|
||||
*
|
||||
*/
|
||||
int xdo_close_window(const xdo_t *xdo, Window window);
|
||||
|
||||
/**
|
||||
* Request that a window close, gracefully.
|
||||
*
|
||||
*/
|
||||
int xdo_quit_window(const xdo_t *xdo, Window window);
|
||||
|
||||
/**
|
||||
* Find a client window that is a parent of the window given
|
||||
*/
|
||||
#define XDO_FIND_PARENTS (0)
|
||||
|
||||
/**
|
||||
* Find a client window that is a child of the window given
|
||||
*/
|
||||
#define XDO_FIND_CHILDREN (1)
|
||||
|
||||
/**
|
||||
* Find a client window (child) in a given window. Useful if you get the
|
||||
* window manager's decorator window rather than the client window.
|
||||
*/
|
||||
int xdo_find_window_client(const xdo_t *xdo, Window window, Window *window_ret,
|
||||
int direction);
|
||||
|
||||
/**
|
||||
* Get a window's name, if any.
|
||||
*
|
||||
* @param window window to get the name of.
|
||||
* @param name_ret character pointer pointer where the address of the window name will be stored.
|
||||
* @param name_len_ret integer pointer where the length of the window name will be stored.
|
||||
* @param name_type integer pointer where the type (atom) of the window name will be stored.
|
||||
*/
|
||||
int xdo_get_window_name(const xdo_t *xdo, Window window,
|
||||
unsigned char **name_ret, int *name_len_ret,
|
||||
int *name_type);
|
||||
|
||||
/**
|
||||
* Disable an xdo feature.
|
||||
*
|
||||
* This function is mainly used by libxdo itself, however, you may find it useful
|
||||
* in your own applications.
|
||||
*
|
||||
* @see XDO_FEATURES
|
||||
*/
|
||||
void xdo_disable_feature(xdo_t *xdo, int feature);
|
||||
|
||||
/**
|
||||
* Enable an xdo feature.
|
||||
*
|
||||
* This function is mainly used by libxdo itself, however, you may find it useful
|
||||
* in your own applications.
|
||||
*
|
||||
* @see XDO_FEATURES
|
||||
*/
|
||||
void xdo_enable_feature(xdo_t *xdo, int feature);
|
||||
|
||||
/**
|
||||
* Check if a feature is enabled.
|
||||
*
|
||||
* This function is mainly used by libxdo itself, however, you may find it useful
|
||||
* in your own applications.
|
||||
*
|
||||
* @see XDO_FEATURES
|
||||
*/
|
||||
int xdo_has_feature(xdo_t *xdo, int feature);
|
||||
|
||||
// Espanso-specific variants
|
||||
|
||||
KeySym fast_keysym_from_char(const xdo_t *xdo, wchar_t key);
|
||||
void fast_charcodemap_from_char(const xdo_t *xdo, charcodemap_t *key);
|
||||
void fast_charcodemap_from_keysym(const xdo_t *xdo, charcodemap_t *key, KeySym keysym);
|
||||
void fast_init_xkeyevent(const xdo_t *xdo, XKeyEvent *xk);
|
||||
void fast_send_key(const xdo_t *xdo, Window window, charcodemap_t *key,
|
||||
int modstate, int is_press, useconds_t delay);
|
||||
int fast_enter_text_window(const xdo_t *xdo, Window window, const char *string, useconds_t delay);
|
||||
void fast_send_event(const xdo_t *xdo, Window window, int keycode, int pressed);
|
||||
int fast_send_keysequence_window(const xdo_t *xdo, Window window,
|
||||
const char *keysequence, useconds_t delay);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* ifndef _XDO_H_ */
|
||||
|
24
espanso-inject/src/x11/xdotool/vendor/xdo_util.h
vendored
Normal file
24
espanso-inject/src/x11/xdotool/vendor/xdo_util.h
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
/* xdo utility pieces
|
||||
*
|
||||
* $Id$
|
||||
*/
|
||||
|
||||
#ifndef _XDO_UTIL_H_
|
||||
#define _XDO_UTIL_H_
|
||||
|
||||
#include "xdo.h"
|
||||
|
||||
/* human to Keysym string mapping */
|
||||
static const char *symbol_map[] = {
|
||||
"alt", "Alt_L",
|
||||
"ctrl", "Control_L",
|
||||
"control", "Control_L",
|
||||
"meta", "Meta_L",
|
||||
"super", "Super_L",
|
||||
"shift", "Shift_L",
|
||||
"enter", "Return",
|
||||
"return", "Return",
|
||||
NULL, NULL,
|
||||
};
|
||||
|
||||
#endif /* ifndef _XDO_UTIL_H_ */
|
|
@ -17,13 +17,13 @@
|
|||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Event {
|
||||
Key { key: Key, chars: Option<String> },
|
||||
VirtualSeparator,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Key {
|
||||
// Modifiers
|
||||
Alt,
|
||||
|
|
|
@ -26,7 +26,7 @@ pub mod regex;
|
|||
pub mod rolling;
|
||||
mod util;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MatchResult<Id> {
|
||||
pub id: Id,
|
||||
pub trigger: String,
|
||||
|
|
|
@ -185,7 +185,7 @@ impl<Id: Clone> RollingMatcher<Id> {
|
|||
// in the state.
|
||||
if !has_previous_state {
|
||||
if let Some(MatcherTreeRef::Node(node)) = node.word_separators.as_ref() {
|
||||
refs.extend(self.find_refs(&*node, event, true));
|
||||
refs.extend(self.find_refs(node, event, true));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ pub mod matcher;
|
|||
mod tree;
|
||||
mod util;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum RollingItem {
|
||||
WordSeparator,
|
||||
Key(Key),
|
||||
|
@ -31,7 +31,7 @@ pub enum RollingItem {
|
|||
CharInsensitive(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct RollingMatch<Id> {
|
||||
pub id: Id,
|
||||
pub items: Vec<RollingItem>,
|
||||
|
|
|
@ -335,7 +335,7 @@ fn map_field_if_present(
|
|||
}
|
||||
|
||||
// This is needed to convert the old form's {{control}} syntax to the new [[control]] one.
|
||||
fn apply_form_syntax_patch(matches: &mut Vec<Yaml>) {
|
||||
fn apply_form_syntax_patch(matches: &mut [Yaml]) {
|
||||
matches.iter_mut().for_each(|m| {
|
||||
if let Yaml::Hash(fields) = m {
|
||||
if let Some(Yaml::String(form_option)) = fields.get_mut(&Yaml::String("form".to_string())) {
|
||||
|
|
|
@ -25,6 +25,9 @@ use std::path::Path;
|
|||
#[cfg(not(target_os = "linux"))]
|
||||
const WX_WIDGETS_ARCHIVE_NAME: &str = "wxWidgets-3.1.5.zip";
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
const WX_WIDGETS_BUILD_OUT_DIR_ENV_NAME: &str = "WX_WIDGETS_BUILD_OUT_DIR";
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn build_native() {
|
||||
use std::process::Command;
|
||||
|
@ -36,7 +39,17 @@ fn build_native() {
|
|||
panic!("could not find wxWidgets archive!");
|
||||
}
|
||||
|
||||
let out_dir = PathBuf::from(std::env::var("OUT_DIR").expect("missing OUT_DIR"));
|
||||
let out_dir = if let Ok(out_path) = std::env::var(WX_WIDGETS_BUILD_OUT_DIR_ENV_NAME) {
|
||||
println!(
|
||||
"detected wxWidgets build output directory override: {}",
|
||||
out_path
|
||||
);
|
||||
let path = PathBuf::from(out_path);
|
||||
std::fs::create_dir_all(&path).expect("unable to create wxWidgets out dir");
|
||||
path
|
||||
} else {
|
||||
PathBuf::from(std::env::var("OUT_DIR").expect("missing OUT_DIR"))
|
||||
};
|
||||
let out_wx_dir = out_dir.join("wx");
|
||||
|
||||
if !out_wx_dir.is_dir() {
|
||||
|
@ -77,7 +90,7 @@ fn build_native() {
|
|||
)
|
||||
.args(&[
|
||||
"/k",
|
||||
&vcvars_path.to_string_lossy().to_string(),
|
||||
&vcvars_path.to_string_lossy(),
|
||||
"&",
|
||||
"nmake",
|
||||
"/f",
|
||||
|
@ -154,8 +167,19 @@ fn build_native() {
|
|||
panic!("could not find wxWidgets archive!");
|
||||
}
|
||||
|
||||
let out_dir = PathBuf::from(std::env::var("OUT_DIR").expect("missing OUT_DIR"));
|
||||
let out_dir = if let Ok(out_path) = std::env::var(WX_WIDGETS_BUILD_OUT_DIR_ENV_NAME) {
|
||||
println!(
|
||||
"detected wxWidgets build output directory override: {}",
|
||||
out_path
|
||||
);
|
||||
let path = PathBuf::from(out_path);
|
||||
std::fs::create_dir_all(&path).expect("unable to create wxWidgets out dir");
|
||||
path
|
||||
} else {
|
||||
PathBuf::from(std::env::var("OUT_DIR").expect("missing OUT_DIR"))
|
||||
};
|
||||
let out_wx_dir = out_dir.join("wx");
|
||||
println!("wxWidgets will be compiled into: {}", out_wx_dir.display());
|
||||
|
||||
let target_arch = match std::env::var("CARGO_CFG_TARGET_ARCH")
|
||||
.expect("unable to read target arch")
|
||||
|
@ -296,7 +320,7 @@ fn convert_fat_libraries_to_arm(lib_dir: &Path) {
|
|||
|
||||
// Make sure it's a fat library
|
||||
let lipo_output = std::process::Command::new("lipo")
|
||||
.args(&["-detailed_info", &path.to_string_lossy().to_string()])
|
||||
.args(&["-detailed_info", &path.to_string_lossy()])
|
||||
.output()
|
||||
.expect("unable to check if library is fat");
|
||||
let lipo_output = String::from_utf8_lossy(&lipo_output.stdout);
|
||||
|
@ -315,9 +339,9 @@ fn convert_fat_libraries_to_arm(lib_dir: &Path) {
|
|||
.args(&[
|
||||
"-thin",
|
||||
"arm64",
|
||||
&path.to_string_lossy().to_string(),
|
||||
&path.to_string_lossy(),
|
||||
"-output",
|
||||
&path.to_string_lossy().to_string(),
|
||||
&path.to_string_lossy(),
|
||||
])
|
||||
.output()
|
||||
.expect("unable to extract arm64 slice from library");
|
||||
|
|
|
@ -25,7 +25,7 @@ lazy_static! {
|
|||
static ref FIELD_REGEX: Regex = Regex::new(r"\{\{(.*?)\}\}|\[\[(.*?)\]\]").unwrap();
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Token {
|
||||
Text(String), // Text
|
||||
Field(String), // id: String
|
||||
|
|
|
@ -21,11 +21,10 @@ use std::collections::HashSet;
|
|||
|
||||
use crate::sys::search::types::SearchItem;
|
||||
|
||||
pub fn get_algorithm(
|
||||
name: &str,
|
||||
use_command_filter: bool,
|
||||
) -> Box<dyn Fn(&str, &[SearchItem]) -> Vec<usize>> {
|
||||
let search_algorithm: Box<dyn Fn(&str, &[SearchItem]) -> Vec<usize>> = match name {
|
||||
type FilterCallback = dyn Fn(&str, &[SearchItem]) -> Vec<usize>;
|
||||
|
||||
pub fn get_algorithm(name: &str, use_command_filter: bool) -> Box<FilterCallback> {
|
||||
let search_algorithm: Box<FilterCallback> = match name {
|
||||
"exact" => Box::new(exact_match),
|
||||
"iexact" => Box::new(case_insensitive_exact_match),
|
||||
"ikey" => Box::new(case_insensitive_keyword),
|
||||
|
@ -100,9 +99,7 @@ fn case_insensitive_keyword(query: &str, items: &[SearchItem]) -> Vec<usize> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
fn command_filter(
|
||||
search_algorithm: Box<dyn Fn(&str, &[SearchItem]) -> Vec<usize>>,
|
||||
) -> Box<dyn Fn(&str, &[SearchItem]) -> Vec<usize>> {
|
||||
fn command_filter(search_algorithm: Box<FilterCallback>) -> Box<FilterCallback> {
|
||||
Box::new(move |query, items| {
|
||||
let (valid_ids, trimmed_query) = if query.starts_with('>') {
|
||||
(
|
||||
|
|
|
@ -147,16 +147,15 @@ mod interop {
|
|||
}
|
||||
}
|
||||
|
||||
type SearchAlgorithmCallback = dyn Fn(&str, &[types::SearchItem]) -> Vec<usize>;
|
||||
|
||||
struct SearchData {
|
||||
owned_search: interop::OwnedSearch,
|
||||
items: Vec<types::SearchItem>,
|
||||
algorithm: Box<dyn Fn(&str, &[types::SearchItem]) -> Vec<usize>>,
|
||||
algorithm: Box<SearchAlgorithmCallback>,
|
||||
}
|
||||
|
||||
pub fn show(
|
||||
search: types::Search,
|
||||
algorithm: Box<dyn Fn(&str, &[types::SearchItem]) -> Vec<usize>>,
|
||||
) -> Option<String> {
|
||||
pub fn show(search: types::Search, algorithm: Box<SearchAlgorithmCallback>) -> Option<String> {
|
||||
use super::interop::*;
|
||||
|
||||
let owned_search: interop::OwnedSearch = (&search).into();
|
||||
|
|
|
@ -44,7 +44,9 @@ pub enum ErrorLevel {
|
|||
Warning,
|
||||
}
|
||||
|
||||
type OpenFileCallback = dyn Fn(&Path) + Send;
|
||||
|
||||
pub struct TroubleshootingHandlers {
|
||||
pub dont_show_again_changed: Option<Box<dyn Fn(bool) + Send>>,
|
||||
pub open_file: Option<Box<dyn Fn(&Path) + Send>>,
|
||||
pub open_file: Option<Box<OpenFileCallback>>,
|
||||
}
|
||||
|
|
|
@ -14,11 +14,19 @@ serde_yaml = "0.8.17"
|
|||
tempdir = "0.3.7"
|
||||
glob = "0.3.0"
|
||||
natord = "1.0.9"
|
||||
reqwest = { version = "0.11.4", features = ["blocking"] }
|
||||
lazy_static = "1.4.0"
|
||||
regex = "1.4.3"
|
||||
zip = "0.5.13"
|
||||
scopeguard = "1.1.0"
|
||||
fs_extra = "1.2.0"
|
||||
sha2 = "0.9.6"
|
||||
hex = "0.4.3"
|
||||
hex = "0.4.3"
|
||||
reqwest = { version = "0.11.4", features = ["blocking"], default-features = false}
|
||||
|
||||
# On Linux we don't want to depend on openssl to avoid dependency issues
|
||||
# https://github.com/espanso/espanso/issues/1056
|
||||
# We need to use features to control this behavior instead of targets due to this Cargo bug:
|
||||
# https://github.com/rust-lang/cargo/issues/1197
|
||||
[features]
|
||||
default-tls=["reqwest/default-tls"]
|
||||
rustls-tls=["reqwest/rustls-tls"]
|
|
@ -30,7 +30,7 @@ mod util;
|
|||
|
||||
pub const PACKAGE_SOURCE_FILE: &str = "_pkgsource.yml";
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ArchivedPackage {
|
||||
// Metadata
|
||||
pub manifest: Manifest,
|
||||
|
@ -39,12 +39,12 @@ pub struct ArchivedPackage {
|
|||
pub source: PackageSource,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct LegacyPackage {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum StoredPackage {
|
||||
Legacy(LegacyPackage),
|
||||
Modern(ArchivedPackage),
|
||||
|
@ -67,7 +67,7 @@ pub struct SaveOptions {
|
|||
pub overwrite_existing: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum PackageSource {
|
||||
Hub,
|
||||
|
|
|
@ -19,10 +19,10 @@
|
|||
|
||||
use std::path::Path;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Eq)]
|
||||
pub struct Manifest {
|
||||
pub name: String,
|
||||
pub title: String,
|
||||
|
@ -34,7 +34,13 @@ pub struct Manifest {
|
|||
impl Manifest {
|
||||
pub fn parse(manifest_path: &Path) -> Result<Self> {
|
||||
let manifest_str = std::fs::read_to_string(manifest_path)?;
|
||||
Ok(serde_yaml::from_str(&manifest_str)?)
|
||||
|
||||
serde_yaml::from_str(&manifest_str).with_context(|| {
|
||||
format!(
|
||||
"Failed manifest parsing for path: {}",
|
||||
manifest_path.display()
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ use anyhow::{anyhow, bail, Context, Result};
|
|||
|
||||
use crate::manifest::Manifest;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ResolvedPackage {
|
||||
pub manifest: Manifest,
|
||||
pub base_dir: PathBuf,
|
||||
|
|
|
@ -82,7 +82,7 @@ fn extract_zip(data: Vec<u8>, dest_dir: &Path) -> Result<()> {
|
|||
None => continue,
|
||||
};
|
||||
|
||||
if (&*file.name()).ends_with('/') {
|
||||
if file.name().ends_with('/') {
|
||||
std::fs::create_dir_all(&outpath)?;
|
||||
} else {
|
||||
if let Some(p) = outpath.parent() {
|
||||
|
|
|
@ -28,7 +28,7 @@ lazy_static! {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct GitHubParts {
|
||||
author: String,
|
||||
name: String,
|
||||
|
|
|
@ -28,7 +28,7 @@ lazy_static! {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct GitLabParts {
|
||||
author: String,
|
||||
name: String,
|
||||
|
|
|
@ -66,16 +66,13 @@ impl Extension for ScriptExtension {
|
|||
// Also replace %CONFIG% and %PACKAGES% path. See issue #380
|
||||
args.iter_mut().for_each(|arg| {
|
||||
if arg.contains("%HOME%") {
|
||||
*arg = arg.replace("%HOME%", &self.home_path.to_string_lossy().to_string());
|
||||
*arg = arg.replace("%HOME%", &self.home_path.to_string_lossy());
|
||||
}
|
||||
if arg.contains("%CONFIG%") {
|
||||
*arg = arg.replace("%CONFIG%", &self.config_path.to_string_lossy().to_string());
|
||||
*arg = arg.replace("%CONFIG%", &self.config_path.to_string_lossy());
|
||||
}
|
||||
if arg.contains("%PACKAGES%") {
|
||||
*arg = arg.replace(
|
||||
"%PACKAGES%",
|
||||
&self.packages_path.to_string_lossy().to_string(),
|
||||
);
|
||||
*arg = arg.replace("%PACKAGES%", &self.packages_path.to_string_lossy());
|
||||
}
|
||||
|
||||
// On Windows, correct paths separators
|
||||
|
|
|
@ -48,7 +48,7 @@ pub struct Context<'a> {
|
|||
pub templates: Vec<&'a Template>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct RenderOptions {
|
||||
pub casing_style: CasingStyle,
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ impl Default for RenderOptions {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum CasingStyle {
|
||||
None,
|
||||
Capitalize,
|
||||
|
@ -123,7 +123,7 @@ pub trait Extension {
|
|||
|
||||
pub type Scope<'a> = HashMap<&'a str, ExtensionOutput>;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ExtensionOutput {
|
||||
Single(String),
|
||||
Multiple(HashMap<String, String>),
|
||||
|
|
|
@ -78,7 +78,7 @@ pub(crate) fn render_variables(body: &str, scope: &Scope) -> Result<String> {
|
|||
ExtensionOutput::Multiple(results) => match var_subname {
|
||||
Some(var_subname) => {
|
||||
let var_subname = var_subname.as_str();
|
||||
results.get(var_subname).map_or("", |value| &*value)
|
||||
results.get(var_subname).map_or("", |value| value)
|
||||
}
|
||||
None => {
|
||||
error!(
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[derive(Debug, PartialEq, Clone, Eq)]
|
||||
pub enum UIEvent {
|
||||
TrayIconClick,
|
||||
ContextMenuClick(u32),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "espanso"
|
||||
version = "2.1.5-beta"
|
||||
version = "2.1.7-beta"
|
||||
authors = ["Federico Terzi <federicoterzi96@gmail.com>"]
|
||||
license = "GPL-3.0"
|
||||
description = "Cross-platform Text Expander written in Rust"
|
||||
|
@ -9,7 +9,14 @@ homepage = "https://github.com/federico-terzi/espanso"
|
|||
edition = "2018"
|
||||
|
||||
[features]
|
||||
default = ["modulo"]
|
||||
default = ["modulo", "native-tls"]
|
||||
|
||||
# These features control whether Espanso will use the native TLS functionality
|
||||
# or not. On some platforms (currently Linux) we prefer vendoring the SSL
|
||||
# logic used by the packages to avoid dependency issues.
|
||||
# https://github.com/espanso/espanso/issues/1056
|
||||
native-tls = ["espanso-package/default-tls"]
|
||||
vendored-tls = ["espanso-package/rustls-tls"]
|
||||
|
||||
# If the wayland feature is enabled, all X11 dependencies will be dropped
|
||||
# and only methods suitable for Wayland will be used
|
||||
|
@ -56,6 +63,7 @@ colored = "2.0.0"
|
|||
tempdir = "0.3.7"
|
||||
notify = "4.0.17"
|
||||
opener = "0.5.0"
|
||||
sysinfo = "0.24.5"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
named_pipe = "0.4.1"
|
||||
|
|
|
@ -177,9 +177,8 @@ fn find_available_backup_dir() -> PathBuf {
|
|||
"".to_string()
|
||||
};
|
||||
|
||||
let target_backup_dir = dirs::document_dir()
|
||||
.expect("unable to generate backup directory")
|
||||
.join(format!("espanso-migrate-backup{}", num));
|
||||
let target_backup_dir =
|
||||
find_backup_dir_location().join(format!("espanso-migrate-backup{}", num));
|
||||
|
||||
if !target_backup_dir.is_dir() {
|
||||
return target_backup_dir;
|
||||
|
@ -189,6 +188,22 @@ fn find_available_backup_dir() -> PathBuf {
|
|||
panic!("could not generate valid backup directory");
|
||||
}
|
||||
|
||||
fn find_backup_dir_location() -> PathBuf {
|
||||
if let Some(documents_dir) = dirs::document_dir() {
|
||||
return documents_dir;
|
||||
}
|
||||
|
||||
if let Some(home_dir) = dirs::home_dir() {
|
||||
return home_dir;
|
||||
}
|
||||
|
||||
if let Ok(current_dir) = std::env::current_dir() {
|
||||
return current_dir;
|
||||
}
|
||||
|
||||
panic!("unable to generate suitable backup location directory");
|
||||
}
|
||||
|
||||
fn error_print_and_log(msg: &str) {
|
||||
error!("{}", msg);
|
||||
eprintln!("{}", msg);
|
||||
|
|
|
@ -67,7 +67,7 @@ impl Default for CliModule {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum LogMode {
|
||||
Read,
|
||||
AppendOnly,
|
||||
|
|
|
@ -29,6 +29,7 @@ use crate::{error_eprintln, info_println, warn_eprintln};
|
|||
|
||||
const LINUX_SERVICE_NAME: &str = "espanso";
|
||||
const LINUX_SERVICE_CONTENT: &str = include_str!("../../res/linux/systemd.service");
|
||||
#[allow(clippy::transmute_bytes_to_str)]
|
||||
const LINUX_SERVICE_FILENAME: &str = formatcp!("{}.service", LINUX_SERVICE_NAME);
|
||||
|
||||
pub fn register() -> Result<()> {
|
||||
|
@ -41,10 +42,8 @@ pub fn register() -> Result<()> {
|
|||
info_println!("creating service file in {:?}", service_file);
|
||||
let espanso_path = get_binary_path().expect("unable to get espanso executable path");
|
||||
|
||||
let service_content = String::from(LINUX_SERVICE_CONTENT).replace(
|
||||
"{{{espanso_path}}}",
|
||||
&espanso_path.to_string_lossy().to_string(),
|
||||
);
|
||||
let service_content = String::from(LINUX_SERVICE_CONTENT)
|
||||
.replace("{{{espanso_path}}}", &espanso_path.to_string_lossy());
|
||||
|
||||
std::fs::write(service_file, service_content)?;
|
||||
|
||||
|
|
|
@ -152,6 +152,14 @@ fn start_main(paths: &Paths, _paths_overrides: &PathsOverrides, args: &ArgMatche
|
|||
}
|
||||
|
||||
error_eprintln!("unable to start service: timed out");
|
||||
|
||||
error_eprintln!(
|
||||
"Hint: sometimes this happens because another Espanso process is left running for some reason."
|
||||
);
|
||||
error_eprintln!(
|
||||
" Please try running 'espanso restart' or manually killing all Espanso processes, then try again."
|
||||
);
|
||||
|
||||
SERVICE_TIMED_OUT
|
||||
}
|
||||
|
||||
|
|
|
@ -19,14 +19,18 @@
|
|||
|
||||
use anyhow::{Error, Result};
|
||||
use log::error;
|
||||
use std::process::Command;
|
||||
use std::{path::Path, time::Instant};
|
||||
use sysinfo::{PidExt, ProcessExt, ProcessRefreshKind, RefreshKind, System, SystemExt};
|
||||
use thiserror::Error;
|
||||
|
||||
use espanso_ipc::IPCClient;
|
||||
|
||||
use crate::info_println;
|
||||
use crate::{
|
||||
ipc::{create_ipc_client_to_worker, IPCEvent},
|
||||
lock::acquire_worker_lock,
|
||||
warn_eprintln,
|
||||
};
|
||||
|
||||
pub fn terminate_worker(runtime_dir: &Path) -> Result<()> {
|
||||
|
@ -46,14 +50,18 @@ pub fn terminate_worker(runtime_dir: &Path) -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
while now.elapsed() < std::time::Duration::from_secs(3) {
|
||||
let lock_file = acquire_worker_lock(runtime_dir);
|
||||
if lock_file.is_some() {
|
||||
return Ok(());
|
||||
}
|
||||
if wait_for_worker_to_be_stopped(runtime_dir) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
warn_eprintln!(
|
||||
"unable to gracefully terminate espanso (timed-out), trying to force the termination..."
|
||||
);
|
||||
|
||||
forcefully_terminate_espanso();
|
||||
|
||||
if wait_for_worker_to_be_stopped(runtime_dir) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(StopError::WorkerTimedOut.into())
|
||||
|
@ -67,3 +75,47 @@ pub enum StopError {
|
|||
#[error("ipc error: `{0}`")]
|
||||
IPCError(Error),
|
||||
}
|
||||
|
||||
fn wait_for_worker_to_be_stopped(runtime_dir: &Path) -> bool {
|
||||
let now = Instant::now();
|
||||
while now.elapsed() < std::time::Duration::from_secs(3) {
|
||||
let lock_file = acquire_worker_lock(runtime_dir);
|
||||
if lock_file.is_some() {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn forcefully_terminate_espanso() {
|
||||
let mut sys =
|
||||
System::new_with_specifics(RefreshKind::new().with_processes(ProcessRefreshKind::new()));
|
||||
sys.refresh_processes_specifics(ProcessRefreshKind::new());
|
||||
|
||||
let target_process_names = if cfg!(target_os = "windows") {
|
||||
vec!["espanso.exe", "espansod.exe"]
|
||||
} else {
|
||||
vec!["espanso"]
|
||||
};
|
||||
|
||||
let current_pid = std::process::id();
|
||||
|
||||
// We want to terminate all Espanso processes except this one
|
||||
for (pid, process) in sys.processes() {
|
||||
if target_process_names.contains(&process.name()) && pid.as_u32() != current_pid {
|
||||
let str_pid = pid.as_u32().to_string();
|
||||
info_println!("killing espanso process with PID: {}", str_pid);
|
||||
|
||||
if cfg!(target_os = "windows") {
|
||||
let _ = Command::new("taskkill")
|
||||
.args(&["/pid", &str_pid, "/f"])
|
||||
.output();
|
||||
} else {
|
||||
let _ = Command::new("kill").args(&["-9", &str_pid]).output();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -143,6 +143,7 @@ impl<'a> super::engine::dispatch::executor::clipboard_injector::ClipboardParamsP
|
|||
restore_clipboard: active.preserve_clipboard(),
|
||||
restore_clipboard_delay: active.restore_clipboard_delay(),
|
||||
x11_use_xclip_backend: active.x11_use_xclip_backend(),
|
||||
x11_use_xdotool_backend: active.x11_use_xdotool_backend(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -164,6 +165,7 @@ impl<'a> super::engine::dispatch::executor::InjectParamsProvider for ConfigManag
|
|||
inject_delay: active.inject_delay(),
|
||||
key_delay: active.key_delay(),
|
||||
evdev_modifier_delay: active.evdev_modifier_delay(),
|
||||
x11_use_xdotool_backend: active.x11_use_xdotool_backend(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -209,3 +211,9 @@ impl<'a> crate::gui::modulo::search::ModuloSearchUIOptionProvider for ConfigMana
|
|||
self.active().post_search_delay()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> espanso_engine::process::AltCodeSynthEnabledProvider for ConfigManager<'a> {
|
||||
fn is_alt_code_synthesizer_enabled(&self) -> bool {
|
||||
self.active().emulate_alt_codes()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ pub struct ClipboardParams {
|
|||
pub restore_clipboard: bool,
|
||||
pub restore_clipboard_delay: usize,
|
||||
pub x11_use_xclip_backend: bool,
|
||||
pub x11_use_xdotool_backend: bool,
|
||||
}
|
||||
|
||||
pub struct ClipboardInjectorAdapter<'a> {
|
||||
|
@ -95,6 +96,7 @@ impl<'a> ClipboardInjectorAdapter<'a> {
|
|||
InjectionOptions {
|
||||
delay: params.paste_shortcut_event_delay as i32,
|
||||
disable_fast_inject: params.disable_x11_fast_inject,
|
||||
x11_use_xdotool_fallback: params.x11_use_xdotool_backend,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
|
|
@ -67,6 +67,7 @@ impl<'a> TextInjector for EventInjectorAdapter<'a> {
|
|||
})
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
x11_use_xdotool_fallback: params.x11_use_xdotool_backend,
|
||||
};
|
||||
|
||||
// We don't use the lines() method because it skips emtpy lines, which is not what we want.
|
||||
|
|
|
@ -59,6 +59,7 @@ impl<'a> KeyInjector for KeyInjectorAdapter<'a> {
|
|||
})
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
x11_use_xdotool_fallback: params.x11_use_xdotool_backend,
|
||||
};
|
||||
|
||||
let converted_keys: Vec<_> = keys.iter().map(convert_to_inject_key).collect();
|
||||
|
@ -107,6 +108,16 @@ fn convert_to_inject_key(key: &espanso_engine::event::input::Key) -> espanso_inj
|
|||
espanso_engine::event::input::Key::F18 => espanso_inject::keys::Key::F18,
|
||||
espanso_engine::event::input::Key::F19 => espanso_inject::keys::Key::F19,
|
||||
espanso_engine::event::input::Key::F20 => espanso_inject::keys::Key::F20,
|
||||
espanso_engine::event::input::Key::Numpad0 => espanso_inject::keys::Key::Numpad0,
|
||||
espanso_engine::event::input::Key::Numpad1 => espanso_inject::keys::Key::Numpad1,
|
||||
espanso_engine::event::input::Key::Numpad2 => espanso_inject::keys::Key::Numpad2,
|
||||
espanso_engine::event::input::Key::Numpad3 => espanso_inject::keys::Key::Numpad3,
|
||||
espanso_engine::event::input::Key::Numpad4 => espanso_inject::keys::Key::Numpad4,
|
||||
espanso_engine::event::input::Key::Numpad5 => espanso_inject::keys::Key::Numpad5,
|
||||
espanso_engine::event::input::Key::Numpad6 => espanso_inject::keys::Key::Numpad6,
|
||||
espanso_engine::event::input::Key::Numpad7 => espanso_inject::keys::Key::Numpad7,
|
||||
espanso_engine::event::input::Key::Numpad8 => espanso_inject::keys::Key::Numpad8,
|
||||
espanso_engine::event::input::Key::Numpad9 => espanso_inject::keys::Key::Numpad9,
|
||||
espanso_engine::event::input::Key::Other(raw) => espanso_inject::keys::Key::Raw(*raw),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,4 +34,5 @@ pub struct InjectParams {
|
|||
pub key_delay: Option<usize>,
|
||||
pub disable_x11_fast_inject: bool,
|
||||
pub evdev_modifier_delay: Option<usize>,
|
||||
pub x11_use_xdotool_backend: bool,
|
||||
}
|
||||
|
|
|
@ -110,6 +110,16 @@ pub fn convert_to_engine_key(key: espanso_detect::event::Key) -> Key {
|
|||
espanso_detect::event::Key::F18 => Key::F18,
|
||||
espanso_detect::event::Key::F19 => Key::F19,
|
||||
espanso_detect::event::Key::F20 => Key::F20,
|
||||
espanso_detect::event::Key::Numpad0 => Key::Numpad0,
|
||||
espanso_detect::event::Key::Numpad1 => Key::Numpad1,
|
||||
espanso_detect::event::Key::Numpad2 => Key::Numpad2,
|
||||
espanso_detect::event::Key::Numpad3 => Key::Numpad3,
|
||||
espanso_detect::event::Key::Numpad4 => Key::Numpad4,
|
||||
espanso_detect::event::Key::Numpad5 => Key::Numpad5,
|
||||
espanso_detect::event::Key::Numpad6 => Key::Numpad6,
|
||||
espanso_detect::event::Key::Numpad7 => Key::Numpad7,
|
||||
espanso_detect::event::Key::Numpad8 => Key::Numpad8,
|
||||
espanso_detect::event::Key::Numpad9 => Key::Numpad9,
|
||||
espanso_detect::event::Key::Other(code) => Key::Other(code),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -243,6 +243,7 @@ pub fn initialize_and_spawn(
|
|||
&modifier_state_store,
|
||||
&combined_match_cache,
|
||||
¬ification_manager,
|
||||
&config_manager,
|
||||
);
|
||||
|
||||
let event_injector = EventInjectorAdapter::new(&*injector, &config_manager);
|
||||
|
|
|
@ -98,5 +98,6 @@ pub fn convert_to_match_key(key: Key) -> espanso_match::event::Key {
|
|||
Key::F19 => espanso_match::event::Key::F19,
|
||||
Key::F20 => espanso_match::event::Key::F20,
|
||||
Key::Other(_) => espanso_match::event::Key::Other,
|
||||
_ => espanso_match::event::Key::Other,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ use simplelog::{
|
|||
use crate::{
|
||||
cli::{LogMode, PathsOverrides},
|
||||
config::load_config,
|
||||
util::log_system_info,
|
||||
};
|
||||
|
||||
mod capabilities;
|
||||
|
@ -586,6 +587,7 @@ For example, specifying 'email' is equivalent to 'match/email.yml'."#))
|
|||
info!("reading configs from: {:?}", paths.config);
|
||||
info!("reading packages from: {:?}", paths.packages);
|
||||
info!("using runtime dir: {:?}", paths.runtime);
|
||||
log_system_info();
|
||||
|
||||
if handler.requires_config {
|
||||
let config_result =
|
||||
|
|
|
@ -50,8 +50,10 @@ generate_patchable_config!(
|
|||
undo_backspace -> bool,
|
||||
post_form_delay -> usize,
|
||||
post_search_delay -> usize,
|
||||
emulate_alt_codes -> bool,
|
||||
win32_exclude_orphan_events -> bool,
|
||||
win32_keyboard_layout_cache_interval -> i64,
|
||||
x11_use_xclip_backend -> bool,
|
||||
x11_use_xdotool_backend -> bool,
|
||||
keyboard_layout -> Option<RMLVOConfig>
|
||||
);
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use log::info;
|
||||
use std::process::Command;
|
||||
use sysinfo::{System, SystemExt};
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn set_command_flags(command: &mut Command) {
|
||||
|
@ -43,3 +45,13 @@ pub fn attach_console() {
|
|||
pub fn attach_console() {
|
||||
// Not necessary on Linux and macOS
|
||||
}
|
||||
|
||||
pub fn log_system_info() {
|
||||
let sys = System::new();
|
||||
info!(
|
||||
"system info: {} v{} - kernel: {}",
|
||||
sys.name().unwrap_or_default(),
|
||||
sys.os_version().unwrap_or_default(),
|
||||
sys.kernel_version().unwrap_or_default()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -53,6 +53,13 @@ fn main() {
|
|||
if !avoid_modulo {
|
||||
features.push("modulo");
|
||||
}
|
||||
// On linux, we don't want to rely on OpenSSL to avoid dependency issues
|
||||
// https://github.com/espanso/espanso/issues/1056
|
||||
if cfg!(target_os = "linux") {
|
||||
features.push("vendored-tls")
|
||||
} else {
|
||||
features.push("native-tls")
|
||||
}
|
||||
|
||||
let features_flag = features.join(" ");
|
||||
|
||||
|
|
|
@ -48,6 +48,13 @@ fn main() {
|
|||
if wayland {
|
||||
features.push("wayland");
|
||||
}
|
||||
// On linux, we don't want to rely on OpenSSL to avoid dependency issues
|
||||
// https://github.com/espanso/espanso/issues/1056
|
||||
if cfg!(target_os = "linux") {
|
||||
features.push("vendored-tls")
|
||||
} else {
|
||||
features.push("native-tls")
|
||||
}
|
||||
|
||||
let features_flag = features.join(" ");
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name: espanso
|
||||
version: 2.1.5-beta
|
||||
version: 2.1.7-beta
|
||||
summary: A Cross-platform Text Expander written in Rust
|
||||
description: |
|
||||
espanso is a Cross-platform, Text Expander written in Rust.
|
||||
|
|
Loading…
Reference in New Issue
Block a user