Compare commits
130 Commits
feature-va
...
dev
Author | SHA1 | Date | |
---|---|---|---|
|
b2356abe69 | ||
|
8e919952e2 | ||
|
5256e3e79f | ||
|
ee14983f7c | ||
|
ce01989f7c | ||
|
d795d81fbf | ||
|
2ea452bf61 | ||
|
7fd1502bcf | ||
|
930bf807b5 | ||
|
1c660d9cfb | ||
|
6b5d0a3d3b | ||
|
1b947ec188 | ||
|
4d0cc7a6f1 | ||
|
abf31616c3 | ||
|
483e9c84ca | ||
|
ed2a2b25df | ||
|
a1101b907f | ||
|
9f82b4e146 | ||
|
9e8a3f10da | ||
|
bd4ae0f8aa | ||
|
9059dcb4c1 | ||
|
5e53acfc24 | ||
|
12bb84f474 | ||
|
f30395b8a6 | ||
|
088080dd63 | ||
|
5df94b5031 | ||
|
0b49784c6f | ||
|
e4309a4d17 | ||
|
4bb540b095 | ||
|
e37aa98616 | ||
|
e3887c0184 | ||
|
51527500e7 | ||
|
880c7c0708 | ||
|
c77d9c560b | ||
|
cbca79ab0f | ||
|
3abe84f8b0 | ||
|
9760776904 | ||
|
56b200609a | ||
|
36a507488b | ||
|
ec24100260 | ||
|
dcea6fa178 | ||
|
4e9b49c3ad | ||
|
81245722b8 | ||
|
a70d9b6770 | ||
|
ca8ca3001d | ||
|
115e2f2138 | ||
|
c09e85ba85 | ||
|
e304192dbd | ||
|
22987c6518 | ||
|
cd9f26ce3e | ||
|
54bfed4c11 | ||
|
ff693dd998 | ||
|
50a67baf03 | ||
|
b5ac6b85c5 | ||
|
86632e38e6 | ||
|
29f079729f | ||
|
3a6bd4f01e | ||
|
50f70b2ba5 | ||
|
671fd3c532 | ||
|
d7ee3b3833 | ||
|
85b8a90913 | ||
|
f07d297ce0 | ||
|
b31617a2f3 | ||
|
90be91d1e9 | ||
|
35ed59bd23 | ||
|
a143d951f8 | ||
|
fe5c9cf19d | ||
|
697f51e210 | ||
|
7588900c06 | ||
|
68e2698a9c | ||
|
152402b74e | ||
|
a9f4cc1edf | ||
|
8d0efc6436 | ||
|
1f0761d140 | ||
|
9801b09ab6 | ||
|
873c70a248 | ||
|
b2e6a8c8cc | ||
|
06f990fbd7 | ||
|
92b85427ed | ||
|
08483e73b1 | ||
|
d4d7609dd8 | ||
|
243c6604f8 | ||
|
37893a3f74 | ||
|
1e92cfef5c | ||
|
88b9c2ae03 | ||
|
bd2abeb8de | ||
|
c23d99311a | ||
|
38b4d437f3 | ||
|
f062a8ec35 | ||
|
bf1d184ae9 | ||
|
de1fefba51 | ||
|
74c5e5ae86 | ||
|
50904c2d2b | ||
|
283b85818b | ||
|
73214cb59a | ||
|
57b2f194e5 | ||
|
20cfb9cb3b | ||
|
08853451c0 | ||
|
42cbb6e3de | ||
|
41b72acdf1 | ||
|
c4f4f438d3 | ||
|
8909ccdb4d | ||
|
e2b6dcba38 | ||
|
c69544c1e2 | ||
|
334e99b343 | ||
|
9081ca76e7 | ||
|
84fd39a952 | ||
|
b2452ecca7 | ||
|
02ec804604 | ||
|
fff9f63f96 | ||
|
f7fdb06db8 | ||
|
6168a28291 | ||
|
411118b550 | ||
|
bf1f3fc2e0 | ||
|
85f1598cf2 | ||
|
0387ba8118 | ||
|
541c8d462c | ||
|
38edd67bd0 | ||
|
2745257ce9 | ||
|
c7d6d69b72 | ||
|
317d3f2051 | ||
|
aa26f27ed9 | ||
|
8acca4a366 | ||
|
57450bee32 | ||
|
3f5b0b04f2 | ||
|
4bd5f4c6c5 | ||
|
b7e91d9b5d | ||
|
f436a9757d | ||
|
aa9465490b | ||
|
386a351df7 |
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']
|
||||
|
|
28
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
28
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Let us know about a bug in Espanso
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots or screen recordings to help explain your problem.
|
||||
|
||||
**Logs**
|
||||
If possible, run `espanso log` in a terminal after the bug has occurred, then post the output here so that we can better diagnose the problem
|
||||
|
||||
**Setup information**
|
||||
- OS: What OS are you using?
|
||||
- Version: which version of Espanso are you running? (you can find out by running `espanso --version` inside a terminal)
|
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
12
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea to make Espanso better
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Feature requests have been moved to discussions, please open it there :)
|
||||
|
||||
https://github.com/espanso/espanso/discussions/categories/feature-requests-and-ideas
|
4
.github/scripts/ubuntu/Dockerfile
vendored
4
.github/scripts/ubuntu/Dockerfile
vendored
|
@ -9,7 +9,7 @@ RUN apt-get update \
|
|||
ENV RUSTUP_HOME=/usr/local/rustup \
|
||||
CARGO_HOME=/usr/local/cargo \
|
||||
PATH=/usr/local/cargo/bin:$PATH \
|
||||
RUST_VERSION=1.55.0
|
||||
RUST_VERSION=1.57.0
|
||||
|
||||
RUN set -eux; \
|
||||
dpkgArch="$(dpkg --print-architecture)"; \
|
||||
|
@ -31,7 +31,7 @@ RUN set -eux; \
|
|||
cargo --version; \
|
||||
rustc --version;
|
||||
|
||||
RUN mkdir espanso && cargo install --force cargo-make --version 0.34.0
|
||||
RUN mkdir espanso && cargo install rust-script --version "0.7.0" && cargo install --force cargo-make --version 0.34.0
|
||||
|
||||
COPY . espanso
|
||||
|
||||
|
|
24
.github/scripts/ubuntu/build_deb.sh
vendored
Executable file
24
.github/scripts/ubuntu/build_deb.sh
vendored
Executable file
|
@ -0,0 +1,24 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "Installing cargo-deb"
|
||||
cargo install cargo-deb --version 1.34.0
|
||||
|
||||
cd espanso
|
||||
|
||||
echo "Building X11 deb package"
|
||||
cargo deb -p espanso -- --no-default-features --features "modulo vendored-tls"
|
||||
|
||||
echo "Building Wayland deb package"
|
||||
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
|
||||
sha256sum espanso-debian-x11-amd64.deb > espanso-debian-x11-amd64-sha256.txt
|
||||
cp espanso/target/debian/espanso-wayland*.deb espanso-debian-wayland-amd64.deb
|
||||
sha256sum espanso-debian-wayland-amd64.deb > espanso-debian-wayland-amd64-sha256.txt
|
||||
ls -la
|
||||
|
||||
echo "Copying to mounted volume"
|
||||
cp espanso-debian-* /shared
|
101
.github/workflows/ci.yml
vendored
101
.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,25 +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"
|
||||
- name: Install cargo-make
|
||||
|
||||
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: |
|
||||
cargo install --force cargo-make --version 0.34.0
|
||||
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 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
|
||||
|
@ -54,27 +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
|
||||
- name: Install cargo-make
|
||||
|
||||
test-wayland:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Check formatting
|
||||
run: |
|
||||
cargo install --force cargo-make --version 0.34.0
|
||||
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 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
|
||||
- name: Install cargo-make
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Install rust-script and cargo-make
|
||||
run: |
|
||||
cargo install --force cargo-make --version 0.34.0
|
||||
cargo install rust-script --version "0.7.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
|
||||
|
@ -82,5 +157,3 @@ jobs:
|
|||
# uses: mxschmitt/action-tmate@v3
|
||||
# with:
|
||||
# limit-access-to-actor: true
|
||||
|
||||
# TODO: add clippy check
|
148
.github/workflows/release.yml
vendored
148
.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"
|
||||
|
||||
|
@ -59,13 +59,30 @@ jobs:
|
|||
- name: Print target version
|
||||
run: |
|
||||
echo Using version ${{ needs.extract-version.outputs.espanso_version }}
|
||||
- name: Install cargo-make
|
||||
- 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
|
||||
- name: Test
|
||||
run: cargo make test-binary --profile release
|
||||
- name: Build
|
||||
run: cargo make build-windows-all --profile release
|
||||
- name: Build resources
|
||||
run: cargo make build-windows-resources --profile release
|
||||
- name: Sign resources
|
||||
run: cargo make sign-windows-resources
|
||||
env:
|
||||
CODESIGN_PWD: ${{ secrets.WIN_CODESIGN_PWD }}
|
||||
CODESIGN_CROSS_SIGNED_B64: ${{ secrets.WIN_CODESIGN_INTERMEDIATE_B64 }}
|
||||
CODESIGN_CERTIFICATE_B64: ${{ secrets.WIN_CODESIGN_CERTIFICATE_B64 }}
|
||||
- name: Build installer
|
||||
run: cargo make build-windows-installer --profile release --skip-tasks build-windows-resources
|
||||
- name: Sign installer
|
||||
run: cargo make sign-windows-installer
|
||||
env:
|
||||
CODESIGN_PWD: ${{ secrets.WIN_CODESIGN_PWD }}
|
||||
CODESIGN_CROSS_SIGNED_B64: ${{ secrets.WIN_CODESIGN_INTERMEDIATE_B64 }}
|
||||
CODESIGN_CERTIFICATE_B64: ${{ secrets.WIN_CODESIGN_CERTIFICATE_B64 }}
|
||||
- name: Build portable mode archive
|
||||
run: cargo make build-windows-portable --profile release --skip-tasks build-windows-resources
|
||||
- name: Create portable mode archive
|
||||
shell: powershell
|
||||
run: |
|
||||
|
@ -116,6 +133,34 @@ jobs:
|
|||
if: ${{ github.ref == 'refs/heads/master' }}
|
||||
run: |
|
||||
gh release upload ${{ needs.extract-version.outputs.espanso_version }} Espanso-X11.AppImage Espanso-X11.AppImage.sha256.txt
|
||||
|
||||
linux-deb:
|
||||
needs: ["extract-version", "create-release"]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Print target version
|
||||
run: |
|
||||
echo Using version ${{ needs.extract-version.outputs.espanso_version }}
|
||||
- name: Build docker image
|
||||
run: |
|
||||
sudo docker build -t espanso-ubuntu . -f .github/scripts/ubuntu/Dockerfile
|
||||
- name: Build Deb packages
|
||||
run: |
|
||||
sudo docker run --rm -v "$(pwd):/shared" espanso-ubuntu espanso/.github/scripts/ubuntu/build_deb.sh
|
||||
- uses: actions/upload-artifact@v2
|
||||
name: "Upload artifacts"
|
||||
with:
|
||||
name: Ubuntu-Debian Artifacts
|
||||
path: |
|
||||
espanso-debian-x11-amd64.deb
|
||||
espanso-debian-wayland-amd64.deb
|
||||
- name: Upload artifacts to Github Releases (if master)
|
||||
if: ${{ github.ref == 'refs/heads/master' }}
|
||||
run: |
|
||||
gh release upload ${{ needs.extract-version.outputs.espanso_version }} espanso-debian-x11-amd64.deb espanso-debian-wayland-amd64.deb espanso-debian-x11-amd64-sha256.txt espanso-debian-wayland-amd64-sha256.txt
|
||||
|
||||
|
||||
macos-intel:
|
||||
needs: ["extract-version", "create-release"]
|
||||
|
@ -126,8 +171,9 @@ jobs:
|
|||
- name: Print target version
|
||||
run: |
|
||||
echo Using version ${{ needs.extract-version.outputs.espanso_version }}
|
||||
- name: Install cargo-make
|
||||
- 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
|
||||
- name: Test
|
||||
run: cargo make test-binary --profile release
|
||||
|
@ -137,6 +183,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
|
||||
|
@ -166,24 +243,43 @@ jobs:
|
|||
echo Using version ${{ needs.extract-version.outputs.espanso_version }}
|
||||
- name: Install rust target
|
||||
run: rustup update && rustup target add aarch64-apple-darwin
|
||||
- name: Install cargo-make
|
||||
- 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
|
||||
- name: Build
|
||||
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
|
||||
|
@ -200,4 +296,26 @@ jobs:
|
|||
- name: Upload artifacts to Github Releases (if master)
|
||||
if: ${{ github.ref == 'refs/heads/master' }}
|
||||
run: |
|
||||
gh release upload ${{ needs.extract-version.outputs.espanso_version }} Espanso-Mac-M1.zip Espanso-Mac-M1.zip.sha256.txt
|
||||
gh release upload ${{ needs.extract-version.outputs.espanso_version }} Espanso-Mac-M1.zip Espanso-Mac-M1.zip.sha256.txt
|
||||
|
||||
macos-publish-homebrew:
|
||||
needs: ["extract-version", "create-release", "macos-m1", "macos-intel"]
|
||||
runs-on: macos-11
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Print target version
|
||||
run: |
|
||||
echo Using version ${{ needs.extract-version.outputs.espanso_version }}
|
||||
|
||||
- name: "Setup SSH deploy key"
|
||||
uses: webfactory/ssh-agent@fc49353b67b2b7c1e0e6a600572d01a69f2672dd
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.HOMEBREW_CASK_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Create and Publish Homebrew Cask
|
||||
if: ${{ github.ref == 'refs/heads/master' }}
|
||||
run: |
|
||||
VERSION="${{ needs.extract-version.outputs.espanso_version }}" ./scripts/publish_homebrew_version.sh
|
||||
|
||||
echo "Cask formula has been published here: "
|
||||
|
|
241
Cargo.lock
generated
241
Cargo.lock
generated
|
@ -187,9 +187,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.66"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
@ -212,6 +212,7 @@ dependencies = [
|
|||
"libc",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"pure-rust-locales",
|
||||
"time",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
@ -263,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"
|
||||
|
@ -331,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",
|
||||
|
@ -355,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",
|
||||
|
@ -366,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",
|
||||
|
@ -380,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",
|
||||
|
@ -390,15 +385,24 @@ 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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cstr_core"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "644828c273c063ab0d39486ba42a5d1f3a499d35529c759e763a9c6cb8a0fb08"
|
||||
dependencies = [
|
||||
"cty",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.1.20"
|
||||
|
@ -409,6 +413,12 @@ dependencies = [
|
|||
"syn 1.0.67",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cty"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
|
||||
|
||||
[[package]]
|
||||
name = "dbus"
|
||||
version = "0.9.1"
|
||||
|
@ -572,7 +582,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "espanso"
|
||||
version = "2.1.0-alpha"
|
||||
version = "2.1.7-beta"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"caps",
|
||||
|
@ -614,6 +624,7 @@ dependencies = [
|
|||
"serde_json",
|
||||
"serde_yaml",
|
||||
"simplelog",
|
||||
"sysinfo",
|
||||
"tempdir",
|
||||
"thiserror",
|
||||
"widestring",
|
||||
|
@ -851,6 +862,7 @@ dependencies = [
|
|||
"log",
|
||||
"rand 0.8.3",
|
||||
"regex",
|
||||
"sys-locale",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
|
@ -1030,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"
|
||||
|
@ -1051,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",
|
||||
]
|
||||
|
||||
|
@ -1234,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"
|
||||
|
@ -1384,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"
|
||||
|
@ -2031,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"
|
||||
|
@ -2040,6 +2089,12 @@ dependencies = [
|
|||
"unicode-xid 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pure-rust-locales"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b45c49fc4f91f35bae654f85ebb3a44d60ac64f11b3166ffa609def390c732d8"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "0.3.15"
|
||||
|
@ -2174,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"
|
||||
|
@ -2211,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",
|
||||
|
@ -2255,6 +2334,7 @@ dependencies = [
|
|||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"hyper-tls",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
|
@ -2264,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"
|
||||
|
@ -2287,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"
|
||||
|
@ -2324,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"
|
||||
|
@ -2472,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"
|
||||
|
@ -2577,6 +2704,34 @@ dependencies = [
|
|||
"unicode-xid 0.0.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sys-locale"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91f89ebb59fa30d4f65fafc2d68e94f6975256fd87e812dd99cb6e020c8563df"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cstr_core",
|
||||
"libc",
|
||||
"web-sys",
|
||||
"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"
|
||||
|
@ -2725,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"
|
||||
|
@ -2846,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"
|
||||
|
@ -3075,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.
|
||||
|
|
|
@ -50,6 +50,16 @@ dependencies = ["build-windows-resources"]
|
|||
[tasks.build-windows-all]
|
||||
dependencies = ["build-windows-portable", "build-windows-installer"]
|
||||
|
||||
[tasks.sign-windows-resources]
|
||||
env = { "TARGET_SIGNTOOL_FILE" = "target/windows/resources/espansod.exe" }
|
||||
script_runner = "@rust"
|
||||
script = { file = "scripts/sign_windows_exe.rs" }
|
||||
|
||||
[tasks.sign-windows-installer]
|
||||
env = { "TARGET_SIGNTOOL_FILE" = "target/windows/installer/Espanso-Win-Installer-x86_64.exe" }
|
||||
script_runner = "@rust"
|
||||
script = { file = "scripts/sign_windows_exe.rs" }
|
||||
|
||||
# macOS
|
||||
|
||||
[tasks.build-macos-arm-binary]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -28,4 +28,4 @@ widestring = "0.4.3"
|
|||
wait-timeout = { version = "0.2.0", optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0.66"
|
||||
cc = "1.0.73"
|
|
@ -24,7 +24,7 @@ use std::{
|
|||
path::PathBuf,
|
||||
};
|
||||
|
||||
use crate::Clipboard;
|
||||
use crate::{Clipboard, ClipboardOperationOptions};
|
||||
use anyhow::Result;
|
||||
use log::error;
|
||||
use thiserror::Error;
|
||||
|
@ -38,7 +38,7 @@ impl CocoaClipboard {
|
|||
}
|
||||
|
||||
impl Clipboard for CocoaClipboard {
|
||||
fn get_text(&self) -> Option<String> {
|
||||
fn get_text(&self, _: &ClipboardOperationOptions) -> Option<String> {
|
||||
let mut buffer: [i8; 2048] = [0; 2048];
|
||||
let native_result =
|
||||
unsafe { ffi::clipboard_get_text(buffer.as_mut_ptr(), (buffer.len() - 1) as i32) };
|
||||
|
@ -50,7 +50,7 @@ impl Clipboard for CocoaClipboard {
|
|||
}
|
||||
}
|
||||
|
||||
fn set_text(&self, text: &str) -> anyhow::Result<()> {
|
||||
fn set_text(&self, text: &str, _: &ClipboardOperationOptions) -> anyhow::Result<()> {
|
||||
let string = CString::new(text)?;
|
||||
let native_result = unsafe { ffi::clipboard_set_text(string.as_ptr()) };
|
||||
if native_result > 0 {
|
||||
|
@ -60,7 +60,11 @@ impl Clipboard for CocoaClipboard {
|
|||
}
|
||||
}
|
||||
|
||||
fn set_image(&self, image_path: &std::path::Path) -> anyhow::Result<()> {
|
||||
fn set_image(
|
||||
&self,
|
||||
image_path: &std::path::Path,
|
||||
_: &ClipboardOperationOptions,
|
||||
) -> anyhow::Result<()> {
|
||||
if !image_path.exists() || !image_path.is_file() {
|
||||
return Err(CocoaClipboardError::ImageNotFound(image_path.to_path_buf()).into());
|
||||
}
|
||||
|
@ -75,7 +79,12 @@ impl Clipboard for CocoaClipboard {
|
|||
}
|
||||
}
|
||||
|
||||
fn set_html(&self, html: &str, fallback_text: Option<&str>) -> anyhow::Result<()> {
|
||||
fn set_html(
|
||||
&self,
|
||||
html: &str,
|
||||
fallback_text: Option<&str>,
|
||||
_: &ClipboardOperationOptions,
|
||||
) -> anyhow::Result<()> {
|
||||
let html_string = CString::new(html)?;
|
||||
let fallback_string = CString::new(fallback_text.unwrap_or_default())?;
|
||||
let fallback_ptr = if fallback_text.is_some() {
|
||||
|
|
|
@ -30,8 +30,6 @@ int32_t clipboard_get_text(char * buffer, int32_t buffer_size) {
|
|||
const char * text = [string UTF8String];
|
||||
strncpy(buffer, text, buffer_size);
|
||||
|
||||
[string release];
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,10 +37,21 @@ mod wayland;
|
|||
mod cocoa;
|
||||
|
||||
pub trait Clipboard {
|
||||
fn get_text(&self) -> Option<String>;
|
||||
fn set_text(&self, text: &str) -> Result<()>;
|
||||
fn set_image(&self, image_path: &Path) -> Result<()>;
|
||||
fn set_html(&self, html: &str, fallback_text: Option<&str>) -> Result<()>;
|
||||
fn get_text(&self, options: &ClipboardOperationOptions) -> Option<String>;
|
||||
fn set_text(&self, text: &str, options: &ClipboardOperationOptions) -> Result<()>;
|
||||
fn set_image(&self, image_path: &Path, options: &ClipboardOperationOptions) -> Result<()>;
|
||||
fn set_html(
|
||||
&self,
|
||||
html: &str,
|
||||
fallback_text: Option<&str>,
|
||||
options: &ClipboardOperationOptions,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Default)]
|
||||
pub struct ClipboardOperationOptions {
|
||||
pub use_xclip_backend: bool,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
@ -74,8 +85,8 @@ pub fn get_clipboard(_: ClipboardOptions) -> Result<Box<dyn Clipboard>> {
|
|||
#[cfg(target_os = "linux")]
|
||||
#[cfg(not(feature = "wayland"))]
|
||||
pub fn get_clipboard(_: ClipboardOptions) -> Result<Box<dyn Clipboard>> {
|
||||
info!("using X11NativeClipboard");
|
||||
Ok(Box::new(x11::native::X11NativeClipboard::new()?))
|
||||
info!("using X11Clipboard");
|
||||
Ok(Box::new(x11::X11Clipboard::new()?))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
|
|
|
@ -24,7 +24,7 @@ use std::{
|
|||
process::Stdio,
|
||||
};
|
||||
|
||||
use crate::{Clipboard, ClipboardOptions};
|
||||
use crate::{Clipboard, ClipboardOperationOptions, ClipboardOptions};
|
||||
use anyhow::Result;
|
||||
use log::{error, warn};
|
||||
use std::process::Command;
|
||||
|
@ -74,7 +74,7 @@ impl WaylandFallbackClipboard {
|
|||
}
|
||||
|
||||
impl Clipboard for WaylandFallbackClipboard {
|
||||
fn get_text(&self) -> Option<String> {
|
||||
fn get_text(&self, _: &ClipboardOperationOptions) -> Option<String> {
|
||||
let timeout = std::time::Duration::from_millis(self.command_timeout);
|
||||
match Command::new("wl-paste")
|
||||
.arg("--no-newline")
|
||||
|
@ -116,11 +116,15 @@ impl Clipboard for WaylandFallbackClipboard {
|
|||
}
|
||||
}
|
||||
|
||||
fn set_text(&self, text: &str) -> anyhow::Result<()> {
|
||||
fn set_text(&self, text: &str, _: &ClipboardOperationOptions) -> anyhow::Result<()> {
|
||||
self.invoke_command_with_timeout(&mut Command::new("wl-copy"), text.as_bytes(), "wl-copy")
|
||||
}
|
||||
|
||||
fn set_image(&self, image_path: &std::path::Path) -> anyhow::Result<()> {
|
||||
fn set_image(
|
||||
&self,
|
||||
image_path: &std::path::Path,
|
||||
_: &ClipboardOperationOptions,
|
||||
) -> anyhow::Result<()> {
|
||||
if !image_path.exists() || !image_path.is_file() {
|
||||
return Err(WaylandFallbackClipboardError::ImageNotFound(image_path.to_path_buf()).into());
|
||||
}
|
||||
|
@ -131,15 +135,20 @@ impl Clipboard for WaylandFallbackClipboard {
|
|||
file.read_to_end(&mut data)?;
|
||||
|
||||
self.invoke_command_with_timeout(
|
||||
&mut Command::new("wl-copy").arg("--type").arg("image/png"),
|
||||
Command::new("wl-copy").arg("--type").arg("image/png"),
|
||||
&data,
|
||||
"wl-copy",
|
||||
)
|
||||
}
|
||||
|
||||
fn set_html(&self, html: &str, _fallback_text: Option<&str>) -> anyhow::Result<()> {
|
||||
fn set_html(
|
||||
&self,
|
||||
html: &str,
|
||||
_fallback_text: Option<&str>,
|
||||
_: &ClipboardOperationOptions,
|
||||
) -> anyhow::Result<()> {
|
||||
self.invoke_command_with_timeout(
|
||||
&mut Command::new("wl-copy").arg("--type").arg("text/html"),
|
||||
Command::new("wl-copy").arg("--type").arg("text/html"),
|
||||
html.as_bytes(),
|
||||
"wl-copy",
|
||||
)
|
||||
|
|
|
@ -21,7 +21,7 @@ mod ffi;
|
|||
|
||||
use std::{ffi::CString, path::PathBuf};
|
||||
|
||||
use crate::Clipboard;
|
||||
use crate::{Clipboard, ClipboardOperationOptions};
|
||||
use anyhow::Result;
|
||||
use log::error;
|
||||
use thiserror::Error;
|
||||
|
@ -36,7 +36,7 @@ impl Win32Clipboard {
|
|||
}
|
||||
|
||||
impl Clipboard for Win32Clipboard {
|
||||
fn get_text(&self) -> Option<String> {
|
||||
fn get_text(&self, _: &ClipboardOperationOptions) -> Option<String> {
|
||||
let mut buffer: [u16; 2048] = [0; 2048];
|
||||
let native_result =
|
||||
unsafe { ffi::clipboard_get_text(buffer.as_mut_ptr(), (buffer.len() - 1) as i32) };
|
||||
|
@ -48,7 +48,7 @@ impl Clipboard for Win32Clipboard {
|
|||
}
|
||||
}
|
||||
|
||||
fn set_text(&self, text: &str) -> anyhow::Result<()> {
|
||||
fn set_text(&self, text: &str, _: &ClipboardOperationOptions) -> anyhow::Result<()> {
|
||||
let string = U16CString::from_str(text)?;
|
||||
let native_result = unsafe { ffi::clipboard_set_text(string.as_ptr()) };
|
||||
if native_result > 0 {
|
||||
|
@ -58,7 +58,11 @@ impl Clipboard for Win32Clipboard {
|
|||
}
|
||||
}
|
||||
|
||||
fn set_image(&self, image_path: &std::path::Path) -> anyhow::Result<()> {
|
||||
fn set_image(
|
||||
&self,
|
||||
image_path: &std::path::Path,
|
||||
_: &ClipboardOperationOptions,
|
||||
) -> anyhow::Result<()> {
|
||||
if !image_path.exists() || !image_path.is_file() {
|
||||
return Err(Win32ClipboardError::ImageNotFound(image_path.to_path_buf()).into());
|
||||
}
|
||||
|
@ -73,7 +77,12 @@ impl Clipboard for Win32Clipboard {
|
|||
}
|
||||
}
|
||||
|
||||
fn set_html(&self, html: &str, fallback_text: Option<&str>) -> anyhow::Result<()> {
|
||||
fn set_html(
|
||||
&self,
|
||||
html: &str,
|
||||
fallback_text: Option<&str>,
|
||||
_: &ClipboardOperationOptions,
|
||||
) -> anyhow::Result<()> {
|
||||
let html_descriptor = generate_html_descriptor(html);
|
||||
let html_string = CString::new(html_descriptor)?;
|
||||
let fallback_string = U16CString::from_str(fallback_text.unwrap_or_default())?;
|
||||
|
|
|
@ -17,4 +17,66 @@
|
|||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
pub(crate) mod native;
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::{Clipboard, ClipboardOperationOptions};
|
||||
|
||||
mod native;
|
||||
mod xclip;
|
||||
|
||||
pub(crate) struct X11Clipboard {
|
||||
native_backend: native::X11NativeClipboard,
|
||||
xclip_backend: xclip::XClipClipboard,
|
||||
}
|
||||
|
||||
impl X11Clipboard {
|
||||
pub fn new() -> Result<Self> {
|
||||
Ok(Self {
|
||||
native_backend: native::X11NativeClipboard::new()?,
|
||||
xclip_backend: xclip::XClipClipboard::new(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Clipboard for X11Clipboard {
|
||||
fn get_text(&self, options: &ClipboardOperationOptions) -> Option<String> {
|
||||
if options.use_xclip_backend {
|
||||
self.xclip_backend.get_text(options)
|
||||
} else {
|
||||
self.native_backend.get_text(options)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_text(&self, text: &str, options: &ClipboardOperationOptions) -> anyhow::Result<()> {
|
||||
if options.use_xclip_backend {
|
||||
self.xclip_backend.set_text(text, options)
|
||||
} else {
|
||||
self.native_backend.set_text(text, options)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_image(
|
||||
&self,
|
||||
image_path: &std::path::Path,
|
||||
options: &ClipboardOperationOptions,
|
||||
) -> anyhow::Result<()> {
|
||||
if options.use_xclip_backend {
|
||||
self.xclip_backend.set_image(image_path, options)
|
||||
} else {
|
||||
self.native_backend.set_image(image_path, options)
|
||||
}
|
||||
}
|
||||
|
||||
fn set_html(
|
||||
&self,
|
||||
html: &str,
|
||||
fallback_text: Option<&str>,
|
||||
options: &ClipboardOperationOptions,
|
||||
) -> anyhow::Result<()> {
|
||||
if options.use_xclip_backend {
|
||||
self.xclip_backend.set_html(html, fallback_text, options)
|
||||
} else {
|
||||
self.native_backend.set_html(html, fallback_text, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ use std::{
|
|||
path::PathBuf,
|
||||
};
|
||||
|
||||
use crate::Clipboard;
|
||||
use crate::{Clipboard, ClipboardOperationOptions};
|
||||
use anyhow::Result;
|
||||
use std::os::raw::c_char;
|
||||
use thiserror::Error;
|
||||
|
@ -39,7 +39,7 @@ impl X11NativeClipboard {
|
|||
}
|
||||
|
||||
impl Clipboard for X11NativeClipboard {
|
||||
fn get_text(&self) -> Option<String> {
|
||||
fn get_text(&self, _: &ClipboardOperationOptions) -> Option<String> {
|
||||
let mut buffer: [c_char; 2048] = [0; 2048];
|
||||
let native_result =
|
||||
unsafe { ffi::clipboard_x11_get_text(buffer.as_mut_ptr(), (buffer.len() - 1) as i32) };
|
||||
|
@ -51,7 +51,7 @@ impl Clipboard for X11NativeClipboard {
|
|||
}
|
||||
}
|
||||
|
||||
fn set_text(&self, text: &str) -> anyhow::Result<()> {
|
||||
fn set_text(&self, text: &str, _: &ClipboardOperationOptions) -> anyhow::Result<()> {
|
||||
let string = CString::new(text)?;
|
||||
let native_result = unsafe { ffi::clipboard_x11_set_text(string.as_ptr()) };
|
||||
if native_result > 0 {
|
||||
|
@ -61,7 +61,11 @@ impl Clipboard for X11NativeClipboard {
|
|||
}
|
||||
}
|
||||
|
||||
fn set_image(&self, image_path: &std::path::Path) -> anyhow::Result<()> {
|
||||
fn set_image(
|
||||
&self,
|
||||
image_path: &std::path::Path,
|
||||
_: &ClipboardOperationOptions,
|
||||
) -> anyhow::Result<()> {
|
||||
if !image_path.exists() || !image_path.is_file() {
|
||||
return Err(X11NativeClipboardError::ImageNotFound(image_path.to_path_buf()).into());
|
||||
}
|
||||
|
@ -80,7 +84,12 @@ impl Clipboard for X11NativeClipboard {
|
|||
}
|
||||
}
|
||||
|
||||
fn set_html(&self, html: &str, fallback_text: Option<&str>) -> anyhow::Result<()> {
|
||||
fn set_html(
|
||||
&self,
|
||||
html: &str,
|
||||
fallback_text: Option<&str>,
|
||||
_: &ClipboardOperationOptions,
|
||||
) -> anyhow::Result<()> {
|
||||
let html_string = CString::new(html)?;
|
||||
let fallback_string = CString::new(fallback_text.unwrap_or_default())?;
|
||||
let fallback_ptr = if fallback_text.is_some() {
|
||||
|
|
139
espanso-clipboard/src/x11/xclip/mod.rs
Normal file
139
espanso-clipboard/src/x11/xclip/mod.rs
Normal file
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* 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 anyhow::bail;
|
||||
use log::error;
|
||||
use std::io::Write;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
use crate::{Clipboard, ClipboardOperationOptions};
|
||||
|
||||
pub struct XClipClipboard {
|
||||
is_xclip_available: bool,
|
||||
}
|
||||
|
||||
impl XClipClipboard {
|
||||
pub fn new() -> Self {
|
||||
let command = Command::new("xclip").arg("-h").output();
|
||||
let is_xclip_available = command
|
||||
.map(|output| output.status.success())
|
||||
.unwrap_or(false);
|
||||
|
||||
Self { is_xclip_available }
|
||||
}
|
||||
}
|
||||
|
||||
impl Clipboard for XClipClipboard {
|
||||
fn get_text(&self, _: &ClipboardOperationOptions) -> Option<String> {
|
||||
if !self.is_xclip_available {
|
||||
error!("attempted to use XClipClipboard, but `xclip` command can't be called");
|
||||
return None;
|
||||
}
|
||||
|
||||
match Command::new("xclip").args(&["-o", "-sel", "clip"]).output() {
|
||||
Ok(output) => {
|
||||
if output.status.success() {
|
||||
let s = String::from_utf8_lossy(&output.stdout);
|
||||
return Some(s.to_string());
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
error!("xclip reported an error: {}", error);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn set_text(&self, text: &str, _: &ClipboardOperationOptions) -> anyhow::Result<()> {
|
||||
if !self.is_xclip_available {
|
||||
bail!("attempted to use XClipClipboard, but `xclip` command can't be called");
|
||||
}
|
||||
|
||||
let mut child = Command::new("xclip")
|
||||
.args(&["-sel", "clip"])
|
||||
.stdin(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
let stdin = child.stdin.as_mut();
|
||||
if let Some(input) = stdin {
|
||||
input.write_all(text.as_bytes())?;
|
||||
child.wait()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_image(
|
||||
&self,
|
||||
image_path: &std::path::Path,
|
||||
_: &ClipboardOperationOptions,
|
||||
) -> anyhow::Result<()> {
|
||||
if !self.is_xclip_available {
|
||||
bail!("attempted to use XClipClipboard, but `xclip` command can't be called");
|
||||
}
|
||||
|
||||
let extension = image_path.extension();
|
||||
let mime = match extension {
|
||||
Some(ext) => {
|
||||
let ext = ext.to_string_lossy().to_lowercase();
|
||||
match ext.as_ref() {
|
||||
"png" => "image/png",
|
||||
"jpg" | "jpeg" => "image/jpeg",
|
||||
"gif" => "image/gif",
|
||||
"svg" => "image/svg",
|
||||
_ => "image/png",
|
||||
}
|
||||
}
|
||||
None => "image/png",
|
||||
};
|
||||
|
||||
let image_path = image_path.to_string_lossy();
|
||||
|
||||
Command::new("xclip")
|
||||
.args(&["-selection", "clipboard", "-t", mime, "-i", &image_path])
|
||||
.spawn()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_html(
|
||||
&self,
|
||||
html: &str,
|
||||
_: Option<&str>,
|
||||
_: &ClipboardOperationOptions,
|
||||
) -> anyhow::Result<()> {
|
||||
if !self.is_xclip_available {
|
||||
bail!("attempted to use XClipClipboard, but `xclip` command can't be called");
|
||||
}
|
||||
|
||||
let mut child = Command::new("xclip")
|
||||
.args(&["-sel", "clip", "-t", "text/html"])
|
||||
.stdin(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
let stdin = child.stdin.as_mut();
|
||||
if let Some(input) = stdin {
|
||||
input.write_all(html.as_bytes())?;
|
||||
child.wait()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -21,3 +21,5 @@ pub(crate) const DEFAULT_CLIPBOARD_THRESHOLD: usize = 100;
|
|||
pub(crate) const DEFAULT_PRE_PASTE_DELAY: usize = 100;
|
||||
pub(crate) const DEFAULT_SHORTCUT_EVENT_DELAY: usize = 10;
|
||||
pub(crate) const DEFAULT_RESTORE_CLIPBOARD_DELAY: usize = 300;
|
||||
pub(crate) const DEFAULT_POST_FORM_DELAY: usize = 200;
|
||||
pub(crate) const DEFAULT_POST_SEARCH_DELAY: usize = 200;
|
||||
|
|
|
@ -150,6 +150,34 @@ pub trait Config: Send + Sync {
|
|||
// If false, avoid showing the SecureInput notification on macOS
|
||||
fn secure_input_notification(&self) -> bool;
|
||||
|
||||
// The number of milliseconds to wait after a form has been closed.
|
||||
// This is useful to let the target application regain focus
|
||||
// after a form has been closed, otherwise the injection might
|
||||
// not be targeted to the right application.
|
||||
fn post_form_delay(&self) -> usize;
|
||||
|
||||
// The number of milliseconds to wait after the search bar has been closed.
|
||||
// This is useful to let the target application regain focus
|
||||
// after the search bar has been closed, otherwise the injection might
|
||||
// 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.
|
||||
|
@ -184,6 +212,8 @@ pub trait Config: Send + Sync {
|
|||
toggle_key: {:?}
|
||||
auto_restart: {:?}
|
||||
restore_clipboard_delay: {:?}
|
||||
post_form_delay: {:?}
|
||||
post_search_delay: {:?}
|
||||
backspace_limit: {}
|
||||
search_trigger: {:?}
|
||||
search_shortcut: {:?}
|
||||
|
@ -193,6 +223,8 @@ pub trait Config: Send + Sync {
|
|||
show_notifications: {:?}
|
||||
secure_input_notification: {:?}
|
||||
|
||||
x11_use_xclip_backend: {:?}
|
||||
x11_use_xdotool_backend: {:?}
|
||||
win32_exclude_orphan_events: {:?}
|
||||
win32_keyboard_layout_cache_interval: {:?}
|
||||
|
||||
|
@ -215,6 +247,8 @@ pub trait Config: Send + Sync {
|
|||
self.toggle_key(),
|
||||
self.auto_restart(),
|
||||
self.restore_clipboard_delay(),
|
||||
self.post_form_delay(),
|
||||
self.post_search_delay(),
|
||||
self.backspace_limit(),
|
||||
self.search_trigger(),
|
||||
self.search_shortcut(),
|
||||
|
@ -224,6 +258,8 @@ pub trait Config: Send + Sync {
|
|||
self.show_notifications(),
|
||||
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(),
|
||||
|
||||
|
|
|
@ -44,8 +44,13 @@ pub(crate) struct ParsedConfig {
|
|||
pub show_notifications: Option<bool>,
|
||||
pub show_icon: Option<bool>,
|
||||
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>,
|
||||
|
|
|
@ -103,15 +103,30 @@ pub(crate) struct YAMLConfig {
|
|||
#[serde(default)]
|
||||
pub show_icon: Option<bool>,
|
||||
|
||||
#[serde(default)]
|
||||
pub post_form_delay: Option<usize>,
|
||||
|
||||
#[serde(default)]
|
||||
pub post_search_delay: Option<usize>,
|
||||
|
||||
#[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>,
|
||||
|
||||
#[serde(default)]
|
||||
pub win32_keyboard_layout_cache_interval: Option<i64>,
|
||||
|
||||
#[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>>,
|
||||
|
@ -198,9 +213,15 @@ impl TryFrom<YAMLConfig> for ParsedConfig {
|
|||
pre_paste_delay: yaml_config.pre_paste_delay,
|
||||
restore_clipboard_delay: yaml_config.restore_clipboard_delay,
|
||||
paste_shortcut_event_delay: yaml_config.paste_shortcut_event_delay,
|
||||
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,
|
||||
|
@ -256,8 +277,13 @@ mod tests {
|
|||
show_icon: false
|
||||
show_notifications: false
|
||||
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"]
|
||||
|
@ -309,8 +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),
|
||||
|
|
|
@ -19,8 +19,8 @@
|
|||
|
||||
use super::{
|
||||
default::{
|
||||
DEFAULT_CLIPBOARD_THRESHOLD, DEFAULT_PRE_PASTE_DELAY, DEFAULT_RESTORE_CLIPBOARD_DELAY,
|
||||
DEFAULT_SHORTCUT_EVENT_DELAY,
|
||||
DEFAULT_CLIPBOARD_THRESHOLD, DEFAULT_POST_FORM_DELAY, DEFAULT_POST_SEARCH_DELAY,
|
||||
DEFAULT_PRE_PASTE_DELAY, DEFAULT_RESTORE_CLIPBOARD_DELAY, DEFAULT_SHORTCUT_EVENT_DELAY,
|
||||
},
|
||||
parse::ParsedConfig,
|
||||
path::calculate_paths,
|
||||
|
@ -37,7 +37,7 @@ use thiserror::Error;
|
|||
|
||||
const STANDARD_INCLUDES: &[&str] = &["../match/**/[!_]*.yml"];
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct ResolvedConfig {
|
||||
parsed: ParsedConfig,
|
||||
|
||||
|
@ -52,20 +52,6 @@ pub(crate) struct ResolvedConfig {
|
|||
filter_exec: Option<Regex>,
|
||||
}
|
||||
|
||||
impl Default for ResolvedConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
parsed: Default::default(),
|
||||
source_path: None,
|
||||
id: 0,
|
||||
match_paths: Vec::new(),
|
||||
filter_title: None,
|
||||
filter_class: None,
|
||||
filter_exec: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config for ResolvedConfig {
|
||||
fn id(&self) -> i32 {
|
||||
self.id
|
||||
|
@ -202,13 +188,10 @@ impl Config for ResolvedConfig {
|
|||
Some("left_shift") => Some(ToggleKey::LeftShift),
|
||||
Some("left_meta") | Some("left_cmd") => Some(ToggleKey::LeftMeta),
|
||||
Some("off") => None,
|
||||
None => Some(ToggleKey::Alt),
|
||||
None => None,
|
||||
err => {
|
||||
error!(
|
||||
"invalid toggle_key specified {:?}, falling back to ALT",
|
||||
err
|
||||
);
|
||||
Some(ToggleKey::Alt)
|
||||
error!("invalid toggle_key specified {:?}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -288,7 +271,7 @@ impl Config for ResolvedConfig {
|
|||
match self.parsed.search_trigger.as_deref() {
|
||||
Some("OFF") | Some("off") => None,
|
||||
Some(x) => Some(x.to_string()),
|
||||
None => Some("jkj".to_string()),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -316,6 +299,24 @@ 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
|
||||
.post_form_delay
|
||||
.unwrap_or(DEFAULT_POST_FORM_DELAY)
|
||||
}
|
||||
|
||||
fn post_search_delay(&self) -> usize {
|
||||
self
|
||||
.parsed
|
||||
.post_search_delay
|
||||
.unwrap_or(DEFAULT_POST_SEARCH_DELAY)
|
||||
}
|
||||
|
||||
fn win32_exclude_orphan_events(&self) -> bool {
|
||||
self.parsed.win32_exclude_orphan_events.unwrap_or(true)
|
||||
}
|
||||
|
@ -330,6 +331,14 @@ impl Config for ResolvedConfig {
|
|||
.win32_keyboard_layout_cache_interval
|
||||
.unwrap_or(2000)
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -411,8 +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,
|
||||
|
@ -701,18 +701,15 @@ impl LegacyConfigSet {
|
|||
let mut sorted_triggers: Vec<String> = default
|
||||
.matches
|
||||
.iter()
|
||||
.flat_map(|t| triggers_for_match(t))
|
||||
.flat_map(triggers_for_match)
|
||||
.collect();
|
||||
sorted_triggers.sort();
|
||||
|
||||
let mut has_conflicts = Self::list_has_conflicts(&sorted_triggers);
|
||||
|
||||
for s in specific.iter() {
|
||||
let mut specific_triggers: Vec<String> = s
|
||||
.matches
|
||||
.iter()
|
||||
.flat_map(|t| triggers_for_match(t))
|
||||
.collect();
|
||||
let mut specific_triggers: Vec<String> =
|
||||
s.matches.iter().flat_map(triggers_for_match).collect();
|
||||
specific_triggers.sort();
|
||||
has_conflicts |= Self::list_has_conflicts(&specific_triggers);
|
||||
}
|
||||
|
@ -743,7 +740,7 @@ impl LegacyConfigSet {
|
|||
}
|
||||
|
||||
// Error handling
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[allow(dead_code)]
|
||||
pub enum ConfigLoadError {
|
||||
FileNotFound,
|
||||
|
|
|
@ -387,6 +387,18 @@ impl Config for LegacyInteropConfig {
|
|||
self.config.enable_active
|
||||
}
|
||||
|
||||
fn post_form_delay(&self) -> usize {
|
||||
crate::config::default::DEFAULT_POST_FORM_DELAY
|
||||
}
|
||||
|
||||
fn post_search_delay(&self) -> usize {
|
||||
crate::config::default::DEFAULT_POST_SEARCH_DELAY
|
||||
}
|
||||
|
||||
fn emulate_alt_codes(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn win32_exclude_orphan_events(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
@ -398,6 +410,14 @@ impl Config for LegacyInteropConfig {
|
|||
fn win32_keyboard_layout_cache_interval(&self) -> i64 {
|
||||
2000
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
|
@ -300,6 +300,7 @@ pub fn try_convert_into_match(
|
|||
effect,
|
||||
label: yaml_match.label,
|
||||
id: next_id(),
|
||||
search_terms: yaml_match.search_terms.unwrap_or_default(),
|
||||
},
|
||||
warnings,
|
||||
))
|
||||
|
@ -316,6 +317,7 @@ pub fn try_convert_into_variable(
|
|||
params: convert_params(yaml_var.params)?,
|
||||
id: next_id(),
|
||||
inject_vars: !use_compatibility_mode && yaml_var.inject_vars.unwrap_or(true),
|
||||
depends_on: yaml_var.depends_on,
|
||||
},
|
||||
Vec::new(),
|
||||
))
|
||||
|
@ -745,6 +747,52 @@ mod tests {
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vars_inject_vars_and_depends_on() {
|
||||
let vars = vec![
|
||||
Variable {
|
||||
name: "var1".to_string(),
|
||||
var_type: "test".to_string(),
|
||||
depends_on: vec!["test".to_owned()],
|
||||
..Default::default()
|
||||
},
|
||||
Variable {
|
||||
name: "var2".to_string(),
|
||||
var_type: "test".to_string(),
|
||||
inject_vars: false,
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
assert_eq!(
|
||||
create_match(
|
||||
r#"
|
||||
trigger: "Hello"
|
||||
replace: "world"
|
||||
vars:
|
||||
- name: var1
|
||||
type: test
|
||||
depends_on: ["test"]
|
||||
- name: var2
|
||||
type: "test"
|
||||
inject_vars: false
|
||||
"#
|
||||
)
|
||||
.unwrap(),
|
||||
Match {
|
||||
cause: MatchCause::Trigger(TriggerCause {
|
||||
triggers: vec!["Hello".to_string()],
|
||||
..Default::default()
|
||||
}),
|
||||
effect: MatchEffect::Text(TextEffect {
|
||||
replace: "world".to_string(),
|
||||
vars,
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vars_no_params_maps_correctly() {
|
||||
let vars = vec![Variable {
|
||||
|
|
|
@ -114,9 +114,12 @@ pub struct YAMLMatch {
|
|||
|
||||
#[serde(default)]
|
||||
pub html: Option<String>,
|
||||
|
||||
#[serde(default)]
|
||||
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,
|
||||
|
||||
|
@ -128,6 +131,9 @@ pub struct YAMLVariable {
|
|||
|
||||
#[serde(default)]
|
||||
pub inject_vars: Option<bool>,
|
||||
|
||||
#[serde(default)]
|
||||
pub depends_on: Vec<String>,
|
||||
}
|
||||
|
||||
fn default_params() -> Mapping {
|
||||
|
|
|
@ -27,23 +27,13 @@ use super::{Match, Variable};
|
|||
pub(crate) mod loader;
|
||||
mod path;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub(crate) struct MatchGroup {
|
||||
pub imports: Vec<String>,
|
||||
pub global_vars: Vec<Variable>,
|
||||
pub matches: Vec<Match>,
|
||||
}
|
||||
|
||||
impl Default for MatchGroup {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
imports: Vec::new(),
|
||||
global_vars: Vec::new(),
|
||||
matches: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MatchGroup {
|
||||
// TODO: test
|
||||
pub fn load(group_path: &Path) -> Result<(Self, Option<NonFatalErrorSet>)> {
|
||||
|
|
|
@ -35,6 +35,7 @@ pub struct Match {
|
|||
|
||||
// Metadata
|
||||
pub label: Option<String>,
|
||||
pub search_terms: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for Match {
|
||||
|
@ -44,6 +45,7 @@ impl Default for Match {
|
|||
effect: MatchEffect::None,
|
||||
label: None,
|
||||
id: 0,
|
||||
search_terms: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,6 +68,15 @@ impl Match {
|
|||
pub fn cause_description(&self) -> Option<&str> {
|
||||
self.cause.description()
|
||||
}
|
||||
|
||||
pub fn search_terms(&self) -> Vec<&str> {
|
||||
self
|
||||
.search_terms
|
||||
.iter()
|
||||
.map(|term| term.as_str())
|
||||
.chain(self.cause.search_terms())
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
// Causes
|
||||
|
@ -100,6 +111,14 @@ impl MatchCause {
|
|||
// TODO: insert rendering for hotkey/shortcut
|
||||
// TODO: insert rendering for regex? I'm worried it might be too long
|
||||
}
|
||||
|
||||
pub fn search_terms(&self) -> Vec<&str> {
|
||||
if let MatchCause::Trigger(trigger_cause) = &self {
|
||||
trigger_cause.triggers.iter().map(|s| s.as_str()).collect()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
|
@ -132,19 +151,11 @@ pub enum UpperCasingStyle {
|
|||
CapitalizeWords,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
|
||||
pub struct RegexCause {
|
||||
pub regex: String,
|
||||
}
|
||||
|
||||
impl Default for RegexCause {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
regex: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Effects
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, EnumAsInner)]
|
||||
|
@ -186,19 +197,11 @@ impl Default for TextEffect {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
|
||||
pub struct ImageEffect {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
impl Default for ImageEffect {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
path: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Variable {
|
||||
pub id: StructId,
|
||||
|
@ -206,6 +209,7 @@ pub struct Variable {
|
|||
pub var_type: String,
|
||||
pub params: Params,
|
||||
pub inject_vars: bool,
|
||||
pub depends_on: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for Variable {
|
||||
|
@ -216,6 +220,7 @@ impl Default for Variable {
|
|||
var_type: String::new(),
|
||||
params: Params::new(),
|
||||
inject_vars: true,
|
||||
depends_on: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -27,7 +27,7 @@ scopeguard = "1.1.0"
|
|||
sctk = { package = "smithay-client-toolkit", version = "0.14.0", optional = true }
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0.66"
|
||||
cc = "1.0.73"
|
||||
|
||||
[dev-dependencies]
|
||||
enum-as-inner = "0.3.3"
|
|
@ -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,
|
||||
|
|
|
@ -37,7 +37,6 @@ pub fn get_active_layout() -> Option<String> {
|
|||
if gnome::is_gnome() {
|
||||
gnome::get_active_layout()
|
||||
} else {
|
||||
log::warn!("unable to determine the currently active layout, you might need to explicitly specify the layout in the config for espanso to work correctly.");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ use log::{error, trace, warn};
|
|||
use anyhow::Result;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::event::{HotKeyEvent, InputEvent, Key, KeyboardEvent, Variant};
|
||||
use crate::event::{HotKeyEvent, InputEvent, Key, KeyboardEvent, Status, Variant};
|
||||
use crate::event::{Key::*, MouseButton, MouseEvent};
|
||||
use crate::{event::Status::*, Source, SourceCallback};
|
||||
use crate::{event::Variant::*, hotkey::HotKey};
|
||||
|
@ -50,6 +50,7 @@ const INPUT_MOUSE_MIDDLE_BUTTON: i32 = 3;
|
|||
|
||||
// Take a look at the native.h header file for an explanation of the fields
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct RawInputEvent {
|
||||
pub event_type: i32,
|
||||
|
||||
|
@ -58,6 +59,12 @@ pub struct RawInputEvent {
|
|||
|
||||
pub key_code: i32,
|
||||
pub status: i32,
|
||||
|
||||
pub is_caps_lock_pressed: i32,
|
||||
pub is_shift_pressed: i32,
|
||||
pub is_control_pressed: i32,
|
||||
pub is_option_pressed: i32,
|
||||
pub is_command_pressed: i32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
|
@ -82,15 +89,97 @@ extern "C" {
|
|||
);
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct ModifierState {
|
||||
is_ctrl_down: bool,
|
||||
is_shift_down: bool,
|
||||
is_command_down: bool,
|
||||
is_option_down: bool,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref CURRENT_SENDER: Arc<Mutex<Option<Sender<InputEvent>>>> = Arc::new(Mutex::new(None));
|
||||
static ref MODIFIER_STATE: Arc<Mutex<ModifierState>> =
|
||||
Arc::new(Mutex::new(ModifierState::default()));
|
||||
}
|
||||
|
||||
extern "C" fn native_callback(raw_event: RawInputEvent) {
|
||||
let lock = CURRENT_SENDER
|
||||
.lock()
|
||||
.expect("unable to acquire CocoaSource sender lock");
|
||||
|
||||
// Most of the times, when pressing a modifier key (such as Alt, Ctrl, Shift, Cmd),
|
||||
// we get both a Pressed and Released event. This is important to keep Espanso's
|
||||
// internal representation of modifiers in sync.
|
||||
// Unfortunately, there are times when the corresponding "release" event is not sent,
|
||||
// and this causes Espanso to mistakenly think that the modifier is still pressed.
|
||||
// This can happen for various reasons, such as when using external bluetooth keyboards
|
||||
// or certain keyboard shortcuts.
|
||||
// Luckily, most key events include the "modifiers flag" information, that tells us which
|
||||
// modifier keys were currently pressed at that time.
|
||||
// We use this modifier flag information to detect "inconsistent" states to send the corresponding
|
||||
// modifier release events, keeping espanso's state in sync.
|
||||
// For more info, see:
|
||||
// https://github.com/federico-terzi/espanso/issues/825
|
||||
// https://github.com/federico-terzi/espanso/issues/858
|
||||
let mut compensating_events = Vec::new();
|
||||
if raw_event.event_type == INPUT_EVENT_TYPE_KEYBOARD {
|
||||
let (key_code, _) = key_code_to_key(raw_event.key_code);
|
||||
let mut current_mod_state = MODIFIER_STATE
|
||||
.lock()
|
||||
.expect("unable to acquire modifier state in cocoa detector");
|
||||
|
||||
if let Key::Alt = &key_code {
|
||||
current_mod_state.is_option_down = raw_event.status == INPUT_STATUS_PRESSED;
|
||||
} else if let Key::Meta = &key_code {
|
||||
current_mod_state.is_command_down = raw_event.status == INPUT_STATUS_PRESSED;
|
||||
} else if let Key::Shift = &key_code {
|
||||
current_mod_state.is_shift_down = raw_event.status == INPUT_STATUS_PRESSED;
|
||||
} else if let Key::Control = &key_code {
|
||||
current_mod_state.is_ctrl_down = raw_event.status == INPUT_STATUS_PRESSED;
|
||||
} else {
|
||||
if current_mod_state.is_command_down && raw_event.is_command_pressed == 0 {
|
||||
compensating_events.push((Key::Meta, 0x37));
|
||||
current_mod_state.is_command_down = false;
|
||||
}
|
||||
if current_mod_state.is_ctrl_down && raw_event.is_control_pressed == 0 {
|
||||
compensating_events.push((Key::Control, 0x3B));
|
||||
current_mod_state.is_ctrl_down = false;
|
||||
}
|
||||
if current_mod_state.is_shift_down && raw_event.is_shift_pressed == 0 {
|
||||
compensating_events.push((Key::Shift, 0x38));
|
||||
current_mod_state.is_shift_down = false;
|
||||
}
|
||||
if current_mod_state.is_option_down && raw_event.is_option_pressed == 0 {
|
||||
compensating_events.push((Key::Alt, 0x3A));
|
||||
current_mod_state.is_option_down = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !compensating_events.is_empty() {
|
||||
warn!(
|
||||
"detected inconsistent modifier state for keys {:?}, sending compensating events...",
|
||||
compensating_events
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(sender) = lock.as_ref() {
|
||||
for (key, code) in compensating_events {
|
||||
if let Err(error) = sender.send(InputEvent::Keyboard(KeyboardEvent {
|
||||
key,
|
||||
value: None,
|
||||
status: Status::Released,
|
||||
variant: None,
|
||||
code,
|
||||
})) {
|
||||
error!(
|
||||
"Unable to send compensating event to Cocoa Sender: {}",
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let event: Option<InputEvent> = raw_event.into();
|
||||
if let Some(event) = event {
|
||||
if let Err(error) = sender.send(event) {
|
||||
|
@ -359,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),
|
||||
}
|
||||
|
@ -386,6 +487,11 @@ mod tests {
|
|||
buffer_len: 0,
|
||||
key_code: 0,
|
||||
status: INPUT_STATUS_PRESSED,
|
||||
is_caps_lock_pressed: 0,
|
||||
is_shift_pressed: 0,
|
||||
is_control_pressed: 0,
|
||||
is_option_pressed: 0,
|
||||
is_command_pressed: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -52,6 +52,16 @@ typedef struct {
|
|||
|
||||
// Pressed or Released status
|
||||
int32_t status;
|
||||
|
||||
// Modifier keys status, this is needed to "correct" missing modifier release events.
|
||||
// For more info, see the following issues:
|
||||
// https://github.com/federico-terzi/espanso/issues/825
|
||||
// https://github.com/federico-terzi/espanso/issues/858
|
||||
int32_t is_caps_lock_pressed;
|
||||
int32_t is_shift_pressed;
|
||||
int32_t is_control_pressed;
|
||||
int32_t is_option_pressed;
|
||||
int32_t is_command_pressed;
|
||||
} InputEvent;
|
||||
|
||||
typedef void (*EventCallback)(InputEvent data);
|
||||
|
|
|
@ -76,6 +76,19 @@ void * detect_initialize(EventCallback callback, InitializeOptions options) {
|
|||
strncpy(inputEvent.buffer, chars, 23);
|
||||
inputEvent.buffer_len = event.characters.length;
|
||||
|
||||
// We also send the modifier key status to "correct" missing modifier release events
|
||||
if (([event modifierFlags] & NSEventModifierFlagShift) != 0) {
|
||||
inputEvent.is_shift_pressed = 1;
|
||||
} else if (([event modifierFlags] & NSEventModifierFlagControl) != 0) {
|
||||
inputEvent.is_control_pressed = 1;
|
||||
} else if (([event modifierFlags] & NSEventModifierFlagCapsLock) != 0) {
|
||||
inputEvent.is_caps_lock_pressed = 1;
|
||||
} else if (([event modifierFlags] & NSEventModifierFlagOption) != 0) {
|
||||
inputEvent.is_option_pressed = 1;
|
||||
} else if (([event modifierFlags] & NSEventModifierFlagCommand) != 0) {
|
||||
inputEvent.is_command_pressed = 1;
|
||||
}
|
||||
|
||||
callback(inputEvent);
|
||||
}else if (event.type == NSEventTypeLeftMouseDown || event.type == NSEventTypeRightMouseDown || event.type == NSEventTypeOtherMouseDown ||
|
||||
event.type == NSEventTypeLeftMouseUp || event.type == NSEventTypeRightMouseUp || event.type == NSEventTypeOtherMouseUp) {
|
||||
|
@ -106,6 +119,20 @@ void * detect_initialize(EventCallback callback, InitializeOptions options) {
|
|||
} else if (event.keyCode == kVK_Option || event.keyCode == kVK_RightOption) {
|
||||
inputEvent.status = (([event modifierFlags] & NSEventModifierFlagOption) == 0) ? INPUT_STATUS_RELEASED : INPUT_STATUS_PRESSED;
|
||||
}
|
||||
|
||||
// We also send the modifier key status to "correct" missing modifier release events
|
||||
if (([event modifierFlags] & NSEventModifierFlagShift) != 0) {
|
||||
inputEvent.is_shift_pressed = 1;
|
||||
} else if (([event modifierFlags] & NSEventModifierFlagControl) != 0) {
|
||||
inputEvent.is_control_pressed = 1;
|
||||
} else if (([event modifierFlags] & NSEventModifierFlagCapsLock) != 0) {
|
||||
inputEvent.is_caps_lock_pressed = 1;
|
||||
} else if (([event modifierFlags] & NSEventModifierFlagOption) != 0) {
|
||||
inputEvent.is_option_pressed = 1;
|
||||
} else if (([event modifierFlags] & NSEventModifierFlagCommand) != 0) {
|
||||
inputEvent.is_command_pressed = 1;
|
||||
}
|
||||
|
||||
callback(inputEvent);
|
||||
}
|
||||
}];
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use super::{ContextMenuHandler, Event, IconHandler, ImageInjector, SecureInputManager};
|
||||
use super::{
|
||||
ContextMenuHandler, Event, IconHandler, ImageInjector, SecureInputManager, TextUIHandler,
|
||||
};
|
||||
use super::{Dispatcher, Executor, HtmlInjector, KeyInjector, ModeProvider, TextInjector};
|
||||
|
||||
pub struct DefaultDispatcher<'a> {
|
||||
|
@ -36,6 +38,7 @@ impl<'a> DefaultDispatcher<'a> {
|
|||
context_menu_handler: &'a dyn ContextMenuHandler,
|
||||
icon_handler: &'a dyn IconHandler,
|
||||
secure_input_manager: &'a dyn SecureInputManager,
|
||||
text_ui_handler: &'a dyn TextUIHandler,
|
||||
) -> Self {
|
||||
Self {
|
||||
executors: vec![
|
||||
|
@ -62,6 +65,9 @@ impl<'a> DefaultDispatcher<'a> {
|
|||
Box::new(super::executor::secure_input::SecureInputExecutor::new(
|
||||
secure_input_manager,
|
||||
)),
|
||||
Box::new(super::executor::text_ui::TextUIExecutor::new(
|
||||
text_ui_handler,
|
||||
)),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,3 +24,4 @@ pub mod image_inject;
|
|||
pub mod key_inject;
|
||||
pub mod secure_input;
|
||||
pub mod text_inject;
|
||||
pub mod text_ui;
|
||||
|
|
63
espanso-engine/src/dispatch/executor/text_ui.rs
Normal file
63
espanso-engine/src/dispatch/executor/text_ui.rs
Normal file
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 crate::event::EventType;
|
||||
use crate::{dispatch::Executor, event::Event};
|
||||
use anyhow::Result;
|
||||
use log::error;
|
||||
|
||||
pub trait TextUIHandler {
|
||||
fn show_text(&self, title: &str, text: &str) -> Result<()>;
|
||||
fn show_logs(&self) -> Result<()>;
|
||||
}
|
||||
|
||||
pub struct TextUIExecutor<'a> {
|
||||
handler: &'a dyn TextUIHandler,
|
||||
}
|
||||
|
||||
impl<'a> TextUIExecutor<'a> {
|
||||
pub fn new(handler: &'a dyn TextUIHandler) -> Self {
|
||||
Self { handler }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Executor for TextUIExecutor<'a> {
|
||||
fn execute(&self, event: &Event) -> bool {
|
||||
if let EventType::ShowText(show_text_event) = &event.etype {
|
||||
if let Err(error) = self
|
||||
.handler
|
||||
.show_text(&show_text_event.title, &show_text_event.text)
|
||||
{
|
||||
error!("text UI handler reported an error: {:?}", error);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else if let EventType::ShowLogs = &event.etype {
|
||||
if let Err(error) = self.handler.show_logs() {
|
||||
error!("text UI handler reported an error: {:?}", error);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: test
|
|
@ -38,6 +38,7 @@ pub use executor::image_inject::ImageInjector;
|
|||
pub use executor::key_inject::KeyInjector;
|
||||
pub use executor::secure_input::SecureInputManager;
|
||||
pub use executor::text_inject::{Mode, ModeProvider, TextInjector};
|
||||
pub use executor::text_ui::{TextUIExecutor, TextUIHandler};
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn default<'a>(
|
||||
|
@ -50,6 +51,7 @@ pub fn default<'a>(
|
|||
context_menu_handler: &'a dyn ContextMenuHandler,
|
||||
icon_handler: &'a dyn IconHandler,
|
||||
secure_input_manager: &'a dyn SecureInputManager,
|
||||
text_ui_handler: &'a dyn TextUIHandler,
|
||||
) -> impl Dispatcher + 'a {
|
||||
default::DefaultDispatcher::new(
|
||||
event_injector,
|
||||
|
@ -61,5 +63,6 @@ pub fn default<'a>(
|
|||
context_menu_handler,
|
||||
icon_handler,
|
||||
secure_input_manager,
|
||||
text_ui_handler,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -101,6 +101,8 @@ pub enum EventType {
|
|||
IconStatusChange(ui::IconStatusChangeEvent),
|
||||
DisplaySecureInputTroubleshoot,
|
||||
ShowSearchBar,
|
||||
ShowText(ui::ShowTextEvent),
|
||||
ShowLogs,
|
||||
|
||||
// Other
|
||||
LaunchSecureInputAutoFix,
|
||||
|
|
|
@ -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,14 +41,20 @@ 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, 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,
|
||||
}
|
||||
}
|
|
@ -32,6 +32,7 @@ const CONTEXT_ITEM_DISABLE: u32 = 3;
|
|||
const CONTEXT_ITEM_SECURE_INPUT_EXPLAIN: u32 = 4;
|
||||
const CONTEXT_ITEM_SECURE_INPUT_TRIGGER_WORKAROUND: u32 = 5;
|
||||
const CONTEXT_ITEM_OPEN_SEARCH: u32 = 6;
|
||||
const CONTEXT_ITEM_SHOW_LOGS: u32 = 7;
|
||||
|
||||
pub struct ContextMenuMiddleware {
|
||||
is_enabled: RefCell<bool>,
|
||||
|
@ -82,6 +83,10 @@ impl Middleware for ContextMenuMiddleware {
|
|||
id: CONTEXT_ITEM_RELOAD,
|
||||
label: "Reload config".to_string(),
|
||||
}),
|
||||
MenuItem::Simple(SimpleMenuItem {
|
||||
id: CONTEXT_ITEM_SHOW_LOGS,
|
||||
label: "Show logs".to_string(),
|
||||
}),
|
||||
MenuItem::Separator,
|
||||
MenuItem::Simple(SimpleMenuItem {
|
||||
id: CONTEXT_ITEM_EXIT,
|
||||
|
@ -94,7 +99,7 @@ impl Middleware for ContextMenuMiddleware {
|
|||
0,
|
||||
MenuItem::Simple(SimpleMenuItem {
|
||||
id: CONTEXT_ITEM_SECURE_INPUT_EXPLAIN,
|
||||
label: "Why is espanso not working?".to_string(),
|
||||
label: "Why is Espanso not working?".to_string(),
|
||||
}),
|
||||
);
|
||||
items.insert(
|
||||
|
@ -155,6 +160,10 @@ impl Middleware for ContextMenuMiddleware {
|
|||
dispatch(Event::caused_by(event.source_id, EventType::ShowSearchBar));
|
||||
Event::caused_by(event.source_id, EventType::NOOP)
|
||||
}
|
||||
CONTEXT_ITEM_SHOW_LOGS => {
|
||||
dispatch(Event::caused_by(event.source_id, EventType::ShowLogs));
|
||||
Event::caused_by(event.source_id, EventType::NOOP)
|
||||
}
|
||||
_ => {
|
||||
// TODO: handle dynamic items
|
||||
todo!()
|
||||
|
|
|
@ -47,7 +47,7 @@ impl<'a> Middleware for ImageResolverMiddleware<'a> {
|
|||
if let EventType::ImageRequested(m_event) = &event.etype {
|
||||
// On Windows, we have to replace the forward / with the backslash \ in the path
|
||||
let path = if cfg!(target_os = "windows") {
|
||||
m_event.image_path.replace("/", "\\")
|
||||
m_event.image_path.replace('/', "\\")
|
||||
} else {
|
||||
m_event.image_path.to_owned()
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -20,4 +20,4 @@ lazy_static = "1.4.0"
|
|||
widestring = "0.4.3"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0.66"
|
||||
cc = "1.0.73"
|
|
@ -23,29 +23,27 @@
|
|||
|
||||
int32_t info_get_title(char *buffer, int32_t buffer_size)
|
||||
{
|
||||
@autoreleasepool {
|
||||
CFArrayRef windows = CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements | kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
|
||||
int32_t result = 0;
|
||||
CFArrayRef windows = CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements | kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
|
||||
int32_t result = 0;
|
||||
|
||||
if (windows) {
|
||||
for (NSDictionary *window in (NSArray *)windows) {
|
||||
NSNumber *ownerPid = window[(id) kCGWindowOwnerPID];
|
||||
if (windows) {
|
||||
for (NSDictionary *window in (NSArray *)windows) {
|
||||
NSNumber *ownerPid = window[(id) kCGWindowOwnerPID];
|
||||
|
||||
NSRunningApplication *currentApp = [NSRunningApplication runningApplicationWithProcessIdentifier: [ownerPid intValue]];
|
||||
NSRunningApplication *currentApp = [NSRunningApplication runningApplicationWithProcessIdentifier: [ownerPid intValue]];
|
||||
|
||||
if ([currentApp isActive]) {
|
||||
NSString *name = window[(id) kCGWindowName];
|
||||
if (name.length > 0) {
|
||||
const char * title = [name UTF8String];
|
||||
snprintf(buffer, buffer_size, "%s", title);
|
||||
result = 1;
|
||||
}
|
||||
break;
|
||||
if ([currentApp isActive]) {
|
||||
NSString *name = window[(id) kCGWindowName];
|
||||
if (name.length > 0) {
|
||||
const char * title = [name UTF8String];
|
||||
snprintf(buffer, buffer_size, "%s", title);
|
||||
result = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
CFRelease(windows);
|
||||
}
|
||||
|
||||
CFRelease(windows);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -54,70 +52,64 @@ int32_t info_get_title(char *buffer, int32_t buffer_size)
|
|||
// Partially taken from: https://stackoverflow.com/questions/480866/get-the-title-of-the-current-active-window-document-in-mac-os-x/23451568#23451568
|
||||
int32_t info_get_title_fallback(char *buffer, int32_t buffer_size)
|
||||
{
|
||||
@autoreleasepool {
|
||||
// Get the process ID of the frontmost application.
|
||||
NSRunningApplication* app = [[NSWorkspace sharedWorkspace] frontmostApplication];
|
||||
pid_t pid = [app processIdentifier];
|
||||
// Get the process ID of the frontmost application.
|
||||
NSRunningApplication* app = [[NSWorkspace sharedWorkspace] frontmostApplication];
|
||||
pid_t pid = [app processIdentifier];
|
||||
|
||||
AXUIElementRef appElem = AXUIElementCreateApplication(pid);
|
||||
if (!appElem) {
|
||||
return -1;
|
||||
}
|
||||
AXUIElementRef appElem = AXUIElementCreateApplication(pid);
|
||||
if (!appElem) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get the accessibility element corresponding to the frontmost window
|
||||
// of the frontmost application.
|
||||
AXUIElementRef window = NULL;
|
||||
if (AXUIElementCopyAttributeValue(appElem,
|
||||
kAXFocusedWindowAttribute, (CFTypeRef*)&window) != kAXErrorSuccess) {
|
||||
CFRelease(appElem);
|
||||
return -2;
|
||||
}
|
||||
|
||||
// Finally, get the title of the frontmost window.
|
||||
CFStringRef title = NULL;
|
||||
AXError result = AXUIElementCopyAttributeValue(window, kAXTitleAttribute,
|
||||
(CFTypeRef*)&title);
|
||||
|
||||
// At this point, we don't need window and appElem anymore.
|
||||
CFRelease(window);
|
||||
// Get the accessibility element corresponding to the frontmost window
|
||||
// of the frontmost application.
|
||||
AXUIElementRef window = NULL;
|
||||
if (AXUIElementCopyAttributeValue(appElem,
|
||||
kAXFocusedWindowAttribute, (CFTypeRef*)&window) != kAXErrorSuccess) {
|
||||
CFRelease(appElem);
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (result != kAXErrorSuccess) {
|
||||
// Failed to get the window title.
|
||||
return -3;
|
||||
}
|
||||
// Finally, get the title of the frontmost window.
|
||||
CFStringRef title = NULL;
|
||||
AXError result = AXUIElementCopyAttributeValue(window, kAXTitleAttribute,
|
||||
(CFTypeRef*)&title);
|
||||
|
||||
if (CFStringGetCString(title, buffer, buffer_size, kCFStringEncodingUTF8)) {
|
||||
CFRelease(title);
|
||||
return 1;
|
||||
} else {
|
||||
return -4;
|
||||
}
|
||||
// At this point, we don't need window and appElem anymore.
|
||||
CFRelease(window);
|
||||
CFRelease(appElem);
|
||||
|
||||
if (result != kAXErrorSuccess) {
|
||||
// Failed to get the window title.
|
||||
return -3;
|
||||
}
|
||||
|
||||
if (CFStringGetCString(title, buffer, buffer_size, kCFStringEncodingUTF8)) {
|
||||
CFRelease(title);
|
||||
return 1;
|
||||
} else {
|
||||
return -4;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t info_get_exec(char *buffer, int32_t buffer_size)
|
||||
{
|
||||
@autoreleasepool {
|
||||
NSRunningApplication *frontApp = [[NSWorkspace sharedWorkspace] frontmostApplication];
|
||||
NSString *bundlePath = [frontApp bundleURL].path;
|
||||
const char * path = [bundlePath UTF8String];
|
||||
NSRunningApplication *frontApp = [[NSWorkspace sharedWorkspace] frontmostApplication];
|
||||
NSString *bundlePath = [frontApp bundleURL].path;
|
||||
const char * path = [bundlePath UTF8String];
|
||||
|
||||
snprintf(buffer, buffer_size, "%s", path);
|
||||
}
|
||||
snprintf(buffer, buffer_size, "%s", path);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int32_t info_get_class(char *buffer, int32_t buffer_size)
|
||||
{
|
||||
@autoreleasepool {
|
||||
NSRunningApplication *frontApp = [[NSWorkspace sharedWorkspace] frontmostApplication];
|
||||
NSString *bundleId = frontApp.bundleIdentifier;
|
||||
const char * bundle = [bundleId UTF8String];
|
||||
NSRunningApplication *frontApp = [[NSWorkspace sharedWorkspace] frontmostApplication];
|
||||
NSString *bundleId = frontApp.bundleIdentifier;
|
||||
const char * bundle = [bundleId UTF8String];
|
||||
|
||||
snprintf(buffer, buffer_size, "%s", bundle);
|
||||
}
|
||||
snprintf(buffer, buffer_size, "%s", bundle);
|
||||
|
||||
return 1;
|
||||
}
|
|
@ -27,7 +27,7 @@ scopeguard = "1.1.0"
|
|||
itertools = "0.10.0"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0.66"
|
||||
cc = "1.0.73"
|
||||
|
||||
[dev-dependencies]
|
||||
enum-as-inner = "0.3.3"
|
|
@ -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");
|
||||
}
|
||||
|
@ -64,6 +75,7 @@ fn cc_config() {
|
|||
println!("cargo:rustc-link-lib=dylib=c++");
|
||||
println!("cargo:rustc-link-lib=static=espansoinject");
|
||||
println!("cargo:rustc-link-lib=framework=Cocoa");
|
||||
println!("cargo:rustc-link-lib=framework=CoreGraphics");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -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,11 +88,13 @@ impl Default for InjectionOptions {
|
|||
delay: default_delay,
|
||||
disable_fast_inject: false,
|
||||
evdev_modifier_delay: 10,
|
||||
x11_use_xdotool_fallback: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Default)]
|
||||
pub struct InjectorCreationOptions {
|
||||
// Only relevant in X11 Linux systems, use the EVDEV backend instead of X11.
|
||||
pub use_evdev: bool,
|
||||
|
@ -128,18 +134,6 @@ pub trait KeyboardStateProvider {
|
|||
fn is_key_pressed(&self, code: u32) -> bool;
|
||||
}
|
||||
|
||||
impl Default for InjectorCreationOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
use_evdev: false,
|
||||
evdev_modifiers: None,
|
||||
evdev_max_modifier_combination_len: None,
|
||||
evdev_keyboard_rmlvo: None,
|
||||
keyboard_state_provider: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn get_injector(_options: InjectorCreationOptions) -> Result<Box<dyn Injector>> {
|
||||
info!("using Win32Injector");
|
||||
|
@ -159,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()?))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ use crate::{keys, InjectionOptions, Injector};
|
|||
#[allow(improper_ctypes)]
|
||||
#[link(name = "espansoinject", kind = "static")]
|
||||
extern "C" {
|
||||
pub fn inject_string(string: *const c_char);
|
||||
pub fn inject_string(string: *const c_char, delay: i32);
|
||||
pub fn inject_separate_vkeys(vkey_array: *const i32, vkey_count: i32, delay: i32);
|
||||
pub fn inject_vkeys_combination(vkey_array: *const i32, vkey_count: i32, delay: i32);
|
||||
}
|
||||
|
@ -60,10 +60,10 @@ impl MacInjector {
|
|||
}
|
||||
|
||||
impl Injector for MacInjector {
|
||||
fn send_string(&self, string: &str, _: InjectionOptions) -> Result<()> {
|
||||
fn send_string(&self, string: &str, options: InjectionOptions) -> Result<()> {
|
||||
let c_string = CString::new(string)?;
|
||||
unsafe {
|
||||
inject_string(c_string.as_ptr());
|
||||
inject_string(c_string.as_ptr(), options.delay);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
#include <stdint.h>
|
||||
|
||||
// Inject a complete string using the KEYEVENTF_UNICODE flag
|
||||
extern "C" void inject_string(char * string);
|
||||
extern "C" void inject_string(char * string, int32_t delay);
|
||||
|
||||
// Send a sequence of vkey presses and releases
|
||||
extern "C" void inject_separate_vkeys(int32_t *vkey_array, int32_t vkey_count, int32_t delay);
|
||||
|
|
|
@ -20,14 +20,17 @@
|
|||
#include "native.h"
|
||||
#include <string.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreGraphics/CoreGraphics.h>
|
||||
#include <vector>
|
||||
|
||||
// Events dispatched by espanso are "marked" with a custom location
|
||||
// so that we can later skip them in the detect module.
|
||||
CGPoint ESPANSO_POINT_MARKER = CGPointMake(-27469, 0);
|
||||
|
||||
void inject_string(char *string)
|
||||
void inject_string(char *string, int32_t delay)
|
||||
{
|
||||
long udelay = delay * 1000;
|
||||
|
||||
char * stringCopy = strdup(string);
|
||||
dispatch_async(dispatch_get_main_queue(), ^(void) {
|
||||
// Convert the c string to a UniChar array as required by the CGEventKeyboardSetUnicodeString method
|
||||
|
@ -48,7 +51,7 @@ void inject_string(char *string)
|
|||
CGEventPost(kCGHIDEventTap, e2);
|
||||
CFRelease(e2);
|
||||
|
||||
usleep(2000);
|
||||
usleep(udelay);
|
||||
}
|
||||
|
||||
// Because of a bug ( or undocumented limit ) of the CGEventKeyboardSetUnicodeString method
|
||||
|
@ -68,7 +71,7 @@ void inject_string(char *string)
|
|||
CGEventPost(kCGHIDEventTap, e);
|
||||
CFRelease(e);
|
||||
|
||||
usleep(2000);
|
||||
usleep(udelay);
|
||||
|
||||
// Some applications require an explicit release of the space key
|
||||
// For more information: https://github.com/federico-terzi/espanso/issues/159
|
||||
|
@ -77,7 +80,7 @@ void inject_string(char *string)
|
|||
CGEventPost(kCGHIDEventTap, e2);
|
||||
CFRelease(e2);
|
||||
|
||||
usleep(2000);
|
||||
usleep(udelay);
|
||||
|
||||
i += chunk_size;
|
||||
}
|
||||
|
|
|
@ -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,24 +40,38 @@ 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,
|
||||
pub modifiermap: *mut KeyCode,
|
||||
}
|
||||
|
||||
// XCreateIC values
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const XIMPreeditNothing: c_int = 0x0008;
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const XIMStatusNothing: c_int = 0x0400;
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const XNClientWindow_0: &[u8] = b"clientWindow\0";
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const XNInputStyle_0: &[u8] = b"inputStyle\0";
|
||||
|
||||
pub enum _XIC {}
|
||||
pub enum _XIM {}
|
||||
pub enum _XrmHashBucketRec {}
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub type XIC = *mut _XIC;
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub type XIM = *mut _XIM;
|
||||
pub type XrmDatabase = *mut _XrmHashBucketRec;
|
||||
|
||||
#[link(name = "X11")]
|
||||
extern "C" {
|
||||
pub fn XOpenDisplay(name: *const c_char) -> *mut Display;
|
||||
pub fn XCloseDisplay(display: *mut Display);
|
||||
pub fn XLookupString(
|
||||
event: *const XKeyEvent,
|
||||
buffer_return: *mut c_char,
|
||||
bytes_buffer: c_int,
|
||||
keysym_return: *mut KeySym,
|
||||
status_in_out: *const c_void,
|
||||
) -> c_int;
|
||||
pub fn XDefaultRootWindow(display: *mut Display) -> Window;
|
||||
pub fn XGetInputFocus(
|
||||
display: *mut Display,
|
||||
|
@ -82,4 +96,33 @@ extern "C" {
|
|||
) -> c_int;
|
||||
pub fn XSync(display: *mut Display, discard: c_int) -> c_int;
|
||||
pub fn XQueryKeymap(display: *mut Display, keys_return: *mut u8);
|
||||
pub fn XOpenIM(
|
||||
display: *mut Display,
|
||||
db: XrmDatabase,
|
||||
res_name: *mut c_char,
|
||||
res_class: *mut c_char,
|
||||
) -> XIM;
|
||||
pub fn XCreateIC(
|
||||
input_method: XIM,
|
||||
p2: *const u8,
|
||||
p3: c_int,
|
||||
p4: *const u8,
|
||||
p5: c_int,
|
||||
p6: *const c_void,
|
||||
) -> XIC;
|
||||
pub fn XDestroyIC(input_context: XIC);
|
||||
pub fn XmbResetIC(input_context: XIC) -> *mut c_char;
|
||||
pub fn Xutf8LookupString(
|
||||
input_context: XIC,
|
||||
event: *mut XKeyEvent,
|
||||
buffer: *mut c_char,
|
||||
buff_size: c_int,
|
||||
keysym_return: *mut c_ulong,
|
||||
status_return: *mut c_int,
|
||||
) -> c_int;
|
||||
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,417 +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,
|
||||
ffi::{CStr, CString},
|
||||
os::raw::c_char,
|
||||
slice,
|
||||
};
|
||||
|
||||
use ffi::{
|
||||
Display, KeyCode, KeyPress, KeyRelease, KeySym, Window, XCloseDisplay, XDefaultRootWindow,
|
||||
XFlush, XFreeModifiermap, XGetInputFocus, XGetModifierMapping, XKeyEvent, XLookupString,
|
||||
XQueryKeymap, XSendEvent, XSync, XTestFakeKeyEvent,
|
||||
};
|
||||
use log::error;
|
||||
|
||||
use crate::linux::raw_keys::convert_to_sym_array;
|
||||
use anyhow::Result;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{keys, InjectionOptions, Injector};
|
||||
|
||||
// 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 KeyRecord {
|
||||
// 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>,
|
||||
}
|
||||
|
||||
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) -> (CharMap, SymMap) {
|
||||
let mut char_map = HashMap::new();
|
||||
let mut sym_map = HashMap::new();
|
||||
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 root_window = unsafe { XDefaultRootWindow(display) };
|
||||
|
||||
// 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 event = XKeyEvent {
|
||||
display,
|
||||
keycode: code_with_offset,
|
||||
state: modifier_state,
|
||||
|
||||
// These might not even need to be filled
|
||||
window: root_window,
|
||||
root: root_window,
|
||||
same_screen: 1,
|
||||
time: 0,
|
||||
type_: KeyRelease,
|
||||
x_root: 1,
|
||||
y_root: 1,
|
||||
x: 1,
|
||||
y: 1,
|
||||
subwindow: 0,
|
||||
serial: 0,
|
||||
send_event: 0,
|
||||
};
|
||||
|
||||
let mut sym: KeySym = 0;
|
||||
let mut buffer: [c_char; 10] = [0; 10];
|
||||
let result = unsafe {
|
||||
XLookupString(
|
||||
&event,
|
||||
buffer.as_mut_ptr(),
|
||||
(buffer.len() - 1) as i32,
|
||||
&mut sym,
|
||||
std::ptr::null(),
|
||||
)
|
||||
};
|
||||
|
||||
let key_record = KeyRecord {
|
||||
code: code_with_offset,
|
||||
state: modifier_state,
|
||||
};
|
||||
|
||||
// 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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
(char_map, sym_map)
|
||||
}
|
||||
|
||||
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.code as u8)) {
|
||||
current_state |= 1 << mod_index;
|
||||
}
|
||||
}
|
||||
|
||||
current_record.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: &KeyRecord, 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: &KeyRecord, 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 {
|
||||
self.xtest_send_key(&record, true, delay_us);
|
||||
self.xtest_send_key(&record, false, delay_us);
|
||||
} else {
|
||||
self.send_key(focused_window, &record, true, delay_us);
|
||||
self.send_key(focused_window, &record, 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, true, delay_us);
|
||||
self.xtest_send_key(&record, false, delay_us);
|
||||
} else {
|
||||
self.send_key(focused_window, &record, true, delay_us);
|
||||
self.send_key(focused_window, &record, 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, true, delay_us);
|
||||
} else {
|
||||
self.send_key(focused_window, record, true, delay_us);
|
||||
}
|
||||
}
|
||||
|
||||
// Then release them
|
||||
for record in records.iter().rev() {
|
||||
if options.disable_fast_inject {
|
||||
self.xtest_send_key(record, false, delay_us);
|
||||
} else {
|
||||
self.send_key(focused_window, record, 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_ */
|
|
@ -14,4 +14,4 @@ lazy_static = "1.4.0"
|
|||
regex = "1.4.3"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0.66"
|
||||
cc = "1.0.73"
|
|
@ -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,
|
||||
|
|
|
@ -40,19 +40,11 @@ impl<Id> RegexMatch<Id> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Default)]
|
||||
pub struct RegexMatcherState {
|
||||
buffer: String,
|
||||
}
|
||||
|
||||
impl Default for RegexMatcherState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
buffer: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RegexMatcherOptions {
|
||||
pub max_buffer_size: usize,
|
||||
}
|
||||
|
|
|
@ -50,20 +50,12 @@ struct RollingMatcherStatePath<'a, Id> {
|
|||
events: Vec<(Event, IsWordSeparator)>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RollingMatcherOptions {
|
||||
pub char_word_separators: Vec<String>,
|
||||
pub key_word_separators: Vec<Key>,
|
||||
}
|
||||
|
||||
impl Default for RollingMatcherOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
char_word_separators: Vec::new(),
|
||||
key_word_separators: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RollingMatcher<Id> {
|
||||
char_word_separators: Vec<String>,
|
||||
key_word_separators: Vec<Key>,
|
||||
|
@ -193,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>,
|
||||
|
@ -72,22 +72,13 @@ impl<Id> RollingMatch<Id> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct StringMatchOptions {
|
||||
pub case_insensitive: bool,
|
||||
pub left_word: bool,
|
||||
pub right_word: bool,
|
||||
}
|
||||
|
||||
impl Default for StringMatchOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
case_insensitive: false,
|
||||
left_word: false,
|
||||
right_word: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -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())) {
|
||||
|
|
|
@ -15,7 +15,7 @@ lazy_static = "1.4.0"
|
|||
regex = "1.4.3"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0.66"
|
||||
cc = "1.0.73"
|
||||
regex = "1.4.3"
|
||||
zip = "0.5.12"
|
||||
winres = "0.1.11"
|
||||
|
|
|
@ -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() {
|
||||
|
@ -49,7 +62,7 @@ fn build_native() {
|
|||
.expect("unable to extract wxWidgets source dir");
|
||||
|
||||
// Compile wxWidgets
|
||||
let tool = cc::windows_registry::find_tool("msvc", "msbuild")
|
||||
let tool = cc::windows_registry::find_tool("msvc", "devenv")
|
||||
.expect("unable to locate MSVC compiler, did you install Visual Studio?");
|
||||
let mut vcvars_path = None;
|
||||
let mut current_root = tool.path();
|
||||
|
@ -77,7 +90,7 @@ fn build_native() {
|
|||
)
|
||||
.args(&[
|
||||
"/k",
|
||||
&vcvars_path.to_string_lossy().to_string(),
|
||||
&vcvars_path.to_string_lossy(),
|
||||
"&",
|
||||
"nmake",
|
||||
"/f",
|
||||
|
@ -121,6 +134,8 @@ fn build_native() {
|
|||
.file("src/sys/wizard/wizard_gui.cpp")
|
||||
.file("src/sys/welcome/welcome.cpp")
|
||||
.file("src/sys/welcome/welcome_gui.cpp")
|
||||
.file("src/sys/textview/textview.cpp")
|
||||
.file("src/sys/textview/textview_gui.cpp")
|
||||
.file("src/sys/troubleshooting/troubleshooting.cpp")
|
||||
.file("src/sys/troubleshooting/troubleshooting_gui.cpp")
|
||||
.flag("/EHsc")
|
||||
|
@ -152,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")
|
||||
|
@ -258,6 +284,8 @@ fn build_native() {
|
|||
.file("src/sys/wizard/wizard_gui.cpp")
|
||||
.file("src/sys/welcome/welcome.cpp")
|
||||
.file("src/sys/welcome/welcome_gui.cpp")
|
||||
.file("src/sys/textview/textview.cpp")
|
||||
.file("src/sys/textview/textview_gui.cpp")
|
||||
.file("src/sys/troubleshooting/troubleshooting.cpp")
|
||||
.file("src/sys/troubleshooting/troubleshooting_gui.cpp")
|
||||
.file("src/sys/common/mac.mm");
|
||||
|
@ -292,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);
|
||||
|
@ -311,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");
|
||||
|
@ -452,6 +480,8 @@ fn build_native() {
|
|||
.file("src/sys/wizard/wizard_gui.cpp")
|
||||
.file("src/sys/welcome/welcome.cpp")
|
||||
.file("src/sys/welcome/welcome_gui.cpp")
|
||||
.file("src/sys/textview/textview.cpp")
|
||||
.file("src/sys/textview/textview_gui.cpp")
|
||||
.file("src/sys/troubleshooting/troubleshooting.cpp")
|
||||
.file("src/sys/troubleshooting/troubleshooting_gui.cpp");
|
||||
build.flag("-std=c++17");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -23,6 +23,7 @@ extern crate lazy_static;
|
|||
pub mod form;
|
||||
pub mod search;
|
||||
mod sys;
|
||||
pub mod textview;
|
||||
pub mod troubleshooting;
|
||||
pub mod welcome;
|
||||
pub mod wizard;
|
||||
|
|
|
@ -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),
|
||||
|
@ -44,7 +43,9 @@ fn exact_match(query: &str, items: &[SearchItem]) -> Vec<usize> {
|
|||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, item)| {
|
||||
item.label.contains(query) || item.trigger.as_deref().map_or(false, |t| t.contains(query))
|
||||
item.label.contains(query)
|
||||
|| item.trigger.as_deref().map_or(false, |t| t.contains(query))
|
||||
|| item.search_terms.iter().any(|term| term.contains(query))
|
||||
})
|
||||
.map(|(i, _)| i)
|
||||
.collect()
|
||||
|
@ -61,6 +62,10 @@ fn case_insensitive_exact_match(query: &str, items: &[SearchItem]) -> Vec<usize>
|
|||
.trigger
|
||||
.as_deref()
|
||||
.map_or(false, |t| t.to_lowercase().contains(query))
|
||||
|| item
|
||||
.search_terms
|
||||
.iter()
|
||||
.any(|term| term.to_lowercase().contains(&lowercase_query))
|
||||
})
|
||||
.map(|(i, _)| i)
|
||||
.collect()
|
||||
|
@ -79,6 +84,10 @@ fn case_insensitive_keyword(query: &str, items: &[SearchItem]) -> Vec<usize> {
|
|||
.trigger
|
||||
.as_deref()
|
||||
.map_or(false, |t| t.to_lowercase().contains(keyword))
|
||||
&& !item
|
||||
.search_terms
|
||||
.iter()
|
||||
.any(|term| term.to_lowercase().contains(keyword))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -90,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('>') {
|
||||
(
|
||||
|
|
|
@ -58,6 +58,7 @@ pub struct SearchItem {
|
|||
pub id: String,
|
||||
pub label: String,
|
||||
pub trigger: Option<String>,
|
||||
pub search_terms: Vec<String>,
|
||||
|
||||
#[serde(default)]
|
||||
pub is_builtin: bool,
|
||||
|
|
|
@ -28,6 +28,7 @@ pub fn generate(config: SearchConfig) -> types::Search {
|
|||
id: item.id,
|
||||
label: item.label,
|
||||
trigger: item.trigger,
|
||||
search_terms: item.search_terms,
|
||||
is_builtin: item.is_builtin,
|
||||
})
|
||||
.collect();
|
||||
|
|
|
@ -96,6 +96,7 @@ private:
|
|||
void Submit();
|
||||
void OnSubmitBtn(wxCommandEvent& event);
|
||||
void OnCharHook(wxKeyEvent& event);
|
||||
void OnListBoxEvent(wxCommandEvent& event);
|
||||
void UpdateHelpText();
|
||||
void HandleNormalFocus(wxFocusEvent& event);
|
||||
void HandleMultilineFocus(wxFocusEvent& event);
|
||||
|
@ -141,7 +142,6 @@ FormFrame::FormFrame(const wxString& title, const wxPoint& pos, const wxSize& si
|
|||
|
||||
Bind(wxEVT_BUTTON, &FormFrame::OnSubmitBtn, this, ID_Submit);
|
||||
Bind(wxEVT_CHAR_HOOK, &FormFrame::OnCharHook, this, wxID_ANY);
|
||||
// TODO: register ESC click handler: https://forums.wxwidgets.org/viewtopic.php?t=41926
|
||||
|
||||
this->SetClientSize(panel->GetBestSize());
|
||||
this->CentreOnScreen();
|
||||
|
@ -218,6 +218,11 @@ void FormFrame::AddComponent(wxPanel *parent, wxBoxSizer *sizer, FieldMetadata m
|
|||
}
|
||||
|
||||
((wxListBox*)choice)->Bind(wxEVT_SET_FOCUS, &FormFrame::HandleNormalFocus, this, wxID_ANY);
|
||||
// ListBoxes prevent the global CHAR_HOOK handler from handling the Return key
|
||||
// correctly, so we need to handle the double click event too (which is triggered
|
||||
// when the enter key is pressed).
|
||||
// See: https://github.com/federico-terzi/espanso/issues/857
|
||||
((wxListBox*)choice)->Bind(wxEVT_LISTBOX_DCLICK, &FormFrame::OnListBoxEvent, this, wxID_ANY);
|
||||
|
||||
// Create the field wrapper
|
||||
std::unique_ptr<FieldWrapper> field((FieldWrapper*) new ListFieldWrapper((wxListBox*) choice));
|
||||
|
@ -311,6 +316,10 @@ void FormFrame::OnCharHook(wxKeyEvent& event) {
|
|||
}
|
||||
}
|
||||
|
||||
void FormFrame::OnListBoxEvent(wxCommandEvent& event) {
|
||||
Submit();
|
||||
}
|
||||
|
||||
extern "C" void interop_show_form(FormMetadata * _metadata, void (*callback)(ValuePair *values, int size, void *data), void *data) {
|
||||
// Setup high DPI support on Windows
|
||||
#ifdef __WXMSW__
|
||||
|
|
|
@ -168,3 +168,11 @@ typedef struct TroubleshootingMetadata {
|
|||
int (*dont_show_again_changed)(int);
|
||||
int (*open_file)(const char * file_name);
|
||||
} TroubleshootingMetadata;
|
||||
|
||||
// TextView
|
||||
|
||||
typedef struct TextViewMetadata {
|
||||
const char *window_icon_path;
|
||||
const char *title;
|
||||
const char *content;
|
||||
} TextViewMetadata;
|
||||
|
|
|
@ -189,6 +189,14 @@ pub struct ErrorMetadata {
|
|||
pub message: *const c_char,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct TextViewMetadata {
|
||||
pub window_icon_path: *const c_char,
|
||||
pub title: *const c_char,
|
||||
pub content: *const c_char,
|
||||
}
|
||||
|
||||
// Native bindings
|
||||
|
||||
#[allow(improper_ctypes)]
|
||||
|
@ -220,4 +228,7 @@ extern "C" {
|
|||
|
||||
// TROUBLESHOOTING
|
||||
pub(crate) fn interop_show_troubleshooting(metadata: *const TroubleshootingMetadata);
|
||||
|
||||
// TEXTVIEW
|
||||
pub(crate) fn interop_show_text_view(metadata: *const TextViewMetadata);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
pub mod form;
|
||||
pub mod search;
|
||||
pub mod textview;
|
||||
pub mod troubleshooting;
|
||||
pub mod welcome;
|
||||
pub mod wizard;
|
||||
|
|
|
@ -26,6 +26,7 @@ pub mod types {
|
|||
pub id: String,
|
||||
pub label: String,
|
||||
pub trigger: Option<String>,
|
||||
pub search_terms: Vec<String>,
|
||||
pub is_builtin: bool,
|
||||
}
|
||||
|
||||
|
@ -146,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();
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user